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