1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
31
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ülcü
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
149
150
151
152
153
154
155
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
217
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
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
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 }