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 }