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.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.SortedMap;
29 import java.util.concurrent.TimeUnit;
30 import java.util.zip.Deflater;
31
32 import org.apache.logging.log4j.core.Core;
33 import org.apache.logging.log4j.core.appender.rolling.action.Action;
34 import org.apache.logging.log4j.core.appender.rolling.action.FileRenameAction;
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
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 @Plugin(name = "DefaultRolloverStrategy", category = Core.CATEGORY_NAME, printObject = true)
78 public class DefaultRolloverStrategy extends AbstractRolloverStrategy {
79
80 private static final int MIN_WINDOW_SIZE = 1;
81 private static final int DEFAULT_WINDOW_SIZE = 7;
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96 @PluginFactory
97 public static DefaultRolloverStrategy createStrategy(
98
99 @PluginAttribute("max") final String max,
100 @PluginAttribute("min") final String min,
101 @PluginAttribute("fileIndex") final String fileIndex,
102 @PluginAttribute("compressionLevel") final String compressionLevelStr,
103 @PluginElement("Actions") final Action[] customActions,
104 @PluginAttribute(value = "stopCustomActionsOnError", defaultBoolean = true)
105 final boolean stopCustomActionsOnError,
106 @PluginConfiguration final Configuration config) {
107
108 int minIndex;
109 int maxIndex;
110 boolean useMax;
111
112 if (fileIndex != null && fileIndex.equalsIgnoreCase("nomax")) {
113 minIndex = Integer.MIN_VALUE;
114 maxIndex = Integer.MAX_VALUE;
115 useMax = false;
116 } else {
117 useMax = fileIndex == null ? true : fileIndex.equalsIgnoreCase("max");
118 minIndex = MIN_WINDOW_SIZE;
119 if (min != null) {
120 minIndex = Integer.parseInt(min);
121 if (minIndex < 1) {
122 LOGGER.error("Minimum window size too small. Limited to " + MIN_WINDOW_SIZE);
123 minIndex = MIN_WINDOW_SIZE;
124 }
125 }
126 maxIndex = DEFAULT_WINDOW_SIZE;
127 if (max != null) {
128 maxIndex = Integer.parseInt(max);
129 if (maxIndex < minIndex) {
130 maxIndex = minIndex < DEFAULT_WINDOW_SIZE ? DEFAULT_WINDOW_SIZE : minIndex;
131 LOGGER.error("Maximum window size must be greater than the minimum windows size. Set to " + maxIndex);
132 }
133 }
134 }
135 final int compressionLevel = Integers.parseInt(compressionLevelStr, Deflater.DEFAULT_COMPRESSION);
136 return new DefaultRolloverStrategy(minIndex, maxIndex, useMax, compressionLevel, config.getStrSubstitutor(),
137 customActions, stopCustomActionsOnError);
138 }
139
140
141
142
143 private final int maxIndex;
144
145
146
147
148 private final int minIndex;
149 private final boolean useMax;
150 private final int compressionLevel;
151 private final List<Action> customActions;
152 private final boolean stopCustomActionsOnError;
153
154
155
156
157
158
159
160
161
162 protected DefaultRolloverStrategy(final int minIndex, final int maxIndex, final boolean useMax,
163 final int compressionLevel, final StrSubstitutor strSubstitutor, final Action[] customActions,
164 final boolean stopCustomActionsOnError) {
165 super(strSubstitutor);
166 this.minIndex = minIndex;
167 this.maxIndex = maxIndex;
168 this.useMax = useMax;
169 this.compressionLevel = compressionLevel;
170 this.stopCustomActionsOnError = stopCustomActionsOnError;
171 this.customActions = customActions == null ? Collections.<Action> emptyList() : Arrays.asList(customActions);
172 }
173
174 public int getCompressionLevel() {
175 return this.compressionLevel;
176 }
177
178 public List<Action> getCustomActions() {
179 return customActions;
180 }
181
182 public int getMaxIndex() {
183 return this.maxIndex;
184 }
185
186 public int getMinIndex() {
187 return this.minIndex;
188 }
189
190 public boolean isStopCustomActionsOnError() {
191 return stopCustomActionsOnError;
192 }
193
194 public boolean isUseMax() {
195 return useMax;
196 }
197
198 private int purge(final int lowIndex, final int highIndex, final RollingFileManager manager) {
199 return useMax ? purgeAscending(lowIndex, highIndex, manager) : purgeDescending(lowIndex, highIndex, manager);
200 }
201
202
203
204
205
206
207
208
209
210
211 private int purgeAscending(final int lowIndex, final int highIndex, final RollingFileManager manager) {
212 final SortedMap<Integer, Path> eligibleFiles = getEligibleFiles(manager);
213 final int maxFiles = highIndex - lowIndex + 1;
214
215 boolean renameFiles = false;
216 while (eligibleFiles.size() >= maxFiles) {
217 try {
218 LOGGER.debug("Eligible files: {}", eligibleFiles);
219 Integer key = eligibleFiles.firstKey();
220 LOGGER.debug("Deleting {}", eligibleFiles.get(key).toFile().getAbsolutePath());
221 Files.delete(eligibleFiles.get(key));
222 eligibleFiles.remove(key);
223 renameFiles = true;
224 } catch (IOException ioe) {
225 LOGGER.error("Unable to delete {}, {}", eligibleFiles.firstKey(), ioe.getMessage(), ioe);
226 break;
227 }
228 }
229 final StringBuilder buf = new StringBuilder();
230 if (renameFiles) {
231 for (Map.Entry<Integer, Path> entry : eligibleFiles.entrySet()) {
232 buf.setLength(0);
233
234 manager.getPatternProcessor().formatFileName(strSubstitutor, buf, entry.getKey() - 1);
235 String currentName = entry.getValue().toFile().getName();
236 String renameTo = buf.toString();
237 int suffixLength = suffixLength(renameTo);
238 if (suffixLength > 0 && suffixLength(currentName) == 0) {
239 renameTo = renameTo.substring(0, renameTo.length() - suffixLength);
240 }
241 Action action = new FileRenameAction(entry.getValue().toFile(), new File(renameTo), true);
242 try {
243 LOGGER.debug("DefaultRolloverStrategy.purgeAscending executing {}", action);
244 if (!action.execute()) {
245 return -1;
246 }
247 } catch (final Exception ex) {
248 LOGGER.warn("Exception during purge in RollingFileAppender", ex);
249 return -1;
250 }
251 }
252 }
253
254 return eligibleFiles.size() > 0 ?
255 (eligibleFiles.lastKey() < highIndex ? eligibleFiles.lastKey() + 1 : highIndex) : lowIndex;
256 }
257
258
259
260
261
262
263
264
265
266
267 private int purgeDescending(final int lowIndex, final int highIndex, final RollingFileManager manager) {
268
269 final SortedMap<Integer, Path> eligibleFiles = getEligibleFiles(manager, false);
270 final int maxFiles = highIndex - lowIndex + 1;
271
272 while (eligibleFiles.size() >= maxFiles) {
273 try {
274 Integer key = eligibleFiles.firstKey();
275 Files.delete(eligibleFiles.get(key));
276 eligibleFiles.remove(key);
277 } catch (IOException ioe) {
278 LOGGER.error("Unable to delete {}, {}", eligibleFiles.firstKey(), ioe.getMessage(), ioe);
279 break;
280 }
281 }
282 final StringBuilder buf = new StringBuilder();
283 for (Map.Entry<Integer, Path> entry : eligibleFiles.entrySet()) {
284 buf.setLength(0);
285
286 manager.getPatternProcessor().formatFileName(strSubstitutor, buf, entry.getKey() + 1);
287 String currentName = entry.getValue().toFile().getName();
288 String renameTo = buf.toString();
289 int suffixLength = suffixLength(renameTo);
290 if (suffixLength > 0 && suffixLength(currentName) == 0) {
291 renameTo = renameTo.substring(0, renameTo.length() - suffixLength);
292 }
293 Action action = new FileRenameAction(entry.getValue().toFile(), new File(renameTo), true);
294 try {
295 LOGGER.debug("DefaultRolloverStrategy.purgeDescending executing {}", action);
296 if (!action.execute()) {
297 return -1;
298 }
299 } catch (final Exception ex) {
300 LOGGER.warn("Exception during purge in RollingFileAppender", ex);
301 return -1;
302 }
303 }
304
305 return lowIndex;
306 }
307
308
309
310
311
312
313
314
315 @Override
316 public RolloverDescription rollover(final RollingFileManager manager) throws SecurityException {
317 int fileIndex;
318 if (minIndex == Integer.MIN_VALUE) {
319 final SortedMap<Integer, Path> eligibleFiles = getEligibleFiles(manager);
320 fileIndex = eligibleFiles.size() > 0 ? eligibleFiles.lastKey() + 1 : 1;
321 } else {
322 if (maxIndex < 0) {
323 return null;
324 }
325 final long startNanos = System.nanoTime();
326 fileIndex = purge(minIndex, maxIndex, manager);
327 if (fileIndex < 0) {
328 return null;
329 }
330 if (LOGGER.isTraceEnabled()) {
331 final double durationMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos);
332 LOGGER.trace("DefaultRolloverStrategy.purge() took {} milliseconds", durationMillis);
333 }
334 }
335 final StringBuilder buf = new StringBuilder(255);
336 manager.getPatternProcessor().formatFileName(strSubstitutor, buf, fileIndex);
337 final String currentFileName = manager.getFileName();
338
339 String renameTo = buf.toString();
340 final String compressedName = renameTo;
341 Action compressAction = null;
342
343 FileExtension fileExtension = manager.getFileExtension();
344 if (fileExtension != null) {
345 renameTo = renameTo.substring(0, renameTo.length() - fileExtension.length());
346 compressAction = fileExtension.createCompressAction(renameTo, compressedName,
347 true, compressionLevel);
348 }
349
350 if (currentFileName.equals(renameTo)) {
351 LOGGER.warn("Attempt to rename file {} to itself will be ignored", currentFileName);
352 return new RolloverDescriptionImpl(currentFileName, false, null, null);
353 }
354
355 final FileRenameAction renameAction = new FileRenameAction(new File(currentFileName), new File(renameTo),
356 manager.isRenameEmptyFiles());
357
358 final Action asyncAction = merge(compressAction, customActions, stopCustomActionsOnError);
359 return new RolloverDescriptionImpl(currentFileName, false, renameAction, asyncAction);
360 }
361
362 @Override
363 public String toString() {
364 return "DefaultRolloverStrategy(min=" + minIndex + ", max=" + maxIndex + ", useMax=" + useMax + ")";
365 }
366
367 }