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