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.Map;
27 import java.util.SortedMap;
28 import java.util.concurrent.TimeUnit;
29 import java.util.zip.Deflater;
30
31 import org.apache.logging.log4j.core.Core;
32 import org.apache.logging.log4j.core.appender.rolling.action.Action;
33 import org.apache.logging.log4j.core.appender.rolling.action.CompositeAction;
34 import org.apache.logging.log4j.core.appender.rolling.action.FileRenameAction;
35 import org.apache.logging.log4j.core.appender.rolling.action.PathCondition;
36 import org.apache.logging.log4j.core.appender.rolling.action.PosixViewAttributeAction;
37 import org.apache.logging.log4j.core.config.Configuration;
38 import org.apache.logging.log4j.core.config.plugins.Plugin;
39 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
40 import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
41 import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
42 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
43 import org.apache.logging.log4j.core.config.plugins.PluginElement;
44 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
45 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
46 import org.apache.logging.log4j.core.util.Integers;
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81 @Plugin(name = "DefaultRolloverStrategy", category = Core.CATEGORY_NAME, printObject = true)
82 public class DefaultRolloverStrategy extends AbstractRolloverStrategy {
83
84 private static final int MIN_WINDOW_SIZE = 1;
85 private static final int DEFAULT_WINDOW_SIZE = 7;
86
87
88
89
90 public static class Builder implements org.apache.logging.log4j.core.util.Builder<DefaultRolloverStrategy> {
91 @PluginBuilderAttribute("max")
92 private String max;
93
94 @PluginBuilderAttribute("min")
95 private String min;
96
97 @PluginBuilderAttribute("fileIndex")
98 private String fileIndex;
99
100 @PluginBuilderAttribute("compressionLevel")
101 private String compressionLevelStr;
102
103 @PluginElement("Actions")
104 private Action[] customActions;
105
106 @PluginBuilderAttribute(value = "stopCustomActionsOnError")
107 private boolean stopCustomActionsOnError = true;
108
109 @PluginBuilderAttribute(value = "tempCompressedFilePattern")
110 private String tempCompressedFilePattern;
111
112 @PluginConfiguration
113 private Configuration config;
114
115 @Override
116 public DefaultRolloverStrategy build() {
117 int minIndex;
118 int maxIndex;
119 boolean useMax;
120
121 if (fileIndex != null && fileIndex.equalsIgnoreCase("nomax")) {
122 minIndex = Integer.MIN_VALUE;
123 maxIndex = Integer.MAX_VALUE;
124 useMax = false;
125 } else {
126 useMax = fileIndex == null ? true : fileIndex.equalsIgnoreCase("max");
127 minIndex = MIN_WINDOW_SIZE;
128 if (min != null) {
129 minIndex = Integer.parseInt(min);
130 if (minIndex < 1) {
131 LOGGER.error("Minimum window size too small. Limited to " + MIN_WINDOW_SIZE);
132 minIndex = MIN_WINDOW_SIZE;
133 }
134 }
135 maxIndex = DEFAULT_WINDOW_SIZE;
136 if (max != null) {
137 maxIndex = Integer.parseInt(max);
138 if (maxIndex < minIndex) {
139 maxIndex = minIndex < DEFAULT_WINDOW_SIZE ? DEFAULT_WINDOW_SIZE : minIndex;
140 LOGGER.error("Maximum window size must be greater than the minimum windows size. Set to " + maxIndex);
141 }
142 }
143 }
144 final int compressionLevel = Integers.parseInt(compressionLevelStr, Deflater.DEFAULT_COMPRESSION);
145 return new DefaultRolloverStrategy(minIndex, maxIndex, useMax, compressionLevel, config.getStrSubstitutor(),
146 customActions, stopCustomActionsOnError, tempCompressedFilePattern);
147 }
148
149 public String getMax() {
150 return max;
151 }
152
153
154
155
156
157
158
159 public Builder withMax(final String max) {
160 this.max = max;
161 return this;
162 }
163
164 public String getMin() {
165 return min;
166 }
167
168
169
170
171
172
173
174 public Builder withMin(final String min) {
175 this.min = min;
176 return this;
177 }
178
179 public String getFileIndex() {
180 return fileIndex;
181 }
182
183
184
185
186
187
188
189
190 public Builder withFileIndex(final String fileIndex) {
191 this.fileIndex = fileIndex;
192 return this;
193 }
194
195 public String getCompressionLevelStr() {
196 return compressionLevelStr;
197 }
198
199
200
201
202
203
204
205 public Builder withCompressionLevelStr(final String compressionLevelStr) {
206 this.compressionLevelStr = compressionLevelStr;
207 return this;
208 }
209
210 public Action[] getCustomActions() {
211 return customActions;
212 }
213
214
215
216
217
218
219
220 public Builder withCustomActions(final Action[] customActions) {
221 this.customActions = customActions;
222 return this;
223 }
224
225 public boolean isStopCustomActionsOnError() {
226 return stopCustomActionsOnError;
227 }
228
229
230
231
232
233
234
235 public Builder withStopCustomActionsOnError(final boolean stopCustomActionsOnError) {
236 this.stopCustomActionsOnError = stopCustomActionsOnError;
237 return this;
238 }
239
240 public String getTempCompressedFilePattern() {
241 return tempCompressedFilePattern;
242 }
243
244
245
246
247
248
249
250 public Builder withTempCompressedFilePattern(final String tempCompressedFilePattern) {
251 this.tempCompressedFilePattern = tempCompressedFilePattern;
252 return this;
253 }
254
255 public Configuration getConfig() {
256 return config;
257 }
258
259
260
261
262
263
264
265 public Builder withConfig(final Configuration config) {
266 this.config = config;
267 return this;
268 }
269 }
270
271 @PluginBuilderFactory
272 public static Builder newBuilder() {
273 return new Builder();
274 }
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290 @PluginFactory
291 @Deprecated
292 public static DefaultRolloverStrategy createStrategy(
293
294 @PluginAttribute("max") final String max,
295 @PluginAttribute("min") final String min,
296 @PluginAttribute("fileIndex") final String fileIndex,
297 @PluginAttribute("compressionLevel") final String compressionLevelStr,
298 @PluginElement("Actions") final Action[] customActions,
299 @PluginAttribute(value = "stopCustomActionsOnError", defaultBoolean = true)
300 final boolean stopCustomActionsOnError,
301 @PluginConfiguration final Configuration config) {
302 return DefaultRolloverStrategy.newBuilder()
303 .withMin(min)
304 .withMax(max)
305 .withFileIndex(fileIndex)
306 .withCompressionLevelStr(compressionLevelStr)
307 .withCustomActions(customActions)
308 .withStopCustomActionsOnError(stopCustomActionsOnError)
309 .withConfig(config)
310 .build();
311
312 }
313
314
315
316
317 private final int maxIndex;
318
319
320
321
322 private final int minIndex;
323 private final boolean useMax;
324 private final int compressionLevel;
325 private final List<Action> customActions;
326 private final boolean stopCustomActionsOnError;
327 private final PatternProcessor tempCompressedFilePattern;
328
329
330
331
332
333
334
335
336
337
338 @Deprecated
339 protected DefaultRolloverStrategy(final int minIndex, final int maxIndex, final boolean useMax,
340 final int compressionLevel, final StrSubstitutor strSubstitutor, final Action[] customActions,
341 final boolean stopCustomActionsOnError) {
342 this(minIndex, maxIndex, useMax, compressionLevel,
343 strSubstitutor, customActions, stopCustomActionsOnError, null);
344 }
345
346
347
348
349
350
351
352
353
354
355
356 protected DefaultRolloverStrategy(final int minIndex, final int maxIndex, final boolean useMax,
357 final int compressionLevel, final StrSubstitutor strSubstitutor, final Action[] customActions,
358 final boolean stopCustomActionsOnError, final String tempCompressedFilePatternString) {
359 super(strSubstitutor);
360 this.minIndex = minIndex;
361 this.maxIndex = maxIndex;
362 this.useMax = useMax;
363 this.compressionLevel = compressionLevel;
364 this.stopCustomActionsOnError = stopCustomActionsOnError;
365 this.customActions = customActions == null ? Collections.<Action> emptyList() : Arrays.asList(customActions);
366 this.tempCompressedFilePattern =
367 tempCompressedFilePatternString != null ? new PatternProcessor(tempCompressedFilePatternString) : null;
368 }
369
370 public int getCompressionLevel() {
371 return this.compressionLevel;
372 }
373
374 public List<Action> getCustomActions() {
375 return customActions;
376 }
377
378 public int getMaxIndex() {
379 return this.maxIndex;
380 }
381
382 public int getMinIndex() {
383 return this.minIndex;
384 }
385
386 public boolean isStopCustomActionsOnError() {
387 return stopCustomActionsOnError;
388 }
389
390 public boolean isUseMax() {
391 return useMax;
392 }
393
394 public PatternProcessor getTempCompressedFilePattern() {
395 return tempCompressedFilePattern;
396 }
397
398 private int purge(final int lowIndex, final int highIndex, final RollingFileManager manager) {
399 return useMax ? purgeAscending(lowIndex, highIndex, manager) : purgeDescending(lowIndex, highIndex, manager);
400 }
401
402
403
404
405
406
407
408
409
410
411 private int purgeAscending(final int lowIndex, final int highIndex, final RollingFileManager manager) {
412 final SortedMap<Integer, Path> eligibleFiles = getEligibleFiles(manager);
413 final int maxFiles = highIndex - lowIndex + 1;
414
415 boolean renameFiles = false;
416 while (eligibleFiles.size() >= maxFiles) {
417 try {
418 LOGGER.debug("Eligible files: {}", eligibleFiles);
419 final Integer key = eligibleFiles.firstKey();
420 LOGGER.debug("Deleting {}", eligibleFiles.get(key).toFile().getAbsolutePath());
421 Files.delete(eligibleFiles.get(key));
422 eligibleFiles.remove(key);
423 renameFiles = true;
424 } catch (final IOException ioe) {
425 LOGGER.error("Unable to delete {}, {}", eligibleFiles.firstKey(), ioe.getMessage(), ioe);
426 break;
427 }
428 }
429 final StringBuilder buf = new StringBuilder();
430 if (renameFiles) {
431 for (final Map.Entry<Integer, Path> entry : eligibleFiles.entrySet()) {
432 buf.setLength(0);
433
434 manager.getPatternProcessor().formatFileName(strSubstitutor, buf, entry.getKey() - 1);
435 final String currentName = entry.getValue().toFile().getName();
436 String renameTo = buf.toString();
437 final int suffixLength = suffixLength(renameTo);
438 if (suffixLength > 0 && suffixLength(currentName) == 0) {
439 renameTo = renameTo.substring(0, renameTo.length() - suffixLength);
440 }
441 final Action action = new FileRenameAction(entry.getValue().toFile(), new File(renameTo), true);
442 try {
443 LOGGER.debug("DefaultRolloverStrategy.purgeAscending executing {}", action);
444 if (!action.execute()) {
445 return -1;
446 }
447 } catch (final Exception ex) {
448 LOGGER.warn("Exception during purge in RollingFileAppender", ex);
449 return -1;
450 }
451 }
452 }
453
454 return eligibleFiles.size() > 0 ?
455 (eligibleFiles.lastKey() < highIndex ? eligibleFiles.lastKey() + 1 : highIndex) : lowIndex;
456 }
457
458
459
460
461
462
463
464
465
466
467 private int purgeDescending(final int lowIndex, final int highIndex, final RollingFileManager manager) {
468
469 final SortedMap<Integer, Path> eligibleFiles = getEligibleFiles(manager, false);
470 final int maxFiles = highIndex - lowIndex + 1;
471
472 while (eligibleFiles.size() >= maxFiles) {
473 try {
474 final Integer key = eligibleFiles.firstKey();
475 Files.delete(eligibleFiles.get(key));
476 eligibleFiles.remove(key);
477 } catch (final IOException ioe) {
478 LOGGER.error("Unable to delete {}, {}", eligibleFiles.firstKey(), ioe.getMessage(), ioe);
479 break;
480 }
481 }
482 final StringBuilder buf = new StringBuilder();
483 for (final Map.Entry<Integer, Path> entry : eligibleFiles.entrySet()) {
484 buf.setLength(0);
485
486 manager.getPatternProcessor().formatFileName(strSubstitutor, buf, entry.getKey() + 1);
487 final String currentName = entry.getValue().toFile().getName();
488 String renameTo = buf.toString();
489 final int suffixLength = suffixLength(renameTo);
490 if (suffixLength > 0 && suffixLength(currentName) == 0) {
491 renameTo = renameTo.substring(0, renameTo.length() - suffixLength);
492 }
493 final Action action = new FileRenameAction(entry.getValue().toFile(), new File(renameTo), true);
494 try {
495 LOGGER.debug("DefaultRolloverStrategy.purgeDescending executing {}", action);
496 if (!action.execute()) {
497 return -1;
498 }
499 } catch (final Exception ex) {
500 LOGGER.warn("Exception during purge in RollingFileAppender", ex);
501 return -1;
502 }
503 }
504
505 return lowIndex;
506 }
507
508
509
510
511
512
513
514
515 @Override
516 public RolloverDescription rollover(final RollingFileManager manager) throws SecurityException {
517 int fileIndex;
518 if (minIndex == Integer.MIN_VALUE) {
519 final SortedMap<Integer, Path> eligibleFiles = getEligibleFiles(manager);
520 fileIndex = eligibleFiles.size() > 0 ? eligibleFiles.lastKey() + 1 : 1;
521 } else {
522 if (maxIndex < 0) {
523 return null;
524 }
525 final long startNanos = System.nanoTime();
526 fileIndex = purge(minIndex, maxIndex, manager);
527 if (fileIndex < 0) {
528 return null;
529 }
530 if (LOGGER.isTraceEnabled()) {
531 final double durationMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos);
532 LOGGER.trace("DefaultRolloverStrategy.purge() took {} milliseconds", durationMillis);
533 }
534 }
535 final StringBuilder buf = new StringBuilder(255);
536 manager.getPatternProcessor().formatFileName(strSubstitutor, buf, fileIndex);
537 final String currentFileName = manager.getFileName();
538
539 String renameTo = buf.toString();
540 final String compressedName = renameTo;
541 Action compressAction = null;
542
543 final FileExtension fileExtension = manager.getFileExtension();
544 if (fileExtension != null) {
545 final File renameToFile = new File(renameTo);
546 renameTo = renameTo.substring(0, renameTo.length() - fileExtension.length());
547 if (tempCompressedFilePattern != null) {
548 buf.delete(0, buf.length());
549 tempCompressedFilePattern.formatFileName(strSubstitutor, buf, fileIndex);
550 final String tmpCompressedName = buf.toString();
551 final File tmpCompressedNameFile = new File(tmpCompressedName);
552 final File parentFile = tmpCompressedNameFile.getParentFile();
553 if (parentFile != null) {
554 parentFile.mkdirs();
555 }
556 compressAction = new CompositeAction(
557 Arrays.asList(fileExtension.createCompressAction(renameTo, tmpCompressedName,
558 true, compressionLevel),
559 new FileRenameAction(tmpCompressedNameFile,
560 renameToFile, true)),
561 true);
562 } else {
563 compressAction = fileExtension.createCompressAction(renameTo, compressedName,
564 true, compressionLevel);
565 }
566 }
567
568 if (currentFileName.equals(renameTo)) {
569 LOGGER.warn("Attempt to rename file {} to itself will be ignored", currentFileName);
570 return new RolloverDescriptionImpl(currentFileName, false, null, null);
571 }
572
573 if (compressAction != null && manager.isAttributeViewEnabled()) {
574
575
576 final Action posixAttributeViewAction = PosixViewAttributeAction.newBuilder()
577 .withBasePath(compressedName)
578 .withFollowLinks(false)
579 .withMaxDepth(1)
580 .withPathConditions(new PathCondition[0])
581 .withSubst(getStrSubstitutor())
582 .withFilePermissions(manager.getFilePermissions())
583 .withFileOwner(manager.getFileOwner())
584 .withFileGroup(manager.getFileGroup())
585 .build();
586
587 compressAction = new CompositeAction(Arrays.asList(compressAction, posixAttributeViewAction), false);
588 }
589
590 final FileRenameAction renameAction = new FileRenameAction(new File(currentFileName), new File(renameTo),
591 manager.isRenameEmptyFiles());
592
593 final Action asyncAction = merge(compressAction, customActions, stopCustomActionsOnError);
594 return new RolloverDescriptionImpl(currentFileName, false, renameAction, asyncAction);
595 }
596
597 @Override
598 public String toString() {
599 return "DefaultRolloverStrategy(min=" + minIndex + ", max=" + maxIndex + ", useMax=" + useMax + ")";
600 }
601
602 }