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ülcü
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 }