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 boolean directWrite;
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.directWrite = rolloverStrategy instanceof DirectWriteRolloverStrategy;
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.directWrite = rolloverStrategy instanceof DirectWriteRolloverStrategy;
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.patternProcessor = new PatternProcessor(pattern);
146 this.patternProcessor.setPrevFileTime(initialTime);
147 this.triggeringPolicy = triggeringPolicy;
148 this.rolloverStrategy = rolloverStrategy;
149 this.fileName = fileName;
150 this.directWrite = rolloverStrategy instanceof DirectFileRolloverStrategy;
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 (directWrite) {
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 (directWrite) {
217 fileName = ((DirectFileRolloverStrategy) rolloverStrategy).getCurrentFileName(this);
218 }
219 return fileName;
220 }
221
222 public boolean isDirectWrite() {
223 return directWrite;
224 }
225
226 public FileExtension getFileExtension() {
227 return patternProcessor.getFileExtension();
228 }
229
230
231 @Override
232 protected synchronized void write(final byte[] bytes, final int offset, final int length,
233 final boolean immediateFlush) {
234 super.write(bytes, offset, length, immediateFlush);
235 }
236
237 @Override
238 protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) {
239 size += length;
240 super.writeToDestination(bytes, offset, length);
241 }
242
243 public boolean isRenameEmptyFiles() {
244 return renameEmptyFiles;
245 }
246
247 public void setRenameEmptyFiles(final boolean renameEmptyFiles) {
248 this.renameEmptyFiles = renameEmptyFiles;
249 }
250
251
252
253
254
255 public long getFileSize() {
256 return size + byteBuffer.position();
257 }
258
259
260
261
262
263 public long getFileTime() {
264 return initialTime;
265 }
266
267
268
269
270
271 public synchronized void checkRollover(final LogEvent event) {
272 if (triggeringPolicy.isTriggeringEvent(event)) {
273 rollover();
274 }
275 }
276
277 @Override
278 public boolean releaseSub(final long timeout, final TimeUnit timeUnit) {
279 LOGGER.debug("Shutting down RollingFileManager {}", getName());
280 boolean stopped = true;
281 if (triggeringPolicy instanceof LifeCycle2) {
282 stopped &= ((LifeCycle2) triggeringPolicy).stop(timeout, timeUnit);
283 } else if (triggeringPolicy instanceof LifeCycle) {
284 ((LifeCycle) triggeringPolicy).stop();
285 stopped &= true;
286 }
287 final boolean status = super.releaseSub(timeout, timeUnit) && stopped;
288 asyncExecutor.shutdown();
289 try {
290
291 final long millis = timeUnit.toMillis(timeout);
292 final long waitInterval = MIN_DURATION < millis ? millis : MIN_DURATION;
293
294 for (int count = 1; count <= MAX_TRIES && !asyncExecutor.isTerminated(); ++count) {
295 asyncExecutor.awaitTermination(waitInterval * count, TimeUnit.MILLISECONDS);
296 }
297 if (asyncExecutor.isTerminated()) {
298 LOGGER.debug("All asynchronous threads have terminated");
299 } else {
300 asyncExecutor.shutdownNow();
301 try {
302 asyncExecutor.awaitTermination(timeout, timeUnit);
303 if (asyncExecutor.isTerminated()) {
304 LOGGER.debug("All asynchronous threads have terminated");
305 } else {
306 LOGGER.debug("RollingFileManager shutting down but some asynchronous services may not have completed");
307 }
308 } catch (final InterruptedException inner) {
309 LOGGER.warn("RollingFileManager stopped but some asynchronous services may not have completed.");
310 }
311 }
312 } catch (final InterruptedException ie) {
313 asyncExecutor.shutdownNow();
314 try {
315 asyncExecutor.awaitTermination(timeout, timeUnit);
316 if (asyncExecutor.isTerminated()) {
317 LOGGER.debug("All asynchronous threads have terminated");
318 }
319 } catch (final InterruptedException inner) {
320 LOGGER.warn("RollingFileManager stopped but some asynchronous services may not have completed.");
321 }
322
323 Thread.currentThread().interrupt();
324 }
325 LOGGER.debug("RollingFileManager shutdown completed with status {}", status);
326 return status;
327 }
328
329 public synchronized void rollover(Date prevFileTime, Date prevRollTime) {
330 getPatternProcessor().setPrevFileTime(prevFileTime.getTime());
331 getPatternProcessor().setCurrentFileTime(prevRollTime.getTime());
332 rollover();
333 }
334
335 public synchronized void rollover() {
336 if (!hasOutputStream() && !isCreateOnDemand()) {
337 return;
338 }
339 if (rollover(rolloverStrategy)) {
340 try {
341 size = 0;
342 initialTime = System.currentTimeMillis();
343 createFileAfterRollover();
344 } catch (final IOException e) {
345 logError("Failed to create file after rollover", e);
346 }
347 }
348 }
349
350 protected void createFileAfterRollover() throws IOException {
351 setOutputStream(createOutputStream());
352 }
353
354
355
356
357
358 public PatternProcessor getPatternProcessor() {
359 return patternProcessor;
360 }
361
362 public void setTriggeringPolicy(final TriggeringPolicy triggeringPolicy) {
363 triggeringPolicy.initialize(this);
364 final TriggeringPolicy policy = this.triggeringPolicy;
365 int count = 0;
366 boolean policyUpdated = false;
367 do {
368 ++count;
369 } while (!(policyUpdated = triggeringPolicyUpdater.compareAndSet(this, this.triggeringPolicy, triggeringPolicy))
370 && count < MAX_TRIES);
371 if (policyUpdated) {
372 if (triggeringPolicy instanceof LifeCycle) {
373 ((LifeCycle) triggeringPolicy).start();
374 }
375 if (policy instanceof LifeCycle) {
376 ((LifeCycle) policy).stop();
377 }
378 } else {
379 if (triggeringPolicy instanceof LifeCycle) {
380 ((LifeCycle) triggeringPolicy).stop();
381 }
382 }
383 }
384
385 public void setRolloverStrategy(final RolloverStrategy rolloverStrategy) {
386 rolloverStrategyUpdater.compareAndSet(this, this.rolloverStrategy, rolloverStrategy);
387 }
388
389 public void setPatternProcessor(final PatternProcessor patternProcessor) {
390 patternProcessorUpdater.compareAndSet(this, this.patternProcessor, patternProcessor);
391 }
392
393
394
395
396
397
398 @SuppressWarnings("unchecked")
399 public <T extends TriggeringPolicy> T getTriggeringPolicy() {
400
401 return (T) this.triggeringPolicy;
402 }
403
404
405
406
407
408 public RolloverStrategy getRolloverStrategy() {
409 return this.rolloverStrategy;
410 }
411
412 private boolean rollover(final RolloverStrategy strategy) {
413
414 boolean releaseRequired = false;
415 try {
416
417 semaphore.acquire();
418 releaseRequired = true;
419 } catch (final InterruptedException e) {
420 logError("Thread interrupted while attempting to check rollover", e);
421 return false;
422 }
423
424 boolean success = true;
425
426 try {
427 final RolloverDescription descriptor = strategy.rollover(this);
428 if (descriptor != null) {
429 writeFooter();
430 closeOutputStream();
431 if (descriptor.getSynchronous() != null) {
432 LOGGER.debug("RollingFileManager executing synchronous {}", descriptor.getSynchronous());
433 try {
434 success = descriptor.getSynchronous().execute();
435 } catch (final Exception ex) {
436 success = false;
437 logError("Caught error in synchronous task", ex);
438 }
439 }
440
441 if (success && descriptor.getAsynchronous() != null) {
442 LOGGER.debug("RollingFileManager executing async {}", descriptor.getAsynchronous());
443 asyncExecutor.execute(new AsyncAction(descriptor.getAsynchronous(), this));
444 releaseRequired = false;
445 }
446 return true;
447 }
448 return false;
449 } finally {
450 if (releaseRequired) {
451 semaphore.release();
452 }
453 }
454
455 }
456
457
458
459
460 private static class AsyncAction extends AbstractAction {
461
462 private final Action action;
463 private final RollingFileManager manager;
464
465
466
467
468
469
470 public AsyncAction(final Action act, final RollingFileManager manager) {
471 this.action = act;
472 this.manager = manager;
473 }
474
475
476
477
478
479
480
481
482
483 @Override
484 public boolean execute() throws IOException {
485 try {
486 return action.execute();
487 } finally {
488 manager.semaphore.release();
489 }
490 }
491
492
493
494
495 @Override
496 public void close() {
497 action.close();
498 }
499
500
501
502
503
504
505 @Override
506 public boolean isComplete() {
507 return action.isComplete();
508 }
509
510 @Override
511 public String toString() {
512 final StringBuilder builder = new StringBuilder();
513 builder.append(super.toString());
514 builder.append("[action=");
515 builder.append(action);
516 builder.append(", manager=");
517 builder.append(manager);
518 builder.append(", isComplete()=");
519 builder.append(isComplete());
520 builder.append(", isInterrupted()=");
521 builder.append(isInterrupted());
522 builder.append("]");
523 return builder.toString();
524 }
525 }
526
527
528
529
530 private static class FactoryData extends ConfigurationFactoryData {
531 private final String fileName;
532 private final String pattern;
533 private final boolean append;
534 private final boolean bufferedIO;
535 private final int bufferSize;
536 private final boolean immediateFlush;
537 private final boolean createOnDemand;
538 private final TriggeringPolicy policy;
539 private final RolloverStrategy strategy;
540 private final String advertiseURI;
541 private final Layout<? extends Serializable> layout;
542 private final String filePermissions;
543 private final String fileOwner;
544 private final String fileGroup;
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561 public FactoryData(final String fileName, final String pattern, final boolean append, final boolean bufferedIO,
562 final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
563 final Layout<? extends Serializable> layout, final int bufferSize, final boolean immediateFlush,
564 final boolean createOnDemand, final String filePermissions, final String fileOwner, final String fileGroup,
565 final Configuration configuration) {
566 super(configuration);
567 this.fileName = fileName;
568 this.pattern = pattern;
569 this.append = append;
570 this.bufferedIO = bufferedIO;
571 this.bufferSize = bufferSize;
572 this.policy = policy;
573 this.strategy = strategy;
574 this.advertiseURI = advertiseURI;
575 this.layout = layout;
576 this.immediateFlush = immediateFlush;
577 this.createOnDemand = createOnDemand;
578 this.filePermissions = filePermissions;
579 this.fileOwner = fileOwner;
580 this.fileGroup = fileGroup;
581 }
582
583 public TriggeringPolicy getTriggeringPolicy() {
584 return this.policy;
585 }
586
587 public RolloverStrategy getRolloverStrategy() {
588 return this.strategy;
589 }
590
591 public String getPattern() {
592 return pattern;
593 }
594
595 @Override
596 public String toString() {
597 final StringBuilder builder = new StringBuilder();
598 builder.append(super.toString());
599 builder.append("[pattern=");
600 builder.append(pattern);
601 builder.append(", append=");
602 builder.append(append);
603 builder.append(", bufferedIO=");
604 builder.append(bufferedIO);
605 builder.append(", bufferSize=");
606 builder.append(bufferSize);
607 builder.append(", policy=");
608 builder.append(policy);
609 builder.append(", strategy=");
610 builder.append(strategy);
611 builder.append(", advertiseURI=");
612 builder.append(advertiseURI);
613 builder.append(", layout=");
614 builder.append(layout);
615 builder.append(", filePermissions=");
616 builder.append(filePermissions);
617 builder.append(", fileOwner=");
618 builder.append(fileOwner);
619 builder.append("]");
620 return builder.toString();
621 }
622 }
623
624
625
626
627
628
629
630 @Override
631 public void updateData(final Object data) {
632 final FactoryData factoryData = (FactoryData) data;
633 setRolloverStrategy(factoryData.getRolloverStrategy());
634 setPatternProcessor(new PatternProcessor(factoryData.getPattern(), getPatternProcessor()));
635 setTriggeringPolicy(factoryData.getTriggeringPolicy());
636 }
637
638
639
640
641 private static class RollingFileManagerFactory implements ManagerFactory<RollingFileManager, FactoryData> {
642
643
644
645
646
647
648
649 @Override
650 public RollingFileManager createManager(final String name, final FactoryData data) {
651 long size = 0;
652 File file = null;
653 if (data.fileName != null) {
654 file = new File(data.fileName);
655
656 try {
657 FileUtils.makeParentDirs(file);
658 final boolean created = data.createOnDemand ? false : file.createNewFile();
659 LOGGER.trace("New file '{}' created = {}", name, created);
660 } catch (final IOException ioe) {
661 LOGGER.error("Unable to create file " + name, ioe);
662 return null;
663 }
664 size = data.append ? file.length() : 0;
665 }
666
667 try {
668 final int actualSize = data.bufferedIO ? data.bufferSize : Constants.ENCODER_BYTE_BUFFER_SIZE;
669 final ByteBuffer buffer = ByteBuffer.wrap(new byte[actualSize]);
670 final OutputStream os = data.createOnDemand || data.fileName == null ? null :
671 new FileOutputStream(data.fileName, data.append);
672
673 final long initialTime = file == null || !file.exists() ? 0 : initialFileTime(file);
674 final boolean writeHeader = file != null && file.exists() && file.length() == 0;
675
676 final RollingFileManager rm = new RollingFileManager(data.getLoggerContext(), data.fileName, data.pattern, os,
677 data.append, data.createOnDemand, size, initialTime, data.policy, data.strategy, data.advertiseURI,
678 data.layout, data.filePermissions, data.fileOwner, data.fileGroup, writeHeader, buffer);
679 if (os != null && rm.isAttributeViewEnabled()) {
680 rm.defineAttributeView(file.toPath());
681 }
682
683 return rm;
684 } catch (final IOException ex) {
685 LOGGER.error("RollingFileManager (" + name + ") " + ex, ex);
686 }
687 return null;
688 }
689 }
690
691 private static long initialFileTime(final File file) {
692 final Path path = file.toPath();
693 if (Files.exists(path)) {
694 try {
695 final BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
696 final FileTime fileTime = attrs.creationTime();
697 if (fileTime.compareTo(EPOCH) > 0) {
698 LOGGER.debug("Returning file creation time for {}", file.getAbsolutePath());
699 return fileTime.toMillis();
700 }
701 LOGGER.info("Unable to obtain file creation time for " + file.getAbsolutePath());
702 } catch (final Exception ex) {
703 LOGGER.info("Unable to calculate file creation time for " + file.getAbsolutePath() + ": " + ex.getMessage());
704 }
705 }
706 return file.lastModified();
707 }
708
709 private static class EmptyQueue extends ArrayBlockingQueue<Runnable> {
710
711
712
713
714 private static final long serialVersionUID = 1L;
715
716 EmptyQueue() {
717 super(1);
718 }
719
720 @Override
721 public int remainingCapacity() {
722 return 0;
723 }
724
725 @Override
726 public boolean add(final Runnable runnable) {
727 throw new IllegalStateException("Queue is full");
728 }
729
730 @Override
731 public void put(final Runnable runnable) throws InterruptedException {
732
733 throw new InterruptedException("Unable to insert into queue");
734 }
735
736 @Override
737 public boolean offer(final Runnable runnable, final long timeout, final TimeUnit timeUnit) throws InterruptedException {
738 Thread.sleep(timeUnit.toMillis(timeout));
739 return false;
740 }
741
742 @Override
743 public boolean addAll(final Collection<? extends Runnable> collection) {
744 if (collection.size() > 0) {
745 throw new IllegalArgumentException("Too many items in collection");
746 }
747 return false;
748 }
749
750 }
751
752 }