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