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