1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.appender.rolling;
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.nio.file.Files;
22 import java.nio.file.Path;
23 import java.util.Arrays;
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.SortedMap;
27 import java.util.concurrent.TimeUnit;
28 import java.util.zip.Deflater;
29
30 import org.apache.logging.log4j.core.Core;
31 import org.apache.logging.log4j.core.appender.rolling.action.Action;
32 import org.apache.logging.log4j.core.appender.rolling.action.CompositeAction;
33 import org.apache.logging.log4j.core.appender.rolling.action.FileRenameAction;
34 import org.apache.logging.log4j.core.appender.rolling.action.PathCondition;
35 import org.apache.logging.log4j.core.appender.rolling.action.PosixViewAttributeAction;
36 import org.apache.logging.log4j.core.config.Configuration;
37 import org.apache.logging.log4j.core.config.plugins.Plugin;
38 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
39 import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
40 import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
41 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
42 import org.apache.logging.log4j.core.config.plugins.PluginElement;
43 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
44 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
45 import org.apache.logging.log4j.core.util.Integers;
46
47
48
49
50
51
52
53
54
55
56
57
58
59 @Plugin(name = "DirectWriteRolloverStrategy", category = Core.CATEGORY_NAME, printObject = true)
60 public class DirectWriteRolloverStrategy extends AbstractRolloverStrategy implements DirectFileRolloverStrategy {
61
62 private static final int DEFAULT_MAX_FILES = 7;
63
64
65
66
67 public static class Builder implements org.apache.logging.log4j.core.util.Builder<DirectWriteRolloverStrategy> {
68 @PluginBuilderAttribute("maxFiles")
69 private String maxFiles;
70
71 @PluginBuilderAttribute("compressionLevel")
72 private String compressionLevelStr;
73
74 @PluginElement("Actions")
75 private Action[] customActions;
76
77 @PluginBuilderAttribute(value = "stopCustomActionsOnError")
78 private boolean stopCustomActionsOnError = true;
79
80 @PluginBuilderAttribute(value = "tempCompressedFilePattern")
81 private String tempCompressedFilePattern;
82
83 @PluginConfiguration
84 private Configuration config;
85
86 @Override
87 public DirectWriteRolloverStrategy build() {
88 int maxIndex = Integer.MAX_VALUE;
89 if (maxFiles != null) {
90 maxIndex = Integer.parseInt(maxFiles);
91 if (maxIndex < 0) {
92 maxIndex = Integer.MAX_VALUE;
93 } else if (maxIndex < 2) {
94 LOGGER.error("Maximum files too small. Limited to " + DEFAULT_MAX_FILES);
95 maxIndex = DEFAULT_MAX_FILES;
96 }
97 }
98 final int compressionLevel = Integers.parseInt(compressionLevelStr, Deflater.DEFAULT_COMPRESSION);
99 return new DirectWriteRolloverStrategy(maxIndex, compressionLevel, config.getStrSubstitutor(),
100 customActions, stopCustomActionsOnError, tempCompressedFilePattern);
101 }
102
103 public String getMaxFiles() {
104 return maxFiles;
105 }
106
107
108
109
110
111
112
113 public Builder withMaxFiles(final String maxFiles) {
114 this.maxFiles = maxFiles;
115 return this;
116 }
117
118 public String getCompressionLevelStr() {
119 return compressionLevelStr;
120 }
121
122
123
124
125
126
127
128 public Builder withCompressionLevelStr(final String compressionLevelStr) {
129 this.compressionLevelStr = compressionLevelStr;
130 return this;
131 }
132
133 public Action[] getCustomActions() {
134 return customActions;
135 }
136
137
138
139
140
141
142
143 public Builder withCustomActions(final Action[] customActions) {
144 this.customActions = customActions;
145 return this;
146 }
147
148 public boolean isStopCustomActionsOnError() {
149 return stopCustomActionsOnError;
150 }
151
152
153
154
155
156
157
158 public Builder withStopCustomActionsOnError(final boolean stopCustomActionsOnError) {
159 this.stopCustomActionsOnError = stopCustomActionsOnError;
160 return this;
161 }
162
163 public String getTempCompressedFilePattern() {
164 return tempCompressedFilePattern;
165 }
166
167
168
169
170
171
172
173 public Builder withTempCompressedFilePattern(final String tempCompressedFilePattern) {
174 this.tempCompressedFilePattern = tempCompressedFilePattern;
175 return this;
176 }
177
178 public Configuration getConfig() {
179 return config;
180 }
181
182
183
184
185
186
187
188 public Builder withConfig(final Configuration config) {
189 this.config = config;
190 return this;
191 }
192 }
193
194 @PluginBuilderFactory
195 public static Builder newBuilder() {
196 return new Builder();
197 }
198
199
200
201
202
203
204
205
206
207
208
209
210 @Deprecated
211 @PluginFactory
212 public static DirectWriteRolloverStrategy createStrategy(
213
214 @PluginAttribute("maxFiles") final String maxFiles,
215 @PluginAttribute("compressionLevel") final String compressionLevelStr,
216 @PluginElement("Actions") final Action[] customActions,
217 @PluginAttribute(value = "stopCustomActionsOnError", defaultBoolean = true)
218 final boolean stopCustomActionsOnError,
219 @PluginConfiguration final Configuration config) {
220 return newBuilder().withMaxFiles(maxFiles)
221 .withCompressionLevelStr(compressionLevelStr)
222 .withCustomActions(customActions)
223 .withStopCustomActionsOnError(stopCustomActionsOnError)
224 .withConfig(config)
225 .build();
226
227 }
228
229
230
231
232 private final int maxFiles;
233 private final int compressionLevel;
234 private final List<Action> customActions;
235 private final boolean stopCustomActionsOnError;
236 private volatile String currentFileName;
237 private int nextIndex = -1;
238 private final PatternProcessor tempCompressedFilePattern;
239 private volatile boolean usePrevTime = false;
240
241
242
243
244
245
246
247
248
249 @Deprecated
250 protected DirectWriteRolloverStrategy(final int maxFiles, final int compressionLevel,
251 final StrSubstitutor strSubstitutor, final Action[] customActions,
252 final boolean stopCustomActionsOnError) {
253 this(maxFiles, compressionLevel, strSubstitutor, customActions, stopCustomActionsOnError, null);
254 }
255
256
257
258
259
260
261
262
263
264
265 protected DirectWriteRolloverStrategy(final int maxFiles, final int compressionLevel,
266 final StrSubstitutor strSubstitutor, final Action[] customActions,
267 final boolean stopCustomActionsOnError, final String tempCompressedFilePatternString) {
268 super(strSubstitutor);
269 this.maxFiles = maxFiles;
270 this.compressionLevel = compressionLevel;
271 this.stopCustomActionsOnError = stopCustomActionsOnError;
272 this.customActions = customActions == null ? Collections.<Action> emptyList() : Arrays.asList(customActions);
273 this.tempCompressedFilePattern =
274 tempCompressedFilePatternString != null ? new PatternProcessor(tempCompressedFilePatternString) : null;
275 }
276
277 public int getCompressionLevel() {
278 return this.compressionLevel;
279 }
280
281 public List<Action> getCustomActions() {
282 return customActions;
283 }
284
285 public int getMaxFiles() {
286 return this.maxFiles;
287 }
288
289 public boolean isStopCustomActionsOnError() {
290 return stopCustomActionsOnError;
291 }
292
293 public PatternProcessor getTempCompressedFilePattern() {
294 return tempCompressedFilePattern;
295 }
296
297 private int purge(final RollingFileManager manager) {
298 final SortedMap<Integer, Path> eligibleFiles = getEligibleFiles(manager);
299 LOGGER.debug("Found {} eligible files, max is {}", eligibleFiles.size(), maxFiles);
300 while (eligibleFiles.size() >= maxFiles) {
301 try {
302 final Integer key = eligibleFiles.firstKey();
303 Files.delete(eligibleFiles.get(key));
304 eligibleFiles.remove(key);
305 } catch (final IOException ioe) {
306 LOGGER.error("Unable to delete {}", eligibleFiles.firstKey(), ioe);
307 break;
308 }
309 }
310 return eligibleFiles.size() > 0 ? eligibleFiles.lastKey() : 1;
311 }
312
313 @Override
314 public String getCurrentFileName(final RollingFileManager manager) {
315 if (currentFileName == null) {
316 final SortedMap<Integer, Path> eligibleFiles = getEligibleFiles(manager);
317 final int fileIndex = eligibleFiles.size() > 0 ? (nextIndex > 0 ? nextIndex : eligibleFiles.size()) : 1;
318 final StringBuilder buf = new StringBuilder(255);
319 manager.getPatternProcessor().formatFileName(strSubstitutor, buf, true, fileIndex);
320 final int suffixLength = suffixLength(buf.toString());
321 final String name = suffixLength > 0 ? buf.substring(0, buf.length() - suffixLength) : buf.toString();
322 currentFileName = name;
323 }
324 return currentFileName;
325 }
326
327 @Override
328 public void clearCurrentFileName() {
329 currentFileName = null;
330 }
331
332
333
334
335
336
337
338
339 @Override
340 public RolloverDescription rollover(final RollingFileManager manager) throws SecurityException {
341 LOGGER.debug("Rolling " + currentFileName);
342 if (maxFiles < 0) {
343 return null;
344 }
345 final long startNanos = System.nanoTime();
346 final int fileIndex = purge(manager);
347 if (LOGGER.isTraceEnabled()) {
348 final double durationMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos);
349 LOGGER.trace("DirectWriteRolloverStrategy.purge() took {} milliseconds", durationMillis);
350 }
351 Action compressAction = null;
352 final String sourceName = getCurrentFileName(manager);
353 String compressedName = sourceName;
354 currentFileName = null;
355 nextIndex = fileIndex + 1;
356 final FileExtension fileExtension = manager.getFileExtension();
357 if (fileExtension != null) {
358 compressedName += fileExtension.getExtension();
359 if (tempCompressedFilePattern != null) {
360 final StringBuilder buf = new StringBuilder();
361 tempCompressedFilePattern.formatFileName(strSubstitutor, buf, fileIndex);
362 final String tmpCompressedName = buf.toString();
363 final File tmpCompressedNameFile = new File(tmpCompressedName);
364 final File parentFile = tmpCompressedNameFile.getParentFile();
365 if (parentFile != null) {
366 parentFile.mkdirs();
367 }
368 compressAction = new CompositeAction(
369 Arrays.asList(fileExtension.createCompressAction(sourceName, tmpCompressedName,
370 true, compressionLevel),
371 new FileRenameAction(tmpCompressedNameFile,
372 new File(compressedName), true)),
373 true);
374 } else {
375 compressAction = fileExtension.createCompressAction(sourceName, compressedName,
376 true, compressionLevel);
377 }
378 }
379
380 if (compressAction != null && manager.isAttributeViewEnabled()) {
381
382
383 final Action posixAttributeViewAction = PosixViewAttributeAction.newBuilder()
384 .withBasePath(compressedName)
385 .withFollowLinks(false)
386 .withMaxDepth(1)
387 .withPathConditions(new PathCondition[0])
388 .withSubst(getStrSubstitutor())
389 .withFilePermissions(manager.getFilePermissions())
390 .withFileOwner(manager.getFileOwner())
391 .withFileGroup(manager.getFileGroup())
392 .build();
393
394 compressAction = new CompositeAction(Arrays.asList(compressAction, posixAttributeViewAction), false);
395 }
396
397 final Action asyncAction = merge(compressAction, customActions, stopCustomActionsOnError);
398 return new RolloverDescriptionImpl(sourceName, false, null, asyncAction);
399 }
400
401 @Override
402 public String toString() {
403 return "DirectWriteRolloverStrategy(maxFiles=" + maxFiles + ')';
404 }
405
406 }