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