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.FileNotFoundException;
21  import java.io.FileOutputStream;
22  import java.io.IOException;
23  import java.io.OutputStream;
24  import java.io.Serializable;
25  import java.nio.ByteBuffer;
26  import java.util.concurrent.Semaphore;
27  import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
28  
29  import org.apache.logging.log4j.core.Layout;
30  import org.apache.logging.log4j.core.LogEvent;
31  import org.apache.logging.log4j.core.appender.FileManager;
32  import org.apache.logging.log4j.core.appender.ManagerFactory;
33  import org.apache.logging.log4j.core.appender.rolling.action.AbstractAction;
34  import org.apache.logging.log4j.core.appender.rolling.action.Action;
35  import org.apache.logging.log4j.core.util.Constants;
36  import org.apache.logging.log4j.core.util.Log4jThread;
37  
38  /**
39   * The Rolling File Manager.
40   */
41  public class RollingFileManager extends FileManager {
42  
43      private static RollingFileManagerFactory factory = new RollingFileManagerFactory();
44  
45      protected long size;
46      private long initialTime;
47      private final PatternProcessor patternProcessor;
48      private final Semaphore semaphore = new Semaphore(1);
49      private volatile TriggeringPolicy triggeringPolicy;
50      private volatile RolloverStrategy rolloverStrategy;
51      private volatile boolean renameEmptyFiles = false;
52  
53      private static final AtomicReferenceFieldUpdater<RollingFileManager, TriggeringPolicy> triggeringPolicyUpdater =
54              AtomicReferenceFieldUpdater.newUpdater(RollingFileManager.class, TriggeringPolicy.class, "triggeringPolicy");
55  
56      private static final AtomicReferenceFieldUpdater<RollingFileManager, RolloverStrategy> rolloverStrategyUpdater =
57              AtomicReferenceFieldUpdater.newUpdater(RollingFileManager.class, RolloverStrategy.class, "rolloverStrategy");
58  
59      @Deprecated
60      protected RollingFileManager(final String fileName, final String pattern, final OutputStream os,
61              final boolean append, final long size, final long time, final TriggeringPolicy triggeringPolicy,
62              final RolloverStrategy rolloverStrategy, final String advertiseURI,
63              final Layout<? extends Serializable> layout, final int bufferSize, final boolean writeHeader) {
64          this(fileName, pattern, os, append, size, time, triggeringPolicy, rolloverStrategy, advertiseURI, layout,
65                  writeHeader, ByteBuffer.wrap(new byte[Constants.ENCODER_BYTE_BUFFER_SIZE]));
66      }
67  
68      protected RollingFileManager(final String fileName, final String pattern, final OutputStream os,
69              final boolean append, final long size, final long time, final TriggeringPolicy triggeringPolicy,
70              final RolloverStrategy rolloverStrategy, final String advertiseURI,
71              final Layout<? extends Serializable> layout, final boolean writeHeader, final ByteBuffer buffer) {
72          super(fileName, os, append, false, advertiseURI, layout, writeHeader, buffer);
73          this.size = size;
74          this.initialTime = time;
75          this.triggeringPolicy = triggeringPolicy;
76          this.rolloverStrategy = rolloverStrategy;
77          this.patternProcessor = new PatternProcessor(pattern);
78          this.patternProcessor.setPrevFileTime(time);
79      }
80  
81      public void initialize() {
82          triggeringPolicy.initialize(this);
83      }
84  
85      /**
86       * Returns a RollingFileManager.
87       * @param fileName The file name.
88       * @param pattern The pattern for rolling file.
89       * @param append true if the file should be appended to.
90       * @param bufferedIO true if data should be buffered.
91       * @param policy The TriggeringPolicy.
92       * @param strategy The RolloverStrategy.
93       * @param advertiseURI the URI to use when advertising the file
94       * @param layout The Layout.
95       * @param bufferSize buffer size to use if bufferedIO is true
96       * @return A RollingFileManager.
97       */
98      public static RollingFileManager getFileManager(final String fileName, final String pattern, final boolean append,
99              final boolean bufferedIO, final TriggeringPolicy policy, final RolloverStrategy strategy,
100             final String advertiseURI, final Layout<? extends Serializable> layout, final int bufferSize,
101             final boolean immediateFlush) {
102 
103         return (RollingFileManager) getManager(fileName, new FactoryData(pattern, append,
104             bufferedIO, policy, strategy, advertiseURI, layout, bufferSize, immediateFlush), factory);
105     }
106 
107     // override to make visible for unit tests
108     @Override
109     protected synchronized void write(final byte[] bytes, final int offset, final int length,
110             final boolean immediateFlush) {
111         super.write(bytes, offset, length, immediateFlush);
112     }
113 
114     @Override
115     protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) {
116         size += length;
117         super.writeToDestination(bytes, offset, length);
118     }
119 
120     public boolean isRenameEmptyFiles() {
121         return renameEmptyFiles;
122     }
123 
124     public void setRenameEmptyFiles(boolean renameEmptyFiles) {
125         this.renameEmptyFiles = renameEmptyFiles;
126     }
127 
128     /**
129      * Returns the current size of the file.
130      * @return The size of the file in bytes.
131      */
132     public long getFileSize() {
133         return size + byteBuffer.position();
134     }
135 
136     /**
137      * Returns the time the file was created.
138      * @return The time the file was created.
139      */
140     public long getFileTime() {
141         return initialTime;
142     }
143 
144     /**
145      * Determine if a rollover should occur.
146      * @param event The LogEvent.
147      */
148     public synchronized void checkRollover(final LogEvent event) {
149         if (triggeringPolicy.isTriggeringEvent(event)) {
150             rollover();
151         }
152     }
153 
154     public synchronized void rollover() {
155         if (rollover(rolloverStrategy)) {
156             try {
157                 size = 0;
158                 initialTime = System.currentTimeMillis();
159                 createFileAfterRollover();
160             } catch (final IOException e) {
161                 logError("Failed to create file after rollover", e);
162             }
163         }
164     }
165 
166     protected void createFileAfterRollover() throws IOException  {
167         setOutputStream(new FileOutputStream(getFileName(), isAppend()));
168     }
169 
170     /**
171      * Returns the pattern processor.
172      * @return The PatternProcessor.
173      */
174     public PatternProcessor getPatternProcessor() {
175         return patternProcessor;
176     }
177 
178     public void setTriggeringPolicy(final TriggeringPolicy triggeringPolicy) {
179         triggeringPolicy.initialize(this);
180         triggeringPolicyUpdater.compareAndSet(this, this.triggeringPolicy, triggeringPolicy);
181     }
182 
183     public void setRolloverStrategy(final RolloverStrategy rolloverStrategy) {
184         rolloverStrategyUpdater.compareAndSet(this, this.rolloverStrategy, rolloverStrategy);
185     }
186 
187     /**
188      * Returns the triggering policy.
189      * @param <T> TriggeringPolicy type
190      * @return The TriggeringPolicy
191      */
192     @SuppressWarnings("unchecked")
193     public <T extends TriggeringPolicy> T getTriggeringPolicy() {
194         // TODO We could parameterize this class with a TriggeringPolicy instead of type casting here.
195         return (T) this.triggeringPolicy;
196     }
197 
198     /**
199      * Returns the rollover strategy.
200      * @return The RolloverStrategy
201      */
202     public RolloverStrategy getRolloverStrategy() {
203         return this.rolloverStrategy;
204     }
205 
206     private boolean rollover(final RolloverStrategy strategy) {
207 
208         try {
209             // Block until the asynchronous operation is completed.
210             semaphore.acquire();
211         } catch (final InterruptedException e) {
212             logError("Thread interrupted while attempting to check rollover", e);
213             return false;
214         }
215 
216         boolean success = false;
217         Thread thread = null;
218 
219         try {
220             final RolloverDescription descriptor = strategy.rollover(this);
221             if (descriptor != null) {
222                 writeFooter();
223                 close();
224                 if (descriptor.getSynchronous() != null) {
225                     LOGGER.debug("RollingFileManager executing synchronous {}", descriptor.getSynchronous());
226                     try {
227                         success = descriptor.getSynchronous().execute();
228                     } catch (final Exception ex) {
229                         logError("Caught error in synchronous task", ex);
230                     }
231                 }
232 
233                 if (success && descriptor.getAsynchronous() != null) {
234                     LOGGER.debug("RollingFileManager executing async {}", descriptor.getAsynchronous());
235                     thread = new Log4jThread(new AsyncAction(descriptor.getAsynchronous(), this));
236                     thread.start();
237                 }
238                 return true;
239             }
240             return false;
241         } finally {
242             if (thread == null || !thread.isAlive()) {
243                 semaphore.release();
244             }
245         }
246 
247     }
248 
249     /**
250      * Performs actions asynchronously.
251      */
252     private static class AsyncAction extends AbstractAction {
253 
254         private final Action action;
255         private final RollingFileManager manager;
256 
257         /**
258          * Constructor.
259          * @param act The action to perform.
260          * @param manager The manager.
261          */
262         public AsyncAction(final Action act, final RollingFileManager manager) {
263             this.action = act;
264             this.manager = manager;
265         }
266 
267         /**
268          * Perform an action.
269          *
270          * @return true if action was successful.  A return value of false will cause
271          *         the rollover to be aborted if possible.
272          * @throws java.io.IOException if IO error, a thrown exception will cause the rollover
273          *                             to be aborted if possible.
274          */
275         @Override
276         public boolean execute() throws IOException {
277             try {
278                 return action.execute();
279             } finally {
280                 manager.semaphore.release();
281             }
282         }
283 
284         /**
285          * Cancels the action if not already initialized or waits till completion.
286          */
287         @Override
288         public void close() {
289             action.close();
290         }
291 
292         /**
293          * Determines if action has been completed.
294          *
295          * @return true if action is complete.
296          */
297         @Override
298         public boolean isComplete() {
299             return action.isComplete();
300         }
301 
302         @Override
303         public String toString() {
304             final StringBuilder builder = new StringBuilder();
305             builder.append(super.toString());
306             builder.append("[action=");
307             builder.append(action);
308             builder.append(", manager=");
309             builder.append(manager);
310             builder.append(", isComplete()=");
311             builder.append(isComplete());
312             builder.append(", isInterrupted()=");
313             builder.append(isInterrupted());
314             builder.append("]");
315             return builder.toString();
316         }
317     }
318 
319     /**
320      * Factory data.
321      */
322     private static class FactoryData {
323         private final String pattern;
324         private final boolean append;
325         private final boolean bufferedIO;
326         private final int bufferSize;
327         private final boolean immediateFlush;
328         private final TriggeringPolicy policy;
329         private final RolloverStrategy strategy;
330         private final String advertiseURI;
331         private final Layout<? extends Serializable> layout;
332 
333         /**
334          * Create the data for the factory.
335          * @param pattern The pattern.
336          * @param append The append flag.
337          * @param bufferedIO The bufferedIO flag.
338          * @param advertiseURI
339          * @param layout The Layout.
340          * @param bufferSize the buffer size
341          * @param immediateFlush flush on every write or not
342          */
343         public FactoryData(final String pattern, final boolean append, final boolean bufferedIO,
344                 final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
345                 final Layout<? extends Serializable> layout, final int bufferSize, final boolean immediateFlush) {
346             this.pattern = pattern;
347             this.append = append;
348             this.bufferedIO = bufferedIO;
349             this.bufferSize = bufferSize;
350             this.policy = policy;
351             this.strategy = strategy;
352             this.advertiseURI = advertiseURI;
353             this.layout = layout;
354             this.immediateFlush = immediateFlush;
355         }
356 
357         public TriggeringPolicy getTriggeringPolicy()
358         {
359             return this.policy;
360         }
361 
362         public RolloverStrategy getRolloverStrategy()
363         {
364             return this.strategy;
365         }
366 
367         @Override
368         public String toString() {
369             final StringBuilder builder = new StringBuilder();
370             builder.append(super.toString());
371             builder.append("[pattern=");
372             builder.append(pattern);
373             builder.append(", append=");
374             builder.append(append);
375             builder.append(", bufferedIO=");
376             builder.append(bufferedIO);
377             builder.append(", bufferSize=");
378             builder.append(bufferSize);
379             builder.append(", policy=");
380             builder.append(policy);
381             builder.append(", strategy=");
382             builder.append(strategy);
383             builder.append(", advertiseURI=");
384             builder.append(advertiseURI);
385             builder.append(", layout=");
386             builder.append(layout);
387             builder.append("]");
388             return builder.toString();
389         }
390     }
391 
392     @Override
393     public void updateData(final Object data)
394     {
395         final FactoryData factoryData = (FactoryData) data;
396         setRolloverStrategy(factoryData.getRolloverStrategy());
397         setTriggeringPolicy(factoryData.getTriggeringPolicy());
398     }
399 
400     /**
401      * Factory to create a RollingFileManager.
402      */
403     private static class RollingFileManagerFactory implements ManagerFactory<RollingFileManager, FactoryData> {
404 
405         /**
406          * Creates a RollingFileManager.
407          * @param name The name of the entity to manage.
408          * @param data The data required to create the entity.
409          * @return a RollingFileManager.
410          */
411         @Override
412         public RollingFileManager createManager(final String name, final FactoryData data) {
413             final File file = new File(name);
414             final File parent = file.getParentFile();
415             if (null != parent && !parent.exists()) {
416                 parent.mkdirs();
417             }
418             // LOG4J2-1140: check writeHeader before creating the file
419             final boolean writeHeader = !data.append || !file.exists();
420             try {
421                 file.createNewFile();
422             } catch (final IOException ioe) {
423                 LOGGER.error("Unable to create file " + name, ioe);
424                 return null;
425             }
426             final long size = data.append ? file.length() : 0;
427 
428             OutputStream os;
429             try {
430                 os = new FileOutputStream(name, data.append);
431                 final int actualSize = data.bufferedIO ? data.bufferSize : Constants.ENCODER_BYTE_BUFFER_SIZE;
432                 final ByteBuffer buffer = ByteBuffer.wrap(new byte[actualSize]);
433 
434                 final long time = file.lastModified(); // LOG4J2-531 create file first so time has valid value
435                 return new RollingFileManager(name, data.pattern, os, data.append, size, time, data.policy,
436                     data.strategy, data.advertiseURI, data.layout, writeHeader, buffer);
437             } catch (final FileNotFoundException ex) {
438                 LOGGER.error("FileManager (" + name + ") " + ex, ex);
439             }
440             return null;
441         }
442     }
443 
444 }