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