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) {
53          super(fileName, os, append, false, advertiseURI, layout, bufferSize);
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 TriggeringPolicy getTriggeringPolicy() {
143         return this.triggeringPolicy;
144     }
145 
146     /**
147      * Returns the rollover strategy
148      * @return The RolloverStrategy
149      */
150     public RolloverStrategy getRolloverStrategy() {
151         return this.rolloverStrategy;
152     }
153 
154     private boolean rollover(final RolloverStrategy strategy) {
155 
156         try {
157             // Block until the asynchronous operation is completed.
158             semaphore.acquire();
159         } catch (final InterruptedException ie) {
160             LOGGER.error("Thread interrupted while attempting to check rollover", ie);
161             return false;
162         }
163 
164         boolean success = false;
165         Thread thread = null;
166 
167         try {
168             final RolloverDescription descriptor = strategy.rollover(this);
169             if (descriptor != null) {
170                 writeFooter();
171                 close();
172                 if (descriptor.getSynchronous() != null) {
173                     LOGGER.debug("RollingFileManager executing synchronous {}", descriptor.getSynchronous());
174                     try {
175                         success = descriptor.getSynchronous().execute();
176                     } catch (final Exception ex) {
177                         LOGGER.error("Error in synchronous task", ex);
178                     }
179                 }
180 
181                 if (success && descriptor.getAsynchronous() != null) {
182                     LOGGER.debug("RollingFileManager executing async {}", descriptor.getAsynchronous());
183                     thread = new Thread(new AsyncAction(descriptor.getAsynchronous(), this));
184                     thread.start();
185                 }
186                 return true;
187             }
188             return false;
189         } finally {
190             if (thread == null || !thread.isAlive()) {
191                 semaphore.release();
192             }
193         }
194 
195     }
196 
197     /**
198      * Performs actions asynchronously.
199      */
200     private static class AsyncAction extends AbstractAction {
201 
202         private final Action action;
203         private final RollingFileManager manager;
204 
205         /**
206          * Constructor.
207          * @param act The action to perform.
208          * @param manager The manager.
209          */
210         public AsyncAction(final Action act, final RollingFileManager manager) {
211             this.action = act;
212             this.manager = manager;
213         }
214 
215         /**
216          * Perform an action.
217          *
218          * @return true if action was successful.  A return value of false will cause
219          *         the rollover to be aborted if possible.
220          * @throws java.io.IOException if IO error, a thrown exception will cause the rollover
221          *                             to be aborted if possible.
222          */
223         @Override
224         public boolean execute() throws IOException {
225             try {
226                 return action.execute();
227             } finally {
228                 manager.semaphore.release();
229             }
230         }
231 
232         /**
233          * Cancels the action if not already initialized or waits till completion.
234          */
235         @Override
236         public void close() {
237             action.close();
238         }
239 
240         /**
241          * Determines if action has been completed.
242          *
243          * @return true if action is complete.
244          */
245         @Override
246         public boolean isComplete() {
247             return action.isComplete();
248         }
249     }
250 
251     /**
252      * Factory data.
253      */
254     private static class FactoryData {
255         private final String pattern;
256         private final boolean append;
257         private final boolean bufferedIO;
258         private final int bufferSize;
259         private final TriggeringPolicy policy;
260         private final RolloverStrategy strategy;
261         private final String advertiseURI;
262         private final Layout<? extends Serializable> layout;
263 
264         /**
265          * Create the data for the factory.
266          * @param pattern The pattern.
267          * @param append The append flag.
268          * @param bufferedIO The bufferedIO flag.
269          * @param advertiseURI
270          * @param layout The Layout.
271          * @param bufferSize the buffer size
272          */
273         public FactoryData(final String pattern, final boolean append, final boolean bufferedIO,
274                 final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
275                 final Layout<? extends Serializable> layout, final int bufferSize) {
276             this.pattern = pattern;
277             this.append = append;
278             this.bufferedIO = bufferedIO;
279             this.bufferSize = bufferSize;
280             this.policy = policy;
281             this.strategy = strategy;
282             this.advertiseURI = advertiseURI;
283             this.layout = layout;
284         }
285     }
286 
287     /**
288      * Factory to create a RollingFileManager.
289      */
290     private static class RollingFileManagerFactory implements ManagerFactory<RollingFileManager, FactoryData> {
291 
292         /**
293          * Create the RollingFileManager.
294          * @param name The name of the entity to manage.
295          * @param data The data required to create the entity.
296          * @return a RollingFileManager.
297          */
298         @Override
299         public RollingFileManager createManager(final String name, final FactoryData data) {
300             final File file = new File(name);
301             final File parent = file.getParentFile();
302             if (null != parent && !parent.exists()) {
303                 parent.mkdirs();
304             }
305             try {
306                 file.createNewFile();
307             } catch (final IOException ioe) {
308                 LOGGER.error("Unable to create file " + name, ioe);
309                 return null;
310             }
311             final long size = data.append ? file.length() : 0;
312 
313             OutputStream os;
314             try {
315                 os = new FileOutputStream(name, data.append);
316                 int bufferSize = data.bufferSize;
317                 if (data.bufferedIO) {
318                     os = new BufferedOutputStream(os, bufferSize);
319                 } else {
320                     bufferSize = -1; // negative buffer size signals bufferedIO was configured false
321                 }
322                 final long time = file.lastModified(); // LOG4J2-531 create file first so time has valid value
323                 return new RollingFileManager(name, data.pattern, os, data.append, size, time, data.policy,
324                     data.strategy, data.advertiseURI, data.layout, bufferSize);
325             } catch (final FileNotFoundException ex) {
326                 LOGGER.error("FileManager (" + name + ") " + ex);
327             }
328             return null;
329         }
330     }
331 
332 }