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