View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
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   * The Rolling File Manager.
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      /* This executor pool will create a new Thread for every work async action to be performed. Using it allows
69         us to make sure all the Threads are completed when the Manager is stopped. */
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      * @since 2.7
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      * Returns a RollingFileManager.
136      * @param fileName The file name.
137      * @param pattern The pattern for rolling file.
138      * @param append true if the file should be appended to.
139      * @param bufferedIO true if data should be buffered.
140      * @param policy The TriggeringPolicy.
141      * @param strategy The RolloverStrategy.
142      * @param advertiseURI the URI to use when advertising the file
143      * @param layout The Layout.
144      * @param bufferSize buffer size to use if bufferedIO is true
145      * @param immediateFlush flush on every write or not
146      * @param createOnDemand true if you want to lazy-create the file (a.k.a. on-demand.)
147      * @param configuration The configuration.
148      * @return A RollingFileManager.
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      * Returns the name of the File being managed.
161      * @return The name of the File being managed.
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     // override to make visible for unit tests
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      * Returns the current size of the file.
198      * @return The size of the file in bytes.
199      */
200     public long getFileSize() {
201         return size + byteBuffer.position();
202     }
203 
204     /**
205      * Returns the time the file was created.
206      * @return The time the file was created.
207      */
208     public long getFileTime() {
209         return initialTime;
210     }
211 
212     /**
213      * Determines if a rollover should occur.
214      * @param event The LogEvent.
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             // Allow at least the minimum interval to pass so async actions can complete.
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             // Preserve interrupt status
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      * Returns the pattern processor.
295      * @return The PatternProcessor.
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      * Returns the triggering policy.
330      * @param <T> TriggeringPolicy type
331      * @return The TriggeringPolicy
332      */
333     @SuppressWarnings("unchecked")
334     public <T extends TriggeringPolicy> T getTriggeringPolicy() {
335         // TODO We could parameterize this class with a TriggeringPolicy instead of type casting here.
336         return (T) this.triggeringPolicy;
337     }
338 
339     /**
340      * Returns the rollover strategy.
341      * @return The RolloverStrategy
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             // Block until the asynchronous operation is completed.
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      * Performs actions asynchronously.
394      */
395     private static class AsyncAction extends AbstractAction {
396 
397         private final Action action;
398         private final RollingFileManager manager;
399 
400         /**
401          * Constructor.
402          * @param act The action to perform.
403          * @param manager The manager.
404          */
405         public AsyncAction(final Action act, final RollingFileManager manager) {
406             this.action = act;
407             this.manager = manager;
408         }
409 
410         /**
411          * Executes an action.
412          *
413          * @return true if action was successful.  A return value of false will cause
414          *         the rollover to be aborted if possible.
415          * @throws java.io.IOException if IO error, a thrown exception will cause the rollover
416          *                             to be aborted if possible.
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          * Cancels the action if not already initialized or waits till completion.
429          */
430         @Override
431         public void close() {
432             action.close();
433         }
434 
435         /**
436          * Determines if action has been completed.
437          *
438          * @return true if action is complete.
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      * Factory data.
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          * Creates the data for the factory.
480          * @param pattern The pattern.
481          * @param append The append flag.
482          * @param bufferedIO The bufferedIO flag.
483          * @param advertiseURI
484          * @param layout The Layout.
485          * @param bufferSize the buffer size
486          * @param immediateFlush flush on every write or not
487          * @param createOnDemand true if you want to lazy-create the file (a.k.a. on-demand.)
488          * @param configuration The configuration
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      * Factory to create a RollingFileManager.
553      */
554     private static class RollingFileManagerFactory implements ManagerFactory<RollingFileManager, FactoryData> {
555 
556         /**
557          * Creates a RollingFileManager.
558          * @param name The name of the entity to manage.
559          * @param data The data required to create the entity.
560          * @return a RollingFileManager.
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                 // LOG4J2-1140: check writeHeader before creating the file
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(); // LOG4J2-531 create file first so time has valid value
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             /* No point in going into a permanent wait */
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 }