View Javadoc

1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2009, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core;
15  
16  import java.io.IOException;
17  import java.io.OutputStream;
18  import java.io.OutputStreamWriter;
19  import java.io.Writer;
20  
21  import ch.qos.logback.core.status.ErrorStatus;
22  
23  /**
24   * WriterAppender appends events to a hava.io.Writer. This class provides basic
25   * services that other appenders build upon.
26   * 
27   * For more information about this appender, please refer to the online manual
28   * at http://logback.qos.ch/manual/appenders.html#WriterAppender
29   * 
30   * @author Ceki Gülcü
31   */
32  public class WriterAppender<E> extends UnsynchronizedAppenderBase<E> {
33  
34    /**
35     * Immediate flush means that the underlying writer or output stream will be
36     * flushed at the end of each append operation. Immediate flush is slower but
37     * ensures that each append request is actually written. If
38     * <code>immediateFlush</code> is set to <code>false</code>, then there
39     * is a good chance that the last few logs events are not actually written to
40     * persistent media if and when the application crashes.
41     * 
42     * <p>
43     * The <code>immediateFlush</code> variable is set to <code>true</code> by
44     * default.
45     */
46    private boolean immediateFlush = true;
47  
48    /**
49     * The encoding to use when opening an InputStream.
50     * <p>
51     * The <code>encoding</code> variable is set to <code>null</null> by default 
52     * which results in the use of the system's default encoding.
53     */
54    private String encoding;
55  
56    /**
57     * This is the {@link Writer Writer} where we will write to.
58     */
59    private Writer writer;
60  
61    /**
62     * The default constructor does nothing.
63     */
64    public WriterAppender() {
65    }
66  
67    /**
68     * If the <b>ImmediateFlush</b> option is set to <code>true</code>, the
69     * appender will flush at the end of each write. This is the default behavior.
70     * If the option is set to <code>false</code>, then the underlying stream
71     * can defer writing to physical medium to a later time.
72     * <p>
73     * Avoiding the flush operation at the end of each append results in a
74     * performance gain of 10 to 20 percent. However, there is safety tradeoff
75     * involved in skipping flushing. Indeed, when flushing is skipped, then it is
76     * likely that the last few log events will not be recorded on disk when the
77     * application exits. This is a high price to pay even for a 20% performance
78     * gain.
79     */
80    public void setImmediateFlush(boolean value) {
81      immediateFlush = value;
82    }
83  
84    /**
85     * Returns value of the <b>ImmediateFlush</b> option.
86     */
87    public boolean getImmediateFlush() {
88      return immediateFlush;
89    }
90  
91    /**
92     * Checks that requires parameters are set and if everything is in order,
93     * activates this appender.
94     */
95    public void start() {
96      int errors = 0;
97      if (this.layout == null) {
98        addStatus(new ErrorStatus("No layout set for the appender named \""
99            + name + "\".", this));
100       errors++;
101     }
102 
103     if (this.writer == null) {
104       addStatus(new ErrorStatus("No writer set for the appender named \""
105           + name + "\".", this));
106       errors++;
107     }
108     // only error free appenders should be activated
109     if (errors == 0) {
110       super.start();
111     }
112   }
113 
114   @Override
115   protected void append(E eventObject) {
116     if (!isStarted()) {
117       return;
118     }
119 
120     subAppend(eventObject);
121   }
122 
123   /**
124    * Stop this appender instance. The underlying stream or writer is also
125    * closed.
126    * 
127    * <p>
128    * Stopped appenders cannot be reused.
129    */
130   public synchronized void stop() {
131     closeWriter();
132     super.stop();
133   }
134 
135   /**
136    * Close the underlying {@link java.io.Writer}.
137    */
138   protected void closeWriter() {
139     if (this.writer != null) {
140       try {
141         // before closing we have to output out layout's footer
142         writeFooter();
143         this.writer.close();
144         this.writer = null;
145       } catch (IOException e) {
146         addStatus(new ErrorStatus("Could not close writer for WriterAppener.",
147             this, e));
148       }
149     }
150   }
151 
152   /**
153    * Returns an OutputStreamWriter when passed an OutputStream. The encoding
154    * used will depend on the value of the <code>encoding</code> property. If
155    * the encoding value is specified incorrectly the writer will be opened using
156    * the default system encoding (an error message will be printed to the
157    * loglog.
158    */
159   protected OutputStreamWriter createWriter(OutputStream os) {
160     OutputStreamWriter retval = null;
161 
162     String enc = getEncoding();
163 
164     try {
165       if (enc != null) {
166         retval = new OutputStreamWriter(os, enc);
167       } else {
168         retval = new OutputStreamWriter(os);
169       }
170     } catch (IOException e) {
171       addStatus(new ErrorStatus("Error initializing output writer.", this, e));
172       if (enc != null) {
173         addStatus(new ErrorStatus("Unsupported encoding?", this));
174       }
175     }
176     return retval;
177   }
178 
179   public String getEncoding() {
180     return encoding;
181   }
182 
183   public void setEncoding(String value) {
184     encoding = value;
185   }
186 
187   void writeHeader() {
188     if (layout != null && (this.writer != null)) {
189       try {
190         StringBuilder sb = new StringBuilder();
191         appendIfNotNull(sb, layout.getFileHeader());
192         appendIfNotNull(sb, layout.getPresentationHeader());
193         if (sb.length() > 0) {
194           sb.append(CoreConstants.LINE_SEPARATOR);
195           // If at least one of file header or presentation header were not
196           // null, then append a line separator.
197           // This should be useful in most cases and should not hurt.
198           writerWrite(sb.toString(), true);
199         }
200 
201       } catch (IOException ioe) {
202         this.started = false;
203         addStatus(new ErrorStatus("Failed to write header for appender named ["
204             + name + "].", this, ioe));
205       }
206     }
207   }
208 
209   private void appendIfNotNull(StringBuilder sb, String s) {
210     if (s != null) {
211       sb.append(s);
212     }
213   }
214 
215   void writeFooter() {
216     if (layout != null && this.writer != null) {
217       try {
218         StringBuilder sb = new StringBuilder();
219         appendIfNotNull(sb, layout.getPresentationFooter());
220         appendIfNotNull(sb, layout.getFileFooter());
221         if (sb.length() > 0) {
222           writerWrite(sb.toString(), true); // force flush
223         }
224       } catch (IOException ioe) {
225         this.started = false;
226         addStatus(new ErrorStatus("Failed to write footer for appender named ["
227             + name + "].", this, ioe));
228       }
229     }
230   }
231 
232   /**
233    * <p>
234    * Sets the Writer where the log output will go. The specified Writer must be
235    * opened by the user and be writable. The <code>java.io.Writer</code> will
236    * be closed when the appender instance is closed.
237    * 
238    * @param writer
239    *          An already opened Writer.
240    */
241   public synchronized void setWriter(Writer writer) {
242     // close any previously opened writer
243     closeWriter();
244 
245     this.writer = writer;
246     writeHeader();
247   }
248 
249   protected void writerWrite(String s, boolean flush) throws IOException {
250     this.writer.write(s);
251     if (flush) {
252       this.writer.flush();
253     }
254   }
255 
256   /**
257    * Actual writing occurs here.
258    * <p>
259    * Most subclasses of <code>WriterAppender</code> will need to override this
260    * method.
261    * 
262    * @since 0.9.0
263    */
264   protected void subAppend(E event) {
265     if (!isStarted()) {
266       return;
267     }
268 
269     try {
270       String output = this.layout.doLayout(event);
271       synchronized (this) {
272         writerWrite(output, this.immediateFlush);
273       }
274     } catch (IOException ioe) {
275       // as soon as an exception occurs, move to non-started state
276       // and add a single ErrorStatus to the SM.
277       this.started = false;
278       addStatus(new ErrorStatus("IO failure in appender", this, ioe));
279     }
280   }
281 }