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.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Collections;
23 import java.util.List;
24 import java.util.Objects;
25 import java.util.concurrent.TimeUnit;
26 import java.util.zip.Deflater;
27
28 import org.apache.logging.log4j.Logger;
29 import org.apache.logging.log4j.core.appender.rolling.action.Action;
30 import org.apache.logging.log4j.core.appender.rolling.action.CommonsCompressAction;
31 import org.apache.logging.log4j.core.appender.rolling.action.CompositeAction;
32 import org.apache.logging.log4j.core.appender.rolling.action.FileRenameAction;
33 import org.apache.logging.log4j.core.appender.rolling.action.GzCompressAction;
34 import org.apache.logging.log4j.core.appender.rolling.action.ZipCompressAction;
35 import org.apache.logging.log4j.core.config.Configuration;
36 import org.apache.logging.log4j.core.config.plugins.Plugin;
37 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
38 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
39 import org.apache.logging.log4j.core.config.plugins.PluginElement;
40 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
41 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
42 import org.apache.logging.log4j.core.util.Integers;
43 import org.apache.logging.log4j.status.StatusLogger;
44
45
46
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 @Plugin(name = "DefaultRolloverStrategy", category = "Core", printObject = true)
79 public class DefaultRolloverStrategy implements RolloverStrategy {
80
81
82
83
84
85
86 static enum FileExtensions {
87 ZIP(".zip") {
88 @Override
89 Action createCompressAction(final String renameTo, final String compressedName, final boolean deleteSource,
90 final int compressionLevel) {
91 return new ZipCompressAction(source(renameTo), target(compressedName), deleteSource, compressionLevel);
92 }
93 },
94 GZ(".gz") {
95 @Override
96 Action createCompressAction(final String renameTo, final String compressedName, final boolean deleteSource,
97 final int compressionLevel) {
98 return new GzCompressAction(source(renameTo), target(compressedName), deleteSource);
99 }
100 },
101 BZIP2(".bz2") {
102 @Override
103 Action createCompressAction(final String renameTo, final String compressedName, final boolean deleteSource,
104 final int compressionLevel) {
105
106 return new CommonsCompressAction("bzip2", source(renameTo), target(compressedName), deleteSource);
107 }
108 },
109 DEFLATE(".deflate") {
110 @Override
111 Action createCompressAction(final String renameTo, final String compressedName, final boolean deleteSource,
112 final int compressionLevel) {
113
114 return new CommonsCompressAction("deflate", source(renameTo), target(compressedName), deleteSource);
115 }
116 },
117 PACK200(".pack200") {
118 @Override
119 Action createCompressAction(final String renameTo, final String compressedName, final boolean deleteSource,
120 final int compressionLevel) {
121
122 return new CommonsCompressAction("pack200", source(renameTo), target(compressedName), deleteSource);
123 }
124 },
125 XY(".xy") {
126 @Override
127 Action createCompressAction(final String renameTo, final String compressedName, final boolean deleteSource,
128 final int compressionLevel) {
129
130 return new CommonsCompressAction("xy", source(renameTo), target(compressedName), deleteSource);
131 }
132 };
133
134 private final String extension;
135
136 private FileExtensions(final String extension) {
137 Objects.requireNonNull(extension, "extension");
138 this.extension = extension;
139 }
140
141 String getExtension() {
142 return extension;
143 }
144
145 boolean isExtensionFor(final String s) {
146 return s.endsWith(this.extension);
147 }
148
149 int length() {
150 return extension.length();
151 }
152
153 File source(String fileName) {
154 return new File(fileName);
155 }
156
157 File target(String fileName) {
158 return new File(fileName);
159 }
160
161 abstract Action createCompressAction(String renameTo, String compressedName, boolean deleteSource,
162 int compressionLevel);
163
164 static FileExtensions lookup(String fileExtension) {
165 for (FileExtensions ext : values()) {
166 if (ext.isExtensionFor(fileExtension)) {
167 return ext;
168 }
169 }
170 return null;
171 }
172 };
173
174
175
176
177 protected static final Logger LOGGER = StatusLogger.getLogger();
178
179 private static final int MIN_WINDOW_SIZE = 1;
180 private static final int DEFAULT_WINDOW_SIZE = 7;
181
182
183
184
185 private final int maxIndex;
186
187
188
189
190 private final int minIndex;
191 private final boolean useMax;
192 private final StrSubstitutor subst;
193 private final int compressionLevel;
194
195 private List<Action> customActions;
196
197 private boolean stopCustomActionsOnError;
198
199
200
201
202
203
204
205
206
207 protected DefaultRolloverStrategy(final int minIndex, final int maxIndex, final boolean useMax,
208 final int compressionLevel, final StrSubstitutor subst, final Action[] customActions,
209 final boolean stopCustomActionsOnError) {
210 this.minIndex = minIndex;
211 this.maxIndex = maxIndex;
212 this.useMax = useMax;
213 this.compressionLevel = compressionLevel;
214 this.subst = subst;
215 this.stopCustomActionsOnError = stopCustomActionsOnError;
216 this.customActions = customActions == null ? Collections.<Action> emptyList() : Arrays.asList(customActions);
217 }
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232 @PluginFactory
233 public static DefaultRolloverStrategy createStrategy(
234
235 @PluginAttribute("max") final String max,
236 @PluginAttribute("min") final String min,
237 @PluginAttribute("fileIndex") final String fileIndex,
238 @PluginAttribute("compressionLevel") final String compressionLevelStr,
239 @PluginElement("Actions") final Action[] customActions,
240 @PluginAttribute(value = "stopCustomActionsOnError", defaultBoolean = true)
241 final boolean stopCustomActionsOnError,
242 @PluginConfiguration final Configuration config) {
243
244 final boolean useMax = fileIndex == null ? true : fileIndex.equalsIgnoreCase("max");
245 int minIndex = MIN_WINDOW_SIZE;
246 if (min != null) {
247 minIndex = Integer.parseInt(min);
248 if (minIndex < 1) {
249 LOGGER.error("Minimum window size too small. Limited to " + MIN_WINDOW_SIZE);
250 minIndex = MIN_WINDOW_SIZE;
251 }
252 }
253 int maxIndex = DEFAULT_WINDOW_SIZE;
254 if (max != null) {
255 maxIndex = Integer.parseInt(max);
256 if (maxIndex < minIndex) {
257 maxIndex = minIndex < DEFAULT_WINDOW_SIZE ? DEFAULT_WINDOW_SIZE : minIndex;
258 LOGGER.error("Maximum window size must be greater than the minimum windows size. Set to " + maxIndex);
259 }
260 }
261 final int compressionLevel = Integers.parseInt(compressionLevelStr, Deflater.DEFAULT_COMPRESSION);
262 return new DefaultRolloverStrategy(minIndex, maxIndex, useMax, compressionLevel, config.getStrSubstitutor(),
263 customActions, stopCustomActionsOnError);
264 }
265
266 public int getCompressionLevel() {
267 return this.compressionLevel;
268 }
269
270 public int getMaxIndex() {
271 return this.maxIndex;
272 }
273
274 public int getMinIndex() {
275 return this.minIndex;
276 }
277
278 private int purge(final int lowIndex, final int highIndex, final RollingFileManager manager) {
279 return useMax ? purgeAscending(lowIndex, highIndex, manager) : purgeDescending(lowIndex, highIndex, manager);
280 }
281
282
283
284
285
286
287
288
289
290
291 private int purgeAscending(final int lowIndex, final int highIndex, final RollingFileManager manager) {
292 final List<FileRenameAction> renames = new ArrayList<>();
293 final StringBuilder buf = new StringBuilder();
294
295
296 manager.getPatternProcessor().formatFileName(subst, buf, highIndex);
297 String highFilename = subst.replace(buf);
298 final int suffixLength = suffixLength(highFilename);
299 int maxIndex = 0;
300
301 for (int i = highIndex; i >= lowIndex; i--) {
302 File toRename = new File(highFilename);
303 if (i == highIndex && toRename.exists()) {
304 maxIndex = highIndex;
305 } else if (maxIndex == 0 && toRename.exists()) {
306 maxIndex = i + 1;
307 break;
308 }
309
310 boolean isBase = false;
311
312 if (suffixLength > 0) {
313 final File toRenameBase = new File(highFilename.substring(0, highFilename.length() - suffixLength));
314
315 if (toRename.exists()) {
316 if (toRenameBase.exists()) {
317 LOGGER.debug("DefaultRolloverStrategy.purgeAscending deleting {} base of {}.",
318 toRenameBase, toRename);
319 toRenameBase.delete();
320 }
321 } else {
322 toRename = toRenameBase;
323 isBase = true;
324 }
325 }
326
327 if (toRename.exists()) {
328
329
330
331
332 if (i == lowIndex) {
333 LOGGER.debug("DefaultRolloverStrategy.purgeAscending deleting {} at low index {}: all slots full.",
334 toRename, i);
335 if (!toRename.delete()) {
336 return -1;
337 }
338
339 break;
340 }
341
342
343
344
345 buf.setLength(0);
346
347 manager.getPatternProcessor().formatFileName(subst, buf, i - 1);
348
349 final String lowFilename = subst.replace(buf);
350 String renameTo = lowFilename;
351
352 if (isBase) {
353 renameTo = lowFilename.substring(0, lowFilename.length() - suffixLength);
354 }
355
356 renames.add(new FileRenameAction(toRename, new File(renameTo), true));
357 highFilename = lowFilename;
358 } else {
359 buf.setLength(0);
360
361 manager.getPatternProcessor().formatFileName(subst, buf, i - 1);
362
363 highFilename = subst.replace(buf);
364 }
365 }
366 if (maxIndex == 0) {
367 maxIndex = lowIndex;
368 }
369
370
371
372
373 for (int i = renames.size() - 1; i >= 0; i--) {
374 final Action action = renames.get(i);
375 try {
376 LOGGER.debug("DefaultRolloverStrategy.purgeAscending executing {} of {}: {}",
377 i, renames.size(), action);
378 if (!action.execute()) {
379 return -1;
380 }
381 } catch (final Exception ex) {
382 LOGGER.warn("Exception during purge in RollingFileAppender", ex);
383 return -1;
384 }
385 }
386 return maxIndex;
387 }
388
389
390
391
392
393
394
395
396
397
398 private int purgeDescending(final int lowIndex, final int highIndex, final RollingFileManager manager) {
399 final List<FileRenameAction> renames = new ArrayList<>();
400 final StringBuilder buf = new StringBuilder();
401
402
403 manager.getPatternProcessor().formatFileName(subst, buf, lowIndex);
404
405 String lowFilename = subst.replace(buf);
406 final int suffixLength = suffixLength(lowFilename);
407
408 for (int i = lowIndex; i <= highIndex; i++) {
409 File toRename = new File(lowFilename);
410 boolean isBase = false;
411
412 if (suffixLength > 0) {
413 final File toRenameBase = new File(lowFilename.substring(0, lowFilename.length() - suffixLength));
414
415 if (toRename.exists()) {
416 if (toRenameBase.exists()) {
417 LOGGER.debug("DefaultRolloverStrategy.purgeDescending deleting {} base of {}.",
418 toRenameBase, toRename);
419 toRenameBase.delete();
420 }
421 } else {
422 toRename = toRenameBase;
423 isBase = true;
424 }
425 }
426
427 if (toRename.exists()) {
428
429
430
431
432 if (i == highIndex) {
433 LOGGER.debug(
434 "DefaultRolloverStrategy.purgeDescending deleting {} at high index {}: all slots full.",
435 toRename, i);
436 if (!toRename.delete()) {
437 return -1;
438 }
439
440 break;
441 }
442
443
444
445
446 buf.setLength(0);
447
448 manager.getPatternProcessor().formatFileName(subst, buf, i + 1);
449
450 final String highFilename = subst.replace(buf);
451 String renameTo = highFilename;
452
453 if (isBase) {
454 renameTo = highFilename.substring(0, highFilename.length() - suffixLength);
455 }
456
457 renames.add(new FileRenameAction(toRename, new File(renameTo), true));
458 lowFilename = highFilename;
459 } else {
460 break;
461 }
462 }
463
464
465
466
467 for (int i = renames.size() - 1; i >= 0; i--) {
468 final Action action = renames.get(i);
469 try {
470 LOGGER.debug("DefaultRolloverStrategy.purgeDescending executing {} of {}: {}",
471 i, renames.size(), action);
472 if (!action.execute()) {
473 return -1;
474 }
475 } catch (final Exception ex) {
476 LOGGER.warn("Exception during purge in RollingFileAppender", ex);
477 return -1;
478 }
479 }
480
481 return lowIndex;
482 }
483
484 private int suffixLength(final String lowFilename) {
485 for (FileExtensions extension : FileExtensions.values()) {
486 if (extension.isExtensionFor(lowFilename)) {
487 return extension.length();
488 }
489 }
490 return 0;
491 }
492
493
494
495
496
497
498
499
500 @Override
501 public RolloverDescription rollover(final RollingFileManager manager) throws SecurityException {
502 if (maxIndex < 0) {
503 return null;
504 }
505 final long startNanos = System.nanoTime();
506 final int fileIndex = purge(minIndex, maxIndex, manager);
507 if (fileIndex < 0) {
508 return null;
509 }
510 if (LOGGER.isTraceEnabled()) {
511 final double durationMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos);
512 LOGGER.trace("DefaultRolloverStrategy.purge() took {} milliseconds", durationMillis);
513 }
514 final StringBuilder buf = new StringBuilder(255);
515 manager.getPatternProcessor().formatFileName(subst, buf, fileIndex);
516 final String currentFileName = manager.getFileName();
517
518 String renameTo = buf.toString();
519 final String compressedName = renameTo;
520 Action compressAction = null;
521
522 for (FileExtensions ext : FileExtensions.values()) {
523 if (ext.isExtensionFor(renameTo)) {
524 renameTo = renameTo.substring(0, renameTo.length() - ext.length());
525 compressAction = ext.createCompressAction(renameTo, compressedName, true, compressionLevel);
526 break;
527 }
528 }
529
530 final FileRenameAction renameAction = new FileRenameAction(new File(currentFileName), new File(renameTo), false);
531
532 final Action asyncAction = merge(compressAction, customActions, stopCustomActionsOnError);
533 return new RolloverDescriptionImpl(currentFileName, false, renameAction, asyncAction);
534 }
535
536 private Action merge(final Action compressAction, final List<Action> custom, final boolean stopOnError) {
537 if (custom.isEmpty()) {
538 return compressAction;
539 }
540 if (compressAction == null) {
541 return new CompositeAction(custom, stopOnError);
542 }
543 final List<Action> all = new ArrayList<>();
544 all.add(compressAction);
545 all.addAll(custom);
546 return new CompositeAction(all, stopOnError);
547 }
548
549 @Override
550 public String toString() {
551 return "DefaultRolloverStrategy(min=" + minIndex + ", max=" + maxIndex + ')';
552 }
553
554 }