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 }