001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 * 
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 * 
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.log4j;
019
020import java.io.IOException;
021import java.io.InterruptedIOException;
022import java.io.OutputStream;
023import java.io.OutputStreamWriter;
024import java.io.Writer;
025
026import org.apache.log4j.helpers.LogLog;
027import org.apache.log4j.helpers.QuietWriter;
028import org.apache.log4j.spi.ErrorHandler;
029import org.apache.log4j.spi.LoggingEvent;
030
031// Contibutors: Jens Uwe Pipka <jens.pipka@gmx.de>
032//              Ben Sandee
033
034/**
035   WriterAppender appends log events to a {@link java.io.Writer} or an
036   {@link java.io.OutputStream} depending on the user's choice.
037
038   @author Ceki G&uuml;lc&uuml;
039   @since 1.1 */
040public class WriterAppender extends AppenderSkeleton {
041
042
043  /**
044     Immediate flush means that the underlying writer or output stream
045     will be flushed at the end of each append operation unless shouldFlush()
046     is overridden. Immediate
047     flush is slower but ensures that each append request is actually
048     written. If <code>immediateFlush</code> is set to
049     <code>false</code>, then there is a good chance that the last few
050     logs events are not actually written to persistent media if and
051     when the application crashes.
052
053     <p>The <code>immediateFlush</code> variable is set to
054     <code>true</code> by default.
055
056  */
057  protected boolean immediateFlush = true;
058
059  /**
060     The encoding to use when writing.  <p>The
061     <code>encoding</code> variable is set to <code>null</null> by
062     default which results in the utilization of the system's default
063     encoding.  */
064  protected String encoding;
065
066  /**
067     This is the {@link QuietWriter quietWriter} where we will write
068     to.
069  */
070  protected QuietWriter qw;
071
072
073  /**
074     This default constructor does nothing.  */
075  public
076  WriterAppender() {
077  }
078
079  /**
080     Instantiate a WriterAppender and set the output destination to a
081     new {@link OutputStreamWriter} initialized with <code>os</code>
082     as its {@link OutputStream}.  */
083  public
084  WriterAppender(Layout layout, OutputStream os) {
085    this(layout, new OutputStreamWriter(os));
086  }
087
088  /**
089     Instantiate a WriterAppender and set the output destination to
090     <code>writer</code>.
091
092     <p>The <code>writer</code> must have been previously opened by
093     the user.  */
094  public
095  WriterAppender(Layout layout, Writer writer) {
096    this.layout = layout;
097    this.setWriter(writer);
098  }
099
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}