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     @Override
128     public void activateOptions() {
129     }
130 
131 
132     /**
133      * This method is called by the {@link AppenderSkeleton#doAppend}
134      * method.
135      *
136      * <p>If the output stream exists and is writable then write a log
137      * statement to the output stream. Otherwise, write a single warning
138      * message to <code>System.err</code>.
139      *
140      * <p>The format of the output will depend on this appender's
141      * layout.
142      */
143     @Override
144     public void append(LoggingEvent event) {
145 
146         // Reminder: the nesting of calls is:
147         //
148         //    doAppend()
149         //      - check threshold
150         //      - filter
151         //      - append();
152         //        - checkEntryConditions();
153         //        - subAppend();
154 
155         if (!checkEntryConditions()) {
156             return;
157         }
158         subAppend(event);
159     }
160 
161     /**
162      * This method determines if there is a sense in attempting to append.
163      *
164      * <p>It checks whether there is a set output target and also if
165      * there is a set layout. If these checks fail, then the boolean
166      * value <code>false</code> is returned.
167      * @return true if appending is allowed, false otherwise.
168      */
169     protected boolean checkEntryConditions() {
170         if (this.closed) {
171             LOGGER.warn("Not allowed to write to a closed appender.");
172             return false;
173         }
174 
175         if (this.qw == null) {
176             errorHandler.error("No output stream or file set for the appender named [" + name + "].");
177             return false;
178         }
179 
180         if (this.layout == null) {
181             errorHandler.error("No layout set for the appender named [" + name + "].");
182             return false;
183         }
184         return true;
185     }
186 
187 
188     /**
189      * Close this appender instance. The underlying stream or writer is
190      * also closed.
191      *
192      * <p>Closed appenders cannot be reused.
193      *
194      * @see #setWriter
195      * @since 0.8.4
196      */
197     @Override
198     public synchronized void close() {
199         if (this.closed) {
200             return;
201         }
202         this.closed = true;
203         writeFooter();
204         reset();
205     }
206 
207     /**
208      * Close the underlying {@link Writer}.
209      */
210     protected void closeWriter() {
211         if (qw != null) {
212             try {
213                 qw.close();
214             } catch (IOException e) {
215                 if (e instanceof InterruptedIOException) {
216                     Thread.currentThread().interrupt();
217                 }
218                 // There is do need to invoke an error handler at this late
219                 // stage.
220                 LOGGER.error("Could not close " + qw, e);
221             }
222         }
223     }
224 
225     /**
226      * Returns an OutputStreamWriter when passed an OutputStream.  The
227      * encoding used will depend on the value of the
228      * <code>encoding</code> property.  If the encoding value is
229      * specified incorrectly the writer will be opened using the default
230      * system encoding (an error message will be printed to the LOGGER.
231      * @param os The OutputStream.
232      * @return The OutputStreamWriter.
233      */
234     protected OutputStreamWriter createWriter(OutputStream os) {
235         OutputStreamWriter retval = null;
236 
237         String enc = getEncoding();
238         if (enc != null) {
239             try {
240                 retval = new OutputStreamWriter(os, enc);
241             } catch (IOException e) {
242                 if (e instanceof InterruptedIOException) {
243                     Thread.currentThread().interrupt();
244                 }
245                 LOGGER.warn("Error initializing output writer.");
246                 LOGGER.warn("Unsupported encoding?");
247             }
248         }
249         if (retval == null) {
250             retval = new OutputStreamWriter(os);
251         }
252         return retval;
253     }
254 
255     public String getEncoding() {
256         return encoding;
257     }
258 
259     public void setEncoding(String value) {
260         encoding = value;
261     }
262 
263 
264     /**
265      * Set the {@link ErrorHandler} for this WriterAppender and also the
266      * underlying {@link QuietWriter} if any.
267      */
268     @Override
269     public synchronized void setErrorHandler(ErrorHandler eh) {
270         if (eh == null) {
271             LOGGER.warn("You have tried to set a null error-handler.");
272         } else {
273             this.errorHandler = eh;
274             if (this.qw != null) {
275                 this.qw.setErrorHandler(eh);
276             }
277         }
278     }
279 
280     /**
281      * <p>Sets the Writer where the log output will go. The
282      * specified Writer must be opened by the user and be
283      * writable.
284      *
285      * <p>The <code>java.io.Writer</code> will be closed when the
286      * appender instance is closed.
287      *
288      *
289      * <p><b>WARNING:</b> Logging to an unopened Writer will fail.
290      * <p>
291      *
292      * @param writer An already opened Writer.
293      */
294     public synchronized void setWriter(Writer writer) {
295         reset();
296         this.qw = new QuietWriter(writer, errorHandler);
297         //this.tp = new TracerPrintWriter(qw);
298         writeHeader();
299     }
300 
301 
302     /**
303      * Actual writing occurs here.
304      *
305      * <p>Most subclasses of <code>WriterAppender</code> will need to
306      * override this method.
307      * @param event The event to log.
308      *
309      * @since 0.9.0
310      */
311     protected void subAppend(LoggingEvent event) {
312         this.qw.write(this.layout.format(event));
313 
314         if (layout.ignoresThrowable()) {
315             String[] s = event.getThrowableStrRep();
316             if (s != null) {
317                 int len = s.length;
318                 for (int i = 0; i < len; i++) {
319                     this.qw.write(s[i]);
320                     this.qw.write(Layout.LINE_SEP);
321                 }
322             }
323         }
324 
325         if (shouldFlush(event)) {
326             this.qw.flush();
327         }
328     }
329 
330 
331     /**
332      * The WriterAppender requires a layout. Hence, this method returns
333      * <code>true</code>.
334      */
335     @Override
336     public boolean requiresLayout() {
337         return true;
338     }
339 
340     /**
341      * Clear internal references to the writer and other variables.
342      * <p>
343      * Subclasses can override this method for an alternate closing
344      * behavior.
345      */
346     protected void reset() {
347         closeWriter();
348         this.qw = null;
349         //this.tp = null;
350     }
351 
352 
353     /**
354      * Write a footer as produced by the embedded layout's {@link
355      * Layout#getFooter} method.
356      */
357     protected void writeFooter() {
358         if (layout != null) {
359             String f = layout.getFooter();
360             if (f != null && this.qw != null) {
361                 this.qw.write(f);
362                 this.qw.flush();
363             }
364         }
365     }
366 
367     /**
368      * Write a header as produced by the embedded layout's {@link
369      * Layout#getHeader} method.
370      */
371     protected void writeHeader() {
372         if (layout != null) {
373             String h = layout.getHeader();
374             if (h != null && this.qw != null) {
375                 this.qw.write(h);
376             }
377         }
378     }
379 
380     /**
381      * Determines whether the writer should be flushed after
382      * this event is written.
383      * @param event The event to log.
384      * @return true if the writer should be flushed.
385      *
386      * @since 1.2.16
387      */
388     protected boolean shouldFlush(final LoggingEvent event) {
389         return immediateFlush;
390     }
391 }