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