View Javadoc

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