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;
    19
    20  import java.io.IOException;
    21  import java.io.Writer;
    22  import java.io.OutputStream;
    23  import java.io.OutputStreamWriter;
    24
    25  import org.apache.log4j.spi.ErrorHandler;
    26  import org.apache.log4j.spi.LoggingEvent;
    27  import org.apache.log4j.helpers.QuietWriter;
    28  import org.apache.log4j.helpers.LogLog;
    29
    30  // Contibutors: Jens Uwe Pipka <jens.pipka@gmx.de>
    31  //              Ben Sandee
    32
    33  /***
    34     WriterAppender appends log events to a {@link java.io.Writer} or an
    35     {@link java.io.OutputStream} depending on the user's choice.
    36
    37     @author Ceki G&uuml;lc&uuml;
    38     @since 1.1 */
    39  public class WriterAppender extends AppenderSkeleton {
    40
    41
    42    /***
    43       Immediate flush means that the underlying writer or output stream
    44       will be flushed at the end of each append operation. Immediate
    45       flush is slower but ensures that each append request is actually
    46       written. If <code>immediateFlush</code> is set to
    47       <code>false</code>, then there is a good chance that the last few
    48       logs events are not actually written to persistent media if and
    49       when the application crashes.
    50
    51       <p>The <code>immediateFlush</code> variable is set to
    52       <code>true</code> by default.
    53
    54    */
    55    protected boolean immediateFlush = true;
    56
    57    /***
    58       The encoding to use when writing.  <p>The
    59       <code>encoding</code> variable is set to <code>null</null> by
    60       default which results in the utilization of the system's default
    61       encoding.  */
    62    protected String encoding;
    63
    64    /***
    65       This is the {@link QuietWriter quietWriter} where we will write
    66       to.
    67    */
    68    protected QuietWriter qw;
    69
    70
    71    /***
    72       This default constructor does nothing.  */
    73    public
    74    WriterAppender() {
    75    }
    76
    77    /***
    78       Instantiate a WriterAppender and set the output destination to a
    79       new {@link OutputStreamWriter} initialized with <code>os</code>
    80       as its {@link OutputStream}.  */
    81    public
    82    WriterAppender(Layout layout, OutputStream os) {
    83      this(layout, new OutputStreamWriter(os));
    84    }
    85
    86    /***
    87       Instantiate a WriterAppender and set the output destination to
    88       <code>writer</code>.
    89
    90       <p>The <code>writer</code> must have been previously opened by
    91       the user.  */
    92    public
    93    WriterAppender(Layout layout, Writer writer) {
    94      this.layout = layout;
    95      this.setWriter(writer);
    96    }
    97
    98    /***
    99       If the <b>ImmediateFlush</b> option is set to
    100      <code>true</code>, the appender will flush at the end of each
    101      write. This is the default behavior. If the option is set to
    102      <code>false</code>, then the underlying stream can defer writing
    103      to physical medium to a later time.
    104
    105      <p>Avoiding the flush operation at the end of each append results in
    106      a performance gain of 10 to 20 percent. However, there is safety
    107      tradeoff involved in skipping flushing. Indeed, when flushing is
    108      skipped, then it is likely that the last few log events will not
    109      be recorded on disk when the application exits. This is a high
    110      price to pay even for a 20% performance gain.
    111    */
    112   public
    113   void setImmediateFlush(boolean value) {
    114     immediateFlush = value;
    115   }
    116
    117   /***
    118      Returns value of the <b>ImmediateFlush</b> option.
    119    */
    120   public
    121   boolean getImmediateFlush() {
    122     return immediateFlush;
    123   }
    124
    125   /***
    126      Does nothing.
    127   */
    128   public
    129   void activateOptions() {
    130   }
    131
    132
    133   /***
    134      This method is called by the {@link AppenderSkeleton#doAppend}
    135      method.
    136
    137      <p>If the output stream exists and is writable then write a log
    138      statement to the output stream. Otherwise, write a single warning
    139      message to <code>System.err</code>.
    140
    141      <p>The format of the output will depend on this appender's
    142      layout.
    143
    144   */
    145   public
    146   void append(LoggingEvent event) {
    147
    148     // Reminder: the nesting of calls is:
    149     //
    150     //    doAppend()
    151     //      - check threshold
    152     //      - filter
    153     //      - append();
    154     //        - checkEntryConditions();
    155     //        - subAppend();
    156
    157     if(!checkEntryConditions()) {
    158       return;
    159     }
    160     subAppend(event);
    161    }
    162
    163   /***
    164      This method determines if there is a sense in attempting to append.
    165
    166      <p>It checks whether there is a set output target and also if
    167      there is a set layout. If these checks fail, then the boolean
    168      value <code>false</code> is returned. */
    169   protected
    170   boolean checkEntryConditions() {
    171     if(this.closed) {
    172       LogLog.warn("Not allowed to write to a closed appender.");
    173       return false;
    174     }
    175
    176     if(this.qw == null) {
    177       errorHandler.error("No output stream or file set for the appender named ["+
    178 			name+"].");
    179       return false;
    180     }
    181
    182     if(this.layout == null) {
    183       errorHandler.error("No layout set for the appender named ["+ name+"].");
    184       return false;
    185     }
    186     return true;
    187   }
    188
    189
    190   /***
    191      Close this appender instance. The underlying stream or writer is
    192      also closed.
    193
    194      <p>Closed appenders cannot be reused.
    195
    196      @see #setWriter
    197      @since 0.8.4 */
    198   public
    199   synchronized
    200   void close() {
    201     if(this.closed)
    202       return;
    203     this.closed = true;
    204     writeFooter();
    205     reset();
    206   }
    207
    208   /***
    209    * Close the underlying {@link java.io.Writer}.
    210    * */
    211   protected void closeWriter() {
    212     if(qw != null) {
    213       try {
    214 	qw.close();
    215       } catch(IOException e) {
    216 	// There is do need to invoke an error handler at this late
    217 	// stage.
    218 	LogLog.error("Could not close " + qw, e);
    219       }
    220     }
    221   }
    222
    223   /***
    224      Returns an OutputStreamWriter when passed an OutputStream.  The
    225      encoding used will depend on the value of the
    226      <code>encoding</code> property.  If the encoding value is
    227      specified incorrectly the writer will be opened using the default
    228      system encoding (an error message will be printed to the loglog.  */
    229   protected
    230   OutputStreamWriter createWriter(OutputStream os) {
    231     OutputStreamWriter retval = null;
    232
    233     String enc = getEncoding();
    234     if(enc != null) {
    235       try {
    236 	retval = new OutputStreamWriter(os, enc);
    237       } catch(IOException e) {
    238 	LogLog.warn("Error initializing output writer.");
    239 	LogLog.warn("Unsupported encoding?");
    240       }
    241     }
    242     if(retval == null) {
    243       retval = new OutputStreamWriter(os);
    244     }
    245     return retval;
    246   }
    247
    248   public String getEncoding() {
    249     return encoding;
    250   }
    251
    252   public void setEncoding(String value) {
    253     encoding = value;
    254   }
    255
    256
    257
    258
    259   /***
    260      Set the {@link ErrorHandler} for this WriterAppender and also the
    261      underlying {@link QuietWriter} if any. */
    262   public synchronized void setErrorHandler(ErrorHandler eh) {
    263     if(eh == null) {
    264       LogLog.warn("You have tried to set a null error-handler.");
    265     } else {
    266       this.errorHandler = eh;
    267       if(this.qw != null) {
    268 	this.qw.setErrorHandler(eh);
    269       }
    270     }
    271   }
    272
    273   /***
    274     <p>Sets the Writer where the log output will go. The
    275     specified Writer must be opened by the user and be
    276     writable.
    277
    278     <p>The <code>java.io.Writer</code> will be closed when the
    279     appender instance is closed.
    280
    281
    282     <p><b>WARNING:</b> Logging to an unopened Writer will fail.
    283     <p>
    284     @param writer An already opened Writer.  */
    285   public synchronized void setWriter(Writer writer) {
    286     reset();
    287     this.qw = new QuietWriter(writer, errorHandler);
    288     //this.tp = new TracerPrintWriter(qw);
    289     writeHeader();
    290   }
    291
    292
    293   /***
    294      Actual writing occurs here.
    295
    296      <p>Most subclasses of <code>WriterAppender</code> will need to
    297      override this method.
    298
    299      @since 0.9.0 */
    300   protected
    301   void subAppend(LoggingEvent event) {
    302     this.qw.write(this.layout.format(event));
    303
    304     if(layout.ignoresThrowable()) {
    305       String[] s = event.getThrowableStrRep();
    306       if (s != null) {
    307 	int len = s.length;
    308 	for(int i = 0; i < len; i++) {
    309 	  this.qw.write(s[i]);
    310 	  this.qw.write(Layout.LINE_SEP);
    311 	}
    312       }
    313     }
    314
    315     if(this.immediateFlush) {
    316       this.qw.flush();
    317     }
    318   }
    319
    320
    321
    322   /***
    323      The WriterAppender requires a layout. Hence, this method returns
    324      <code>true</code>.
    325   */
    326   public
    327   boolean requiresLayout() {
    328     return true;
    329   }
    330
    331   /***
    332      Clear internal references to the writer and other variables.
    333
    334      Subclasses can override this method for an alternate closing
    335      behavior.  */
    336   protected
    337   void reset() {
    338     closeWriter();
    339     this.qw = null;
    340     //this.tp = null;
    341   }
    342
    343
    344   /***
    345      Write a footer as produced by the embedded layout's {@link
    346      Layout#getFooter} method.  */
    347   protected
    348   void writeFooter() {
    349     if(layout != null) {
    350       String f = layout.getFooter();
    351       if(f != null && this.qw != null) {
    352 	this.qw.write(f);
    353 	this.qw.flush();
    354       }
    355     }
    356   }
    357
    358   /***
    359      Write a header as produced by the embedded layout's {@link
    360      Layout#getHeader} method.  */
    361   protected
    362   void writeHeader() {
    363     if(layout != null) {
    364       String h = layout.getHeader();
    365       if(h != null && this.qw != null)
    366 	this.qw.write(h);
    367     }
    368   }
    369 }