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  
18  package org.apache.log4j.rolling;
19  
20  import org.apache.log4j.Appender;
21  import org.apache.log4j.FileAppender;
22  import org.apache.log4j.Logger;
23  import org.apache.log4j.helpers.LogLog;
24  import org.apache.log4j.helpers.QuietWriter;
25  import org.apache.log4j.rolling.helper.Action;
26  import org.apache.log4j.spi.ErrorHandler;
27  import org.apache.log4j.spi.LoggingEvent;
28  import org.apache.log4j.spi.OptionHandler;
29  import org.apache.log4j.xml.UnrecognizedElementHandler;
30  import org.w3c.dom.Element;
31  
32  import java.io.File;
33  import java.io.FileOutputStream;
34  import java.io.IOException;
35  import java.io.OutputStream;
36  import java.io.OutputStreamWriter;
37  import java.io.Writer;
38  import java.util.Properties;
39  
40  
41  /***
42   * <code>RollingFileAppender</code> extends {@link FileAppender} to backup the log files
43   * depending on {@link RollingPolicy} and {@link TriggeringPolicy}.
44   * <p>
45   * To be of any use, a <code>RollingFileAppender</code> instance must have both
46   * a <code>RollingPolicy</code> and a <code>TriggeringPolicy</code> set up.
47   * However, if its <code>RollingPolicy</code> also implements the
48   * <code>TriggeringPolicy</code> interface, then only the former needs to be
49   * set up. For example, {@link TimeBasedRollingPolicy} acts both as a
50   * <code>RollingPolicy</code> and a <code>TriggeringPolicy</code>.
51   *
52   * <p><code>RollingFileAppender</code> can be configured programattically or
53   * using {@link org.apache.log4j.extras.DOMConfigurator} or
54   * {@link org.apache.log4j.xml.DOMConfigurator} in log4j 1.2.15 or later. Here is a sample
55   * configration file:
56  
57  <pre>&lt;?xml version="1.0" encoding="UTF-8" ?>
58  &lt;!DOCTYPE log4j:configuration>
59  
60  &lt;log4j:configuration debug="true">
61  
62    &lt;appender name="ROLL" class="org.apache.log4j.rolling.RollingFileAppender">
63      <b>&lt;rollingPolicy class="org.apache.log4j.rolling.TimeBasedRollingPolicy">
64        &lt;param name="FileNamePattern" value="/wombat/foo.%d{yyyy-MM}.gz"/>
65      &lt;/rollingPolicy></b>
66  
67      &lt;layout class="org.apache.log4j.PatternLayout">
68        &lt;param name="ConversionPattern" value="%c{1} - %m%n"/>
69      &lt;/layout>
70    &lt;/appender>
71  
72    &lt;root">
73      &lt;appender-ref ref="ROLL"/>
74    &lt;/root>
75  
76  &lt;/log4j:configuration>
77  </pre>
78  
79   *<p>This configuration file specifies a monthly rollover schedule including
80   * automatic compression of the archived files. See
81   * {@link TimeBasedRollingPolicy} for more details.
82   *
83   * @author Heinz Richter
84   * @author Ceki G&uuml;lc&uuml;
85   * @since  1.3
86   * */
87  public final class RollingFileAppender extends FileAppender
88          implements UnrecognizedElementHandler {
89    /***
90     * Triggering policy.
91     */
92    private TriggeringPolicy triggeringPolicy;
93  
94    /***
95     * Rolling policy.
96     */
97    private RollingPolicy rollingPolicy;
98  
99    /***
100    * Length of current active log file.
101    */
102   private long fileLength = 0;
103 
104   /***
105    * Asynchronous action (like compression) from previous rollover.
106    */
107   private Action lastRolloverAsyncAction = null;
108 
109 
110   /***
111    * Construct a new instance.
112    */
113   public RollingFileAppender() {
114   }
115 
116   /***
117    * Prepare instance of use.
118    */
119   public void activateOptions() {
120     if (rollingPolicy == null) {
121       LogLog.warn(
122         "Please set a rolling policy for the RollingFileAppender named '"
123                 + getName() + "'");
124 
125         return;
126     }
127 
128     //
129     //  if no explicit triggering policy and rolling policy is both.
130     //
131     if (
132       (triggeringPolicy == null) && rollingPolicy instanceof TriggeringPolicy) {
133       triggeringPolicy = (TriggeringPolicy) rollingPolicy;
134     }
135 
136     if (triggeringPolicy == null) {
137       LogLog.warn(
138         "Please set a TriggeringPolicy for the RollingFileAppender named '"
139                 + getName() + "'");
140 
141       return;
142     }
143 
144     Exception exception = null;
145 
146     synchronized (this) {
147       triggeringPolicy.activateOptions();
148       rollingPolicy.activateOptions();
149 
150       try {
151         RolloverDescription rollover =
152           rollingPolicy.initialize(getFile(), getAppend());
153 
154         if (rollover != null) {
155           Action syncAction = rollover.getSynchronous();
156 
157           if (syncAction != null) {
158             syncAction.execute();
159           }
160 
161           setFile(rollover.getActiveFileName());
162           setAppend(rollover.getAppend());
163           lastRolloverAsyncAction = rollover.getAsynchronous();
164 
165           if (lastRolloverAsyncAction != null) {
166             Thread runner = new Thread(lastRolloverAsyncAction);
167             runner.start();
168           }
169         }
170 
171         File activeFile = new File(getFile());
172 
173         if (getAppend()) {
174           fileLength = activeFile.length();
175         } else {
176           fileLength = 0;
177         }
178 
179         super.activateOptions();
180       } catch (Exception ex) {
181         exception = ex;
182       }
183     }
184 
185     if (exception != null) {
186       LogLog.warn(
187         "Exception while initializing RollingFileAppender named '" + getName()
188         + "'", exception);
189     }
190   }
191 
192 
193     private QuietWriter createQuietWriter(final Writer writer) {
194          ErrorHandler handler = errorHandler;
195          if (handler == null) {
196              handler = new DefaultErrorHandler(this);
197          }
198          return new QuietWriter(writer, handler);
199      }
200 
201 
202   /***
203      Implements the usual roll over behaviour.
204 
205      <p>If <code>MaxBackupIndex</code> is positive, then files
206      {<code>File.1</code>, ..., <code>File.MaxBackupIndex -1</code>}
207      are renamed to {<code>File.2</code>, ...,
208      <code>File.MaxBackupIndex</code>}. Moreover, <code>File</code> is
209      renamed <code>File.1</code> and closed. A new <code>File</code> is
210      created to receive further log output.
211 
212      <p>If <code>MaxBackupIndex</code> is equal to zero, then the
213      <code>File</code> is truncated with no backup files created.
214 
215    * @return true if rollover performed.
216    */
217   public boolean rollover() {
218     //
219     //   can't roll without a policy
220     //
221     if (rollingPolicy != null) {
222       Exception exception = null;
223 
224       synchronized (this) {
225         //
226         //   if a previous async task is still running
227         //}
228         if (lastRolloverAsyncAction != null) {
229           //
230           //  block until complete
231           //
232           lastRolloverAsyncAction.close();
233 
234           //
235           //    or don't block and return to rollover later
236           //
237           //if (!lastRolloverAsyncAction.isComplete()) return false;
238         }
239 
240         try {
241           RolloverDescription rollover = rollingPolicy.rollover(getFile());
242 
243           if (rollover != null) {
244             if (rollover.getActiveFileName().equals(getFile())) {
245               closeWriter();
246 
247               boolean success = true;
248 
249               if (rollover.getSynchronous() != null) {
250                 success = false;
251 
252                 try {
253                   success = rollover.getSynchronous().execute();
254                 } catch (Exception ex) {
255                   exception = ex;
256                 }
257               }
258 
259               if (success) {
260                 if (rollover.getAppend()) {
261                   fileLength = new File(rollover.getActiveFileName()).length();
262                 } else {
263                   fileLength = 0;
264                 }
265 
266                 if (rollover.getAsynchronous() != null) {
267                   lastRolloverAsyncAction = rollover.getAsynchronous();
268                   new Thread(lastRolloverAsyncAction).start();
269                 }
270 
271                 setFile(
272                   rollover.getActiveFileName(), rollover.getAppend(),
273                   bufferedIO, bufferSize);
274               } else {
275                 setFile(
276                   rollover.getActiveFileName(), true, bufferedIO, bufferSize);
277 
278                 if (exception == null) {
279                   LogLog.warn("Failure in post-close rollover action");
280                 } else {
281                   LogLog.warn(
282                     "Exception in post-close rollover action", exception);
283                 }
284               }
285             } else {
286               Writer newWriter =
287                 createWriter(
288                   new FileOutputStream(
289                     rollover.getActiveFileName(), rollover.getAppend()));
290               closeWriter();
291               setFile(rollover.getActiveFileName());
292               this.qw = createQuietWriter(newWriter);
293 
294               boolean success = true;
295 
296               if (rollover.getSynchronous() != null) {
297                 success = false;
298 
299                 try {
300                   success = rollover.getSynchronous().execute();
301                 } catch (Exception ex) {
302                   exception = ex;
303                 }
304               }
305 
306               if (success) {
307                 if (rollover.getAppend()) {
308                   fileLength = new File(rollover.getActiveFileName()).length();
309                 } else {
310                   fileLength = 0;
311                 }
312 
313                 if (rollover.getAsynchronous() != null) {
314                   lastRolloverAsyncAction = rollover.getAsynchronous();
315                   new Thread(lastRolloverAsyncAction).start();
316                 }
317               }
318 
319               writeHeader();
320             }
321 
322             return true;
323           }
324         } catch (Exception ex) {
325           exception = ex;
326         }
327       }
328 
329       if (exception != null) {
330         LogLog.warn(
331           "Exception during rollover, rollover deferred.", exception);
332       }
333     }
334 
335     return false;
336   }
337 
338   /***
339    * {@inheritDoc}
340   */
341   protected void subAppend(final LoggingEvent event) {
342     // The rollover check must precede actual writing. This is the 
343     // only correct behavior for time driven triggers. 
344     if (
345       triggeringPolicy.isTriggeringEvent(
346           this, event, getFile(), getFileLength())) {
347       //
348       //   wrap rollover request in try block since
349       //    rollover may fail in case read access to directory
350       //    is not provided.  However appender should still be in good
351       //     condition and the append should still happen.
352       try {
353         rollover();
354       } catch (Exception ex) {
355           LogLog.warn("Exception during rollover attempt.", ex);
356       }
357     }
358 
359     super.subAppend(event);
360   }
361 
362   /***
363    * Get rolling policy.
364    * @return rolling policy.
365    */
366   public RollingPolicy getRollingPolicy() {
367     return rollingPolicy;
368   }
369 
370   /***
371    * Get triggering policy.
372    * @return triggering policy.
373    */
374   public TriggeringPolicy getTriggeringPolicy() {
375     return triggeringPolicy;
376   }
377 
378   /***
379    * Sets the rolling policy.
380    * @param policy rolling policy.
381    */
382   public void setRollingPolicy(final RollingPolicy policy) {
383     rollingPolicy = policy;
384   }
385 
386   /***
387    * Set triggering policy.
388    * @param policy triggering policy.
389    */
390   public void setTriggeringPolicy(final TriggeringPolicy policy) {
391     triggeringPolicy = policy;
392   }
393 
394   /***
395    * Close appender.  Waits for any asynchronous file compression actions to be completed.
396    */
397   public void close() {
398     synchronized (this) {
399       if (lastRolloverAsyncAction != null) {
400         lastRolloverAsyncAction.close();
401       }
402     }
403 
404     super.close();
405   }
406 
407   /***
408      Returns an OutputStreamWriter when passed an OutputStream.  The
409      encoding used will depend on the value of the
410      <code>encoding</code> property.  If the encoding value is
411      specified incorrectly the writer will be opened using the default
412      system encoding (an error message will be printed to the loglog.
413    @param os output stream, may not be null.
414    @return new writer.
415    */
416   protected OutputStreamWriter createWriter(final OutputStream os) {
417     return super.createWriter(new CountingOutputStream(os, this));
418   }
419 
420   /***
421    * Get byte length of current active log file.
422    * @return byte length of current active log file.
423    */
424   public long getFileLength() {
425     return fileLength;
426   }
427 
428   /***
429    * Increments estimated byte length of current active log file.
430    * @param increment additional bytes written to log file.
431    */
432   public synchronized void incrementFileLength(int increment) {
433     fileLength += increment;
434   }
435 
436 
437 
438     /***
439      * {@inheritDoc}
440      */
441   public boolean parseUnrecognizedElement(final Element element,
442                                           final Properties props) throws Exception {
443       final String nodeName = element.getNodeName();
444       if ("rollingPolicy".equals(nodeName)) {
445           OptionHandler rollingPolicy =
446                   org.apache.log4j.extras.DOMConfigurator.parseElement(
447                           element, props, RollingPolicy.class);
448           if (rollingPolicy != null) {
449               rollingPolicy.activateOptions();
450               this.setRollingPolicy((RollingPolicy) rollingPolicy);
451           }
452           return true;
453       }
454       if ("triggeringPolicy".equals(nodeName)) {
455           OptionHandler triggerPolicy =
456                   org.apache.log4j.extras.DOMConfigurator.parseElement(
457                           element, props, TriggeringPolicy.class);
458           if (triggerPolicy != null) {
459               triggerPolicy.activateOptions();
460               this.setTriggeringPolicy((TriggeringPolicy) triggerPolicy);
461           }
462           return true;
463       }
464       return false;
465   }
466 
467   /***
468    * Wrapper for OutputStream that will report all write
469    * operations back to this class for file length calculations.
470    */
471   private static class CountingOutputStream extends OutputStream {
472     /***
473      * Wrapped output stream.
474      */
475     private final OutputStream os;
476 
477     /***
478      * Rolling file appender to inform of stream writes.
479      */
480     private final RollingFileAppender rfa;
481 
482     /***
483      * Constructor.
484      * @param os output stream to wrap.
485      * @param rfa rolling file appender to inform.
486      */
487     public CountingOutputStream(
488       final OutputStream os, final RollingFileAppender rfa) {
489       this.os = os;
490       this.rfa = rfa;
491     }
492 
493     /***
494      * {@inheritDoc}
495      */
496     public void close() throws IOException {
497       os.close();
498     }
499 
500     /***
501      * {@inheritDoc}
502      */
503     public void flush() throws IOException {
504       os.flush();
505     }
506 
507     /***
508      * {@inheritDoc}
509      */
510     public void write(final byte[] b) throws IOException {
511       os.write(b);
512       rfa.incrementFileLength(b.length);
513     }
514 
515     /***
516      * {@inheritDoc}
517      */
518     public void write(final byte[] b, final int off, final int len)
519       throws IOException {
520       os.write(b, off, len);
521       rfa.incrementFileLength(len);
522     }
523 
524     /***
525      * {@inheritDoc}
526      */
527     public void write(final int b) throws IOException {
528       os.write(b);
529       rfa.incrementFileLength(1);
530     }
531   }
532 
533     private static final class DefaultErrorHandler implements ErrorHandler {
534         private final RollingFileAppender appender;
535         public DefaultErrorHandler(final RollingFileAppender appender) {
536             this.appender = appender;
537         }
538         /***@since 1.2*/
539         public void setLogger(Logger logger) {
540 
541         }
542         public void error(String message, Exception ioe, int errorCode) {
543           appender.close();
544           LogLog.error("IO failure for appender named "+ appender.getName(), ioe);
545         }
546         public void error(String message) {
547 
548         }
549         /***@since 1.2*/
550         public void error(String message, Exception e, int errorCode, LoggingEvent event) {
551 
552         }
553         /***@since 1.2*/
554         public void setAppender(Appender appender) {
555 
556         }
557         /***@since 1.2*/
558         public void setBackupAppender(Appender appender) {
559 
560         }
561 
562         public void activateOptions() {
563         }
564 
565     }
566 
567 }