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