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.FileOutputStream;
21 import java.io.IOException;
22 import java.io.OutputStream;
23 import java.io.Serializable;
24 import java.nio.ByteBuffer;
25 import java.nio.file.Files;
26 import java.nio.file.Path;
27 import java.nio.file.attribute.BasicFileAttributes;
28 import java.nio.file.attribute.FileTime;
29 import java.util.Collection;
30 import java.util.Date;
31 import java.util.concurrent.ArrayBlockingQueue;
32 import java.util.concurrent.ExecutorService;
33 import java.util.concurrent.Semaphore;
34 import java.util.concurrent.ThreadPoolExecutor;
35 import java.util.concurrent.TimeUnit;
36 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
37
38 import org.apache.logging.log4j.core.Layout;
39 import org.apache.logging.log4j.core.LifeCycle;
40 import org.apache.logging.log4j.core.LifeCycle2;
41 import org.apache.logging.log4j.core.LogEvent;
42 import org.apache.logging.log4j.core.LoggerContext;
43 import org.apache.logging.log4j.core.appender.ConfigurationFactoryData;
44 import org.apache.logging.log4j.core.appender.FileManager;
45 import org.apache.logging.log4j.core.appender.ManagerFactory;
46 import org.apache.logging.log4j.core.appender.rolling.action.AbstractAction;
47 import org.apache.logging.log4j.core.appender.rolling.action.Action;
48 import org.apache.logging.log4j.core.config.Configuration;
49 import org.apache.logging.log4j.core.util.Constants;
50 import org.apache.logging.log4j.core.util.FileUtils;
51 import org.apache.logging.log4j.core.util.Log4jThreadFactory;
52
53
54
55
56 public class RollingFileManager extends FileManager {
57
58 private static RollingFileManagerFactory factory = new RollingFileManagerFactory();
59 private static final int MAX_TRIES = 3;
60 private static final int MIN_DURATION = 100;
61 private static final FileTime EPOCH = FileTime.fromMillis(0);
62
63 protected long size;
64 private long initialTime;
65 private volatile PatternProcessor patternProcessor;
66 private final Semaphore semaphore = new Semaphore(1);
67 private final Log4jThreadFactory threadFactory = Log4jThreadFactory.createThreadFactory("RollingFileManager");
68 private volatile TriggeringPolicy triggeringPolicy;
69 private volatile RolloverStrategy rolloverStrategy;
70 private volatile boolean renameEmptyFiles = false;
71 private volatile boolean initialized = false;
72 private volatile String fileName;
73 private final FileExtension fileExtension;
74
75
76
77 private final ExecutorService asyncExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0, TimeUnit.MILLISECONDS,
78 new EmptyQueue(), threadFactory);
79
80 private static final AtomicReferenceFieldUpdater<RollingFileManager, TriggeringPolicy> triggeringPolicyUpdater =
81 AtomicReferenceFieldUpdater.newUpdater(RollingFileManager.class, TriggeringPolicy.class, "triggeringPolicy");
82
83 private static final AtomicReferenceFieldUpdater<RollingFileManager, RolloverStrategy> rolloverStrategyUpdater =
84 AtomicReferenceFieldUpdater.newUpdater(RollingFileManager.class, RolloverStrategy.class, "rolloverStrategy");
85
86 private static final AtomicReferenceFieldUpdater<RollingFileManager, PatternProcessor> patternProcessorUpdater =
87 AtomicReferenceFieldUpdater.newUpdater(RollingFileManager.class, PatternProcessor.class, "patternProcessor");
88
89 @Deprecated
90 protected RollingFileManager(final String fileName, final String pattern, final OutputStream os,
91 final boolean append, final long size, final long initialTime, final TriggeringPolicy triggeringPolicy,
92 final RolloverStrategy rolloverStrategy, final String advertiseURI,
93 final Layout<? extends Serializable> layout, final int bufferSize, final boolean writeHeader) {
94 this(fileName, pattern, os, append, size, initialTime, triggeringPolicy, rolloverStrategy, advertiseURI, layout,
95 writeHeader, ByteBuffer.wrap(new byte[Constants.ENCODER_BYTE_BUFFER_SIZE]));
96 }
97
98 @Deprecated
99 protected RollingFileManager(final String fileName, final String pattern, final OutputStream os,
100 final boolean append, final long size, final long initialTime, final TriggeringPolicy triggeringPolicy,
101 final RolloverStrategy rolloverStrategy, final String advertiseURI,
102 final Layout<? extends Serializable> layout, final boolean writeHeader, final ByteBuffer buffer) {
103 super(fileName != null ? fileName : pattern, os, append, false, advertiseURI, layout, writeHeader,
104 buffer);
105 this.size = size;
106 this.initialTime = initialTime;
107 this.triggeringPolicy = triggeringPolicy;
108 this.rolloverStrategy = rolloverStrategy;
109 this.patternProcessor = new PatternProcessor(pattern);
110 this.patternProcessor.setPrevFileTime(initialTime);
111 this.fileName = fileName;
112 this.fileExtension = FileExtension.lookupForFile(pattern);
113 }
114
115 @Deprecated
116 protected RollingFileManager(final LoggerContext loggerContext, final String fileName, final String pattern, final OutputStream os,
117 final boolean append, final boolean createOnDemand, final long size, final long initialTime,
118 final TriggeringPolicy triggeringPolicy, final RolloverStrategy rolloverStrategy,
119 final String advertiseURI, final Layout<? extends Serializable> layout, final boolean writeHeader, final ByteBuffer buffer) {
120 super(loggerContext, fileName != null ? fileName : pattern, os, append, false, createOnDemand,
121 advertiseURI, layout, writeHeader, buffer);
122 this.size = size;
123 this.initialTime = initialTime;
124 this.triggeringPolicy = triggeringPolicy;
125 this.rolloverStrategy = rolloverStrategy;
126 this.patternProcessor = new PatternProcessor(pattern);
127 this.patternProcessor.setPrevFileTime(initialTime);
128 this.fileName = fileName;
129 this.fileExtension = FileExtension.lookupForFile(pattern);
130 }
131
132
133
134
135 protected RollingFileManager(final LoggerContext loggerContext, final String fileName, final String pattern, final OutputStream os,
136 final boolean append, final boolean createOnDemand, final long size, final long initialTime,
137 final TriggeringPolicy triggeringPolicy, final RolloverStrategy rolloverStrategy,
138 final String advertiseURI, final Layout<? extends Serializable> layout,
139 final String filePermissions, final String fileOwner, final String fileGroup,
140 final boolean writeHeader, final ByteBuffer buffer) {
141 super(loggerContext, fileName != null ? fileName : pattern, os, append, false, createOnDemand,
142 advertiseURI, layout, filePermissions, fileOwner, fileGroup, writeHeader, buffer);
143 this.size = size;
144 this.initialTime = initialTime;
145 this.triggeringPolicy = triggeringPolicy;
146 this.rolloverStrategy = rolloverStrategy;
147 this.patternProcessor = new PatternProcessor(pattern);
148 this.patternProcessor.setPrevFileTime(initialTime);
149 this.fileName = fileName;
150 this.fileExtension = FileExtension.lookupForFile(pattern);
151 }
152
153 public void initialize() {
154
155 if (!initialized) {
156 LOGGER.debug("Initializing triggering policy {}", triggeringPolicy);
157 initialized = true;
158 triggeringPolicy.initialize(this);
159 if (triggeringPolicy instanceof LifeCycle) {
160 ((LifeCycle) triggeringPolicy).start();
161 }
162 if (rolloverStrategy instanceof DirectFileRolloverStrategy) {
163
164 File file = new File(getFileName());
165 if (file.exists()) {
166 size = file.length();
167 } else {
168 ((DirectFileRolloverStrategy) rolloverStrategy).clearCurrentFileName();
169 }
170 }
171 }
172 }
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193 public static RollingFileManager getFileManager(final String fileName, final String pattern, final boolean append,
194 final boolean bufferedIO, final TriggeringPolicy policy, final RolloverStrategy strategy,
195 final String advertiseURI, final Layout<? extends Serializable> layout, final int bufferSize,
196 final boolean immediateFlush, final boolean createOnDemand,
197 final String filePermissions, final String fileOwner, final String fileGroup,
198 final Configuration configuration) {
199
200 if (strategy instanceof DirectWriteRolloverStrategy && fileName != null) {
201 LOGGER.error("The fileName attribute must not be specified with the DirectWriteRolloverStrategy");
202 return null;
203 }
204 final String name = fileName == null ? pattern : fileName;
205 return narrow(RollingFileManager.class, getManager(name, new FactoryData(fileName, pattern, append,
206 bufferedIO, policy, strategy, advertiseURI, layout, bufferSize, immediateFlush, createOnDemand,
207 filePermissions, fileOwner, fileGroup, configuration), factory));
208 }
209
210
211
212
213
214 @Override
215 public String getFileName() {
216 if (rolloverStrategy instanceof DirectFileRolloverStrategy) {
217 fileName = ((DirectFileRolloverStrategy) rolloverStrategy).getCurrentFileName(this);
218 }
219 return fileName;
220 }
221
222 public FileExtension getFileExtension() {
223 return fileExtension;
224 }
225
226
227 @Override
228 protected synchronized void write(final byte[] bytes, final int offset, final int length,
229 final boolean immediateFlush) {
230 super.write(bytes, offset, length, immediateFlush);
231 }
232
233 @Override
234 protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) {
235 size += length;
236 super.writeToDestination(bytes, offset, length);
237 }
238
239 public boolean isRenameEmptyFiles() {
240 return renameEmptyFiles;
241 }
242
243 public void setRenameEmptyFiles(final boolean renameEmptyFiles) {
244 this.renameEmptyFiles = renameEmptyFiles;
245 }
246
247
248
249
250
251 public long getFileSize() {
252 return size + byteBuffer.position();
253 }
254
255
256
257
258
259 public long getFileTime() {
260 return initialTime;
261 }
262
263
264
265
266
267 public synchronized void checkRollover(final LogEvent event) {
268 if (triggeringPolicy.isTriggeringEvent(event)) {
269 rollover();
270 }
271 }
272
273 @Override
274 public boolean releaseSub(final long timeout, final TimeUnit timeUnit) {
275 LOGGER.debug("Shutting down RollingFileManager {}", getName());
276 boolean stopped = true;
277 if (triggeringPolicy instanceof LifeCycle2) {
278 stopped &= ((LifeCycle2) triggeringPolicy).stop(timeout, timeUnit);
279 } else if (triggeringPolicy instanceof LifeCycle) {
280 ((LifeCycle) triggeringPolicy).stop();
281 stopped &= true;
282 }
283 final boolean status = super.releaseSub(timeout, timeUnit) && stopped;
284 asyncExecutor.shutdown();
285 try {
286
287 final long millis = timeUnit.toMillis(timeout);
288 final long waitInterval = MIN_DURATION < millis ? millis : MIN_DURATION;
289
290 for (int count = 1; count <= MAX_TRIES && !asyncExecutor.isTerminated(); ++count) {
291 asyncExecutor.awaitTermination(waitInterval * count, TimeUnit.MILLISECONDS);
292 }
293 if (asyncExecutor.isTerminated()) {
294 LOGGER.debug("All asynchronous threads have terminated");
295 } else {
296 asyncExecutor.shutdownNow();
297 try {
298 asyncExecutor.awaitTermination(timeout, timeUnit);
299 if (asyncExecutor.isTerminated()) {
300 LOGGER.debug("All asynchronous threads have terminated");
301 } else {
302 LOGGER.debug("RollingFileManager shutting down but some asynchronous services may not have completed");
303 }
304 } catch (final InterruptedException inner) {
305 LOGGER.warn("RollingFileManager stopped but some asynchronous services may not have completed.");
306 }
307 }
308 } catch (final InterruptedException ie) {
309 asyncExecutor.shutdownNow();
310 try {
311 asyncExecutor.awaitTermination(timeout, timeUnit);
312 if (asyncExecutor.isTerminated()) {
313 LOGGER.debug("All asynchronous threads have terminated");
314 }
315 } catch (final InterruptedException inner) {
316 LOGGER.warn("RollingFileManager stopped but some asynchronous services may not have completed.");
317 }
318
319 Thread.currentThread().interrupt();
320 }
321 LOGGER.debug("RollingFileManager shutdown completed with status {}", status);
322 return status;
323 }
324
325 public synchronized void rollover(Date prevFileTime, Date prevRollTime) {
326 getPatternProcessor().setPrevFileTime(prevFileTime.getTime());
327 getPatternProcessor().setCurrentFileTime(prevRollTime.getTime());
328 rollover();
329 }
330
331 public synchronized void rollover() {
332 if (!hasOutputStream()) {
333 return;
334 }
335 if (rollover(rolloverStrategy)) {
336 try {
337 size = 0;
338 initialTime = System.currentTimeMillis();
339 createFileAfterRollover();
340 } catch (final IOException e) {
341 logError("Failed to create file after rollover", e);
342 }
343 }
344 }
345
346 protected void createFileAfterRollover() throws IOException {
347 setOutputStream(createOutputStream());
348 }
349
350
351
352
353
354 public PatternProcessor getPatternProcessor() {
355 return patternProcessor;
356 }
357
358 public void setTriggeringPolicy(final TriggeringPolicy triggeringPolicy) {
359 triggeringPolicy.initialize(this);
360 final TriggeringPolicy policy = this.triggeringPolicy;
361 int count = 0;
362 boolean policyUpdated = false;
363 do {
364 ++count;
365 } while (!(policyUpdated = triggeringPolicyUpdater.compareAndSet(this, this.triggeringPolicy, triggeringPolicy))
366 && count < MAX_TRIES);
367 if (policyUpdated) {
368 if (triggeringPolicy instanceof LifeCycle) {
369 ((LifeCycle) triggeringPolicy).start();
370 }
371 if (policy instanceof LifeCycle) {
372 ((LifeCycle) policy).stop();
373 }
374 } else {
375 if (triggeringPolicy instanceof LifeCycle) {
376 ((LifeCycle) triggeringPolicy).stop();
377 }
378 }
379 }
380
381 public void setRolloverStrategy(final RolloverStrategy rolloverStrategy) {
382 rolloverStrategyUpdater.compareAndSet(this, this.rolloverStrategy, rolloverStrategy);
383 }
384
385 public void setPatternProcessor(final PatternProcessor patternProcessor) {
386 patternProcessorUpdater.compareAndSet(this, this.patternProcessor, patternProcessor);
387 }
388
389
390
391
392
393
394 @SuppressWarnings("unchecked")
395 public <T extends TriggeringPolicy> T getTriggeringPolicy() {
396
397 return (T) this.triggeringPolicy;
398 }
399
400
401
402
403
404 public RolloverStrategy getRolloverStrategy() {
405 return this.rolloverStrategy;
406 }
407
408 private boolean rollover(final RolloverStrategy strategy) {
409
410 boolean releaseRequired = false;
411 try {
412
413 semaphore.acquire();
414 releaseRequired = true;
415 } catch (final InterruptedException e) {
416 logError("Thread interrupted while attempting to check rollover", e);
417 return false;
418 }
419
420 boolean success = true;
421
422 try {
423 final RolloverDescription descriptor = strategy.rollover(this);
424 if (descriptor != null) {
425 writeFooter();
426 closeOutputStream();
427 if (descriptor.getSynchronous() != null) {
428 LOGGER.debug("RollingFileManager executing synchronous {}", descriptor.getSynchronous());
429 try {
430 success = descriptor.getSynchronous().execute();
431 } catch (final Exception ex) {
432 success = false;
433 logError("Caught error in synchronous task", ex);
434 }
435 }
436
437 if (success && descriptor.getAsynchronous() != null) {
438 LOGGER.debug("RollingFileManager executing async {}", descriptor.getAsynchronous());
439 asyncExecutor.execute(new AsyncAction(descriptor.getAsynchronous(), this));
440 releaseRequired = false;
441 }
442 return true;
443 }
444 return false;
445 } finally {
446 if (releaseRequired) {
447 semaphore.release();
448 }
449 }
450
451 }
452
453
454
455
456 private static class AsyncAction extends AbstractAction {
457
458 private final Action action;
459 private final RollingFileManager manager;
460
461
462
463
464
465
466 public AsyncAction(final Action act, final RollingFileManager manager) {
467 this.action = act;
468 this.manager = manager;
469 }
470
471
472
473
474
475
476
477
478
479 @Override
480 public boolean execute() throws IOException {
481 try {
482 return action.execute();
483 } finally {
484 manager.semaphore.release();
485 }
486 }
487
488
489
490
491 @Override
492 public void close() {
493 action.close();
494 }
495
496
497
498
499
500
501 @Override
502 public boolean isComplete() {
503 return action.isComplete();
504 }
505
506 @Override
507 public String toString() {
508 final StringBuilder builder = new StringBuilder();
509 builder.append(super.toString());
510 builder.append("[action=");
511 builder.append(action);
512 builder.append(", manager=");
513 builder.append(manager);
514 builder.append(", isComplete()=");
515 builder.append(isComplete());
516 builder.append(", isInterrupted()=");
517 builder.append(isInterrupted());
518 builder.append("]");
519 return builder.toString();
520 }
521 }
522
523
524
525
526 private static class FactoryData extends ConfigurationFactoryData {
527 private final String fileName;
528 private final String pattern;
529 private final boolean append;
530 private final boolean bufferedIO;
531 private final int bufferSize;
532 private final boolean immediateFlush;
533 private final boolean createOnDemand;
534 private final TriggeringPolicy policy;
535 private final RolloverStrategy strategy;
536 private final String advertiseURI;
537 private final Layout<? extends Serializable> layout;
538 private final String filePermissions;
539 private final String fileOwner;
540 private final String fileGroup;
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557 public FactoryData(final String fileName, final String pattern, final boolean append, final boolean bufferedIO,
558 final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
559 final Layout<? extends Serializable> layout, final int bufferSize, final boolean immediateFlush,
560 final boolean createOnDemand, final String filePermissions, final String fileOwner, final String fileGroup,
561 final Configuration configuration) {
562 super(configuration);
563 this.fileName = fileName;
564 this.pattern = pattern;
565 this.append = append;
566 this.bufferedIO = bufferedIO;
567 this.bufferSize = bufferSize;
568 this.policy = policy;
569 this.strategy = strategy;
570 this.advertiseURI = advertiseURI;
571 this.layout = layout;
572 this.immediateFlush = immediateFlush;
573 this.createOnDemand = createOnDemand;
574 this.filePermissions = filePermissions;
575 this.fileOwner = fileOwner;
576 this.fileGroup = fileGroup;
577 }
578
579 public TriggeringPolicy getTriggeringPolicy() {
580 return this.policy;
581 }
582
583 public RolloverStrategy getRolloverStrategy() {
584 return this.strategy;
585 }
586
587 public String getPattern() {
588 return pattern;
589 }
590
591 @Override
592 public String toString() {
593 final StringBuilder builder = new StringBuilder();
594 builder.append(super.toString());
595 builder.append("[pattern=");
596 builder.append(pattern);
597 builder.append(", append=");
598 builder.append(append);
599 builder.append(", bufferedIO=");
600 builder.append(bufferedIO);
601 builder.append(", bufferSize=");
602 builder.append(bufferSize);
603 builder.append(", policy=");
604 builder.append(policy);
605 builder.append(", strategy=");
606 builder.append(strategy);
607 builder.append(", advertiseURI=");
608 builder.append(advertiseURI);
609 builder.append(", layout=");
610 builder.append(layout);
611 builder.append(", filePermissions=");
612 builder.append(filePermissions);
613 builder.append(", fileOwner=");
614 builder.append(fileOwner);
615 builder.append("]");
616 return builder.toString();
617 }
618 }
619
620 @Override
621 public void updateData(final Object data) {
622 final FactoryData factoryData = (FactoryData) data;
623 setRolloverStrategy(factoryData.getRolloverStrategy());
624 setTriggeringPolicy(factoryData.getTriggeringPolicy());
625 setPatternProcessor(new PatternProcessor(factoryData.getPattern(), getPatternProcessor()));
626 }
627
628
629
630
631 private static class RollingFileManagerFactory implements ManagerFactory<RollingFileManager, FactoryData> {
632
633
634
635
636
637
638
639 @Override
640 public RollingFileManager createManager(final String name, final FactoryData data) {
641 long size = 0;
642 boolean writeHeader = !data.append;
643 File file = null;
644 if (data.fileName != null) {
645 file = new File(data.fileName);
646
647 writeHeader = !data.append || !file.exists();
648
649 try {
650 FileUtils.makeParentDirs(file);
651 final boolean created = data.createOnDemand ? false : file.createNewFile();
652 LOGGER.trace("New file '{}' created = {}", name, created);
653 } catch (final IOException ioe) {
654 LOGGER.error("Unable to create file " + name, ioe);
655 return null;
656 }
657 size = data.append ? file.length() : 0;
658 }
659
660 try {
661 final int actualSize = data.bufferedIO ? data.bufferSize : Constants.ENCODER_BYTE_BUFFER_SIZE;
662 final ByteBuffer buffer = ByteBuffer.wrap(new byte[actualSize]);
663 final OutputStream os = data.createOnDemand || data.fileName == null ? null :
664 new FileOutputStream(data.fileName, data.append);
665 final long initialTime = data.createOnDemand || file == null ?
666 0 : initialFileTime(file);
667
668 final RollingFileManager rm = new RollingFileManager(data.getLoggerContext(), data.fileName, data.pattern, os,
669 data.append, data.createOnDemand, size, initialTime, data.policy, data.strategy, data.advertiseURI,
670 data.layout, data.filePermissions, data.fileOwner, data.fileGroup, writeHeader, buffer);
671 if (os != null && rm.isAttributeViewEnabled()) {
672 rm.defineAttributeView(file.toPath());
673 }
674
675 return rm;
676 } catch (final IOException ex) {
677 LOGGER.error("RollingFileManager (" + name + ") " + ex, ex);
678 }
679 return null;
680 }
681 }
682
683 private static long initialFileTime(final File file) {
684 final Path path = file.toPath();
685 if (Files.exists(path)) {
686 try {
687 final BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
688 final FileTime fileTime = attrs.creationTime();
689 if (fileTime.compareTo(EPOCH) > 0) {
690 LOGGER.debug("Returning file creation time for {}", file.getAbsolutePath());
691 return fileTime.toMillis();
692 }
693 LOGGER.info("Unable to obtain file creation time for " + file.getAbsolutePath());
694 } catch (final Exception ex) {
695 LOGGER.info("Unable to calculate file creation time for " + file.getAbsolutePath() + ": " + ex.getMessage());
696 }
697 }
698 return file.lastModified();
699 }
700
701 private static class EmptyQueue extends ArrayBlockingQueue<Runnable> {
702
703
704
705
706 private static final long serialVersionUID = 1L;
707
708 EmptyQueue() {
709 super(1);
710 }
711
712 @Override
713 public int remainingCapacity() {
714 return 0;
715 }
716
717 @Override
718 public boolean add(final Runnable runnable) {
719 throw new IllegalStateException("Queue is full");
720 }
721
722 @Override
723 public void put(final Runnable runnable) throws InterruptedException {
724
725 throw new InterruptedException("Unable to insert into queue");
726 }
727
728 @Override
729 public boolean offer(final Runnable runnable, final long timeout, final TimeUnit timeUnit) throws InterruptedException {
730 Thread.sleep(timeUnit.toMillis(timeout));
731 return false;
732 }
733
734 @Override
735 public boolean addAll(final Collection<? extends Runnable> collection) {
736 if (collection.size() > 0) {
737 throw new IllegalArgumentException("Too many items in collection");
738 }
739 return false;
740 }
741
742 }
743
744 }