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.Arrays;
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.SortedMap;
27 import java.util.concurrent.TimeUnit;
28 import java.util.zip.Deflater;
29
30 import org.apache.logging.log4j.core.Core;
31 import org.apache.logging.log4j.core.appender.rolling.action.Action;
32 import org.apache.logging.log4j.core.appender.rolling.action.CompositeAction;
33 import org.apache.logging.log4j.core.appender.rolling.action.FileRenameAction;
34 import org.apache.logging.log4j.core.appender.rolling.action.PathCondition;
35 import org.apache.logging.log4j.core.appender.rolling.action.PosixViewAttributeAction;
36 import org.apache.logging.log4j.core.config.Configuration;
37 import org.apache.logging.log4j.core.config.plugins.Plugin;
38 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
39 import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
40 import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
41 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
42 import org.apache.logging.log4j.core.config.plugins.PluginElement;
43 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
44 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
45 import org.apache.logging.log4j.core.util.Integers;
46
47
48
49
50
51
52
53
54
55
56
57
58
59 @Plugin(name = "DirectWriteRolloverStrategy", category = Core.CATEGORY_NAME, printObject = true)
60 public class DirectWriteRolloverStrategy extends AbstractRolloverStrategy implements DirectFileRolloverStrategy {
61
62 private static final int DEFAULT_MAX_FILES = 7;
63
64
65
66
67 public static class Builder implements org.apache.logging.log4j.core.util.Builder<DirectWriteRolloverStrategy> {
68 @PluginBuilderAttribute("maxFiles")
69 private String maxFiles;
70
71 @PluginBuilderAttribute("compressionLevel")
72 private String compressionLevelStr;
73
74 @PluginElement("Actions")
75 private Action[] customActions;
76
77 @PluginBuilderAttribute(value = "stopCustomActionsOnError")
78 private boolean stopCustomActionsOnError = true;
79
80 @PluginBuilderAttribute(value = "tempCompressedFilePattern")
81 private String tempCompressedFilePattern;
82
83 @PluginConfiguration
84 private Configuration config;
85
86 @Override
87 public DirectWriteRolloverStrategy build() {
88 int maxIndex = Integer.MAX_VALUE;
89 if (maxFiles != null) {
90 maxIndex = Integer.parseInt(maxFiles);
91 if (maxIndex < 0) {
92 maxIndex = Integer.MAX_VALUE;
93 } else if (maxIndex < 2) {
94 LOGGER.error("Maximum files too small. Limited to " + DEFAULT_MAX_FILES);
95 maxIndex = DEFAULT_MAX_FILES;
96 }
97 }
98 final int compressionLevel = Integers.parseInt(compressionLevelStr, Deflater.DEFAULT_COMPRESSION);
99 return new DirectWriteRolloverStrategy(maxIndex, compressionLevel, config.getStrSubstitutor(),
100 customActions, stopCustomActionsOnError, tempCompressedFilePattern);
101 }
102
103 public String getMaxFiles() {
104 return maxFiles;
105 }
106
107
108
109
110
111
112
113 public Builder withMaxFiles(final String maxFiles) {
114 this.maxFiles = maxFiles;
115 return this;
116 }
117
118 public String getCompressionLevelStr() {
119 return compressionLevelStr;
120 }
121
122
123
124
125
126
127
128 public Builder withCompressionLevelStr(final String compressionLevelStr) {
129 this.compressionLevelStr = compressionLevelStr;
130 return this;
131 }
132
133 public Action[] getCustomActions() {
134 return customActions;
135 }
136
137
138
139
140
141
142
143 public Builder withCustomActions(final Action[] customActions) {
144 this.customActions = customActions;
145 return this;
146 }
147
148 public boolean isStopCustomActionsOnError() {
149 return stopCustomActionsOnError;
150 }
151
152
153
154
155
156
157
158 public Builder withStopCustomActionsOnError(final boolean stopCustomActionsOnError) {
159 this.stopCustomActionsOnError = stopCustomActionsOnError;
160 return this;
161 }
162
163 public String getTempCompressedFilePattern() {
164 return tempCompressedFilePattern;
165 }
166
167
168
169
170
171
172
173 public Builder withTempCompressedFilePattern(final String tempCompressedFilePattern) {
174 this.tempCompressedFilePattern = tempCompressedFilePattern;
175 return this;
176 }
177
178 public Configuration getConfig() {
179 return config;
180 }
181
182
183
184
185
186
187
188 public Builder withConfig(final Configuration config) {
189 this.config = config;
190 return this;
191 }
192 }
193
194 @PluginBuilderFactory
195 public static Builder newBuilder() {
196 return new Builder();
197 }
198
199
200
201
202
203
204
205
206
207
208
209
210 @Deprecated
211 @PluginFactory
212 public static DirectWriteRolloverStrategy createStrategy(
213
214 @PluginAttribute("maxFiles") final String maxFiles,
215 @PluginAttribute("compressionLevel") final String compressionLevelStr,
216 @PluginElement("Actions") final Action[] customActions,
217 @PluginAttribute(value = "stopCustomActionsOnError", defaultBoolean = true)
218 final boolean stopCustomActionsOnError,
219 @PluginConfiguration final Configuration config) {
220 return newBuilder().withMaxFiles(maxFiles)
221 .withCompressionLevelStr(compressionLevelStr)
222 .withCustomActions(customActions)
223 .withStopCustomActionsOnError(stopCustomActionsOnError)
224 .withConfig(config)
225 .build();
226
227 }
228
229
230
231
232 private final int maxFiles;
233 private final int compressionLevel;
234 private final List<Action> customActions;
235 private final boolean stopCustomActionsOnError;
236 private volatile String currentFileName;
237 private int nextIndex = -1;
238 private final PatternProcessor tempCompressedFilePattern;
239
240
241
242
243
244
245
246
247
248 @Deprecated
249 protected DirectWriteRolloverStrategy(final int maxFiles, final int compressionLevel,
250 final StrSubstitutor strSubstitutor, final Action[] customActions,
251 final boolean stopCustomActionsOnError) {
252 this(maxFiles, compressionLevel, strSubstitutor, customActions, stopCustomActionsOnError, null);
253 }
254
255
256
257
258
259
260
261
262
263
264 protected DirectWriteRolloverStrategy(final int maxFiles, final int compressionLevel,
265 final StrSubstitutor strSubstitutor, final Action[] customActions,
266 final boolean stopCustomActionsOnError, final String tempCompressedFilePatternString) {
267 super(strSubstitutor);
268 this.maxFiles = maxFiles;
269 this.compressionLevel = compressionLevel;
270 this.stopCustomActionsOnError = stopCustomActionsOnError;
271 this.customActions = customActions == null ? Collections.<Action> emptyList() : Arrays.asList(customActions);
272 this.tempCompressedFilePattern =
273 tempCompressedFilePatternString != null ? new PatternProcessor(tempCompressedFilePatternString) : null;
274 }
275
276 public int getCompressionLevel() {
277 return this.compressionLevel;
278 }
279
280 public List<Action> getCustomActions() {
281 return customActions;
282 }
283
284 public int getMaxFiles() {
285 return this.maxFiles;
286 }
287
288 public boolean isStopCustomActionsOnError() {
289 return stopCustomActionsOnError;
290 }
291
292 public PatternProcessor getTempCompressedFilePattern() {
293 return tempCompressedFilePattern;
294 }
295
296 private int purge(final RollingFileManager manager) {
297 final SortedMap<Integer, Path> eligibleFiles = getEligibleFiles(manager);
298 LOGGER.debug("Found {} eligible files, max is {}", eligibleFiles.size(), maxFiles);
299 while (eligibleFiles.size() >= maxFiles) {
300 try {
301 final Integer key = eligibleFiles.firstKey();
302 Files.delete(eligibleFiles.get(key));
303 eligibleFiles.remove(key);
304 } catch (final IOException ioe) {
305 LOGGER.error("Unable to delete {}", eligibleFiles.firstKey(), ioe);
306 break;
307 }
308 }
309 return eligibleFiles.size() > 0 ? eligibleFiles.lastKey() : 1;
310 }
311
312 @Override
313 public String getCurrentFileName(final RollingFileManager manager) {
314 if (currentFileName == null) {
315 final SortedMap<Integer, Path> eligibleFiles = getEligibleFiles(manager);
316 final int fileIndex = eligibleFiles.size() > 0 ? (nextIndex > 0 ? nextIndex : eligibleFiles.size()) : 1;
317 final StringBuilder buf = new StringBuilder(255);
318 manager.getPatternProcessor().formatFileName(strSubstitutor, buf, true, fileIndex);
319 final int suffixLength = suffixLength(buf.toString());
320 final String name = suffixLength > 0 ? buf.substring(0, buf.length() - suffixLength) : buf.toString();
321 currentFileName = name;
322 }
323 return currentFileName;
324 }
325
326
327
328
329
330
331
332
333 @Override
334 public RolloverDescription rollover(final RollingFileManager manager) throws SecurityException {
335 LOGGER.debug("Rolling " + currentFileName);
336 if (maxFiles < 0) {
337 return null;
338 }
339 final long startNanos = System.nanoTime();
340 final int fileIndex = purge(manager);
341 if (LOGGER.isTraceEnabled()) {
342 final double durationMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos);
343 LOGGER.trace("DirectWriteRolloverStrategy.purge() took {} milliseconds", durationMillis);
344 }
345 Action compressAction = null;
346 final String sourceName = getCurrentFileName(manager);
347 String compressedName = sourceName;
348 currentFileName = null;
349 nextIndex = fileIndex + 1;
350 final FileExtension fileExtension = manager.getFileExtension();
351 if (fileExtension != null) {
352 compressedName += fileExtension.getExtension();
353 if (tempCompressedFilePattern != null) {
354 final StringBuilder buf = new StringBuilder();
355 tempCompressedFilePattern.formatFileName(strSubstitutor, buf, fileIndex);
356 final String tmpCompressedName = buf.toString();
357 final File tmpCompressedNameFile = new File(tmpCompressedName);
358 final File parentFile = tmpCompressedNameFile.getParentFile();
359 if (parentFile != null) {
360 parentFile.mkdirs();
361 }
362 compressAction = new CompositeAction(
363 Arrays.asList(fileExtension.createCompressAction(sourceName, tmpCompressedName,
364 true, compressionLevel),
365 new FileRenameAction(tmpCompressedNameFile,
366 new File(compressedName), true)),
367 true);
368 } else {
369 compressAction = fileExtension.createCompressAction(sourceName, compressedName,
370 true, compressionLevel);
371 }
372 }
373
374 if (compressAction != null && manager.isAttributeViewEnabled()) {
375
376
377 final Action posixAttributeViewAction = PosixViewAttributeAction.newBuilder()
378 .withBasePath(compressedName)
379 .withFollowLinks(false)
380 .withMaxDepth(1)
381 .withPathConditions(new PathCondition[0])
382 .withSubst(getStrSubstitutor())
383 .withFilePermissions(manager.getFilePermissions())
384 .withFileOwner(manager.getFileOwner())
385 .withFileGroup(manager.getFileGroup())
386 .build();
387
388 compressAction = new CompositeAction(Arrays.asList(compressAction, posixAttributeViewAction), false);
389 }
390
391 final Action asyncAction = merge(compressAction, customActions, stopCustomActionsOnError);
392 return new RolloverDescriptionImpl(sourceName, false, null, asyncAction);
393 }
394
395 @Override
396 public String toString() {
397 return "DirectWriteRolloverStrategy(maxFiles=" + maxFiles + ')';
398 }
399
400 }