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