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 org.apache.log4j.helpers.QuietWriter;
21  import org.apache.log4j.spi.ErrorHandler;
22  import org.apache.log4j.spi.LoggingEvent;
23  import org.apache.logging.log4j.status.StatusLogger;
24  
25  import java.io.IOException;
26  import java.io.InterruptedIOException;
27  import java.io.OutputStream;
28  import java.io.OutputStreamWriter;
29  import java.io.Writer;
30  
31  
32  /**
33   * WriterAppender appends log events to a {@link Writer} or an
34   * {@link OutputStream} depending on the user's choice.
35   */
36  public class WriterAppender extends AppenderSkeleton {
37      private static final org.apache.logging.log4j.Logger LOGGER = StatusLogger.getLogger();
38  
39      /**
40       * Immediate flush means that the underlying writer or output stream
41       * will be flushed at the end of each append operation unless shouldFlush()
42       * is overridden. Immediate
43       * flush is slower but ensures that each append request is actually
44       * written. If <code>immediateFlush</code> is set to
45       * <code>false</code>, then there is a good chance that the last few
46       * logs events are not actually written to persistent media if and
47       * when the application crashes.
48       *
49       * <p>The <code>immediateFlush</code> variable is set to
50       * <code>true</code> by default.
51       */
52      protected boolean immediateFlush = true;
53  
54      /**
55       * The encoding to use when writing.  <p>The
56       * <code>encoding</code> variable is set to <code>null</null> by
57       * default which results in the utilization of the system's default
58       * encoding.
59       */
60      protected String encoding;
61  
62      /**
63       * This is the {@link QuietWriter quietWriter} where we will write
64       * to.
65       */
66      protected QuietWriter qw;
67  
68  
69      /**
70       * This default constructor does nothing.
71       */
72      public WriterAppender() {
73      }
74  
75      /**
76       * Instantiate a WriterAppender and set the output destination to a
77       * new {@link OutputStreamWriter} initialized with <code>os</code>
78       * as its {@link OutputStream}.
79       */
80      public WriterAppender(Layout layout, OutputStream os) {
81          this(layout, new OutputStreamWriter(os));
82      }
83  
84      /**
85       * Instantiate a WriterAppender and set the output destination to
86       * <code>writer</code>.
87       *
88       * <p>The <code>writer</code> must have been previously opened by
89       * the user.
90       */
91      public WriterAppender(Layout layout, Writer writer) {
92          this.layout = layout;
93          this.setWriter(writer);
94      }
95  
96      /**
97       * Returns value of the <b>ImmediateFlush</b> option.
98       * @return the value of the immediate flush setting.
99       */
100     public boolean getImmediateFlush() {
101         return immediateFlush;
102     }
103 
104     /**
105      * If the <b>ImmediateFlush</b> option is set to
106      * <code>true</code>, the appender will flush at the end of each
107      * write. This is the default behavior. If the option is set to
108      * <code>false</code>, then the underlying stream can defer writing
109      * to physical medium to a later time.
110      *
111      * <p>Avoiding the flush operation at the end of each append results in
112      * a performance gain of 10 to 20 percent. However, there is safety
113      * tradeoff involved in skipping flushing. Indeed, when flushing is
114      * skipped, then it is likely that the last few log events will not
115      * be recorded on disk when the application exits. This is a high
116      * price to pay even for a 20% performance gain.
117      *
118      * @param value the value to set the immediate flush setting to.
119      */
120     public void setImmediateFlush(boolean value) {
121         immediateFlush = value;
122     }
123 
124     /**
125      * Does nothing.
126      */
127     public void activateOptions() {
128     }
129 
130 
131     /**
132      * This method is called by the {@link AppenderSkeleton#doAppend}
133      * method.
134      *
135      * <p>If the output stream exists and is writable then write a log
136      * statement to the output stream. Otherwise, write a single warning
137      * message to <code>System.err</code>.
138      *
139      * <p>The format of the output will depend on this appender's
140      * layout.
141      */
142     public void append(LoggingEvent event) {
143 
144         // Reminder: the nesting of calls is:
145         //
146         //    doAppend()
147         //      - check threshold
148         //      - filter
149         //      - append();
150         //        - checkEntryConditions();
151         //        - subAppend();
152 
153         if (!checkEntryConditions()) {
154             return;
155         }
156         subAppend(event);
157     }
158 
159     /**
160      * This method determines if there is a sense in attempting to append.
161      *
162      * <p>It checks whether there is a set output target and also if
163      * there is a set layout. If these checks fail, then the boolean
164      * value <code>false</code> is returned.
165      * @return true if appending is allowed, false otherwise.
166      */
167     protected boolean checkEntryConditions() {
168         if (this.closed) {
169             LOGGER.warn("Not allowed to write to a closed appender.");
170             return false;
171         }
172 
173         if (this.qw == null) {
174             errorHandler.error("No output stream or file set for the appender named [" + name + "].");
175             return false;
176         }
177 
178         if (this.layout == null) {
179             errorHandler.error("No layout set for the appender named [" + name + "].");
180             return false;
181         }
182         return true;
183     }
184 
185 
186     /**
187      * Close this appender instance. The underlying stream or writer is
188      * also closed.
189      *
190      * <p>Closed appenders cannot be reused.
191      *
192      * @see #setWriter
193      * @since 0.8.4
194      */
195     public
196     synchronized void close() {
197         if (this.closed) {
198             return;
199         }
200         this.closed = true;
201         writeFooter();
202         reset();
203     }
204 
205     /**
206      * Close the underlying {@link Writer}.
207      */
208     protected void closeWriter() {
209         if (qw != null) {
210             try {
211                 qw.close();
212             } catch (IOException e) {
213                 if (e instanceof InterruptedIOException) {
214                     Thread.currentThread().interrupt();
215                 }
216                 // There is do need to invoke an error handler at this late
217                 // stage.
218                 LOGGER.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 LOGGER.
229      * @param os The OutputStream.
230      * @return The OutputStreamWriter.
231      */
232     protected OutputStreamWriter createWriter(OutputStream os) {
233         OutputStreamWriter retval = null;
234 
235         String enc = getEncoding();
236         if (enc != null) {
237             try {
238                 retval = new OutputStreamWriter(os, enc);
239             } catch (IOException e) {
240                 if (e instanceof InterruptedIOException) {
241                     Thread.currentThread().interrupt();
242                 }
243                 LOGGER.warn("Error initializing output writer.");
244                 LOGGER.warn("Unsupported encoding?");
245             }
246         }
247         if (retval == null) {
248             retval = new OutputStreamWriter(os);
249         }
250         return retval;
251     }
252 
253     public String getEncoding() {
254         return encoding;
255     }
256 
257     public void setEncoding(String value) {
258         encoding = value;
259     }
260 
261 
262     /**
263      * Set the {@link ErrorHandler} for this WriterAppender and also the
264      * underlying {@link QuietWriter} if any.
265      */
266     public synchronized void setErrorHandler(ErrorHandler eh) {
267         if (eh == null) {
268             LOGGER.warn("You have tried to set a null error-handler.");
269         } else {
270             this.errorHandler = eh;
271             if (this.qw != null) {
272                 this.qw.setErrorHandler(eh);
273             }
274         }
275     }
276 
277     /**
278      * <p>Sets the Writer where the log output will go. The
279      * specified Writer must be opened by the user and be
280      * writable.
281      *
282      * <p>The <code>java.io.Writer</code> will be closed when the
283      * appender instance is closed.
284      *
285      *
286      * <p><b>WARNING:</b> Logging to an unopened Writer will fail.
287      * <p>
288      *
289      * @param writer An already opened Writer.
290      */
291     public synchronized void setWriter(Writer writer) {
292         reset();
293         this.qw = new QuietWriter(writer, errorHandler);
294         //this.tp = new TracerPrintWriter(qw);
295         writeHeader();
296     }
297 
298 
299     /**
300      * Actual writing occurs here.
301      *
302      * <p>Most subclasses of <code>WriterAppender</code> will need to
303      * override this method.
304      * @param event The event to log.
305      *
306      * @since 0.9.0
307      */
308     protected void subAppend(LoggingEvent event) {
309         this.qw.write(this.layout.format(event));
310 
311         if (layout.ignoresThrowable()) {
312             String[] s = event.getThrowableStrRep();
313             if (s != null) {
314                 int len = s.length;
315                 for (int i = 0; i < len; i++) {
316                     this.qw.write(s[i]);
317                     this.qw.write(Layout.LINE_SEP);
318                 }
319             }
320         }
321 
322         if (shouldFlush(event)) {
323             this.qw.flush();
324         }
325     }
326 
327 
328     /**
329      * The WriterAppender requires a layout. Hence, this method returns
330      * <code>true</code>.
331      */
332     public boolean requiresLayout() {
333         return true;
334     }
335 
336     /**
337      * Clear internal references to the writer and other variables.
338      * <p>
339      * Subclasses can override this method for an alternate closing
340      * behavior.
341      */
342     protected void reset() {
343         closeWriter();
344         this.qw = null;
345         //this.tp = null;
346     }
347 
348 
349     /**
350      * Write a footer as produced by the embedded layout's {@link
351      * Layout#getFooter} method.
352      */
353     protected void writeFooter() {
354         if (layout != null) {
355             String f = layout.getFooter();
356             if (f != null && this.qw != null) {
357                 this.qw.write(f);
358                 this.qw.flush();
359             }
360         }
361     }
362 
363     /**
364      * Write a header as produced by the embedded layout's {@link
365      * Layout#getHeader} method.
366      */
367     protected void writeHeader() {
368         if (layout != null) {
369             String h = layout.getHeader();
370             if (h != null && this.qw != null) {
371                 this.qw.write(h);
372             }
373         }
374     }
375 
376     /**
377      * Determines whether the writer should be flushed after
378      * this event is written.
379      * @param event The event to log.
380      * @return true if the writer should be flushed.
381      *
382      * @since 1.2.16
383      */
384     protected boolean shouldFlush(final LoggingEvent event) {
385         return immediateFlush;
386     }
387 }