View Javadoc

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