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