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 }