1 /**
2 * Logback: the reliable, generic, fast and flexible logging framework.
3 * Copyright (C) 1999-2009, QOS.ch. All rights reserved.
4 *
5 * This program and the accompanying materials are dual-licensed under
6 * either the terms of the Eclipse Public License v1.0 as published by
7 * the Eclipse Foundation
8 *
9 * or (per the licensee's choosing)
10 *
11 * under the terms of the GNU Lesser General Public License version 2.1
12 * as published by the Free Software Foundation.
13 */
14 package ch.qos.logback.core;
15
16 import java.io.IOException;
17 import java.io.OutputStream;
18 import java.io.OutputStreamWriter;
19 import java.io.Writer;
20
21 import ch.qos.logback.core.status.ErrorStatus;
22
23 /**
24 * WriterAppender appends events to a hava.io.Writer. This class provides basic
25 * services that other appenders build upon.
26 *
27 * For more information about this appender, please refer to the online manual
28 * at http://logback.qos.ch/manual/appenders.html#WriterAppender
29 *
30 * @author Ceki Gülcü
31 */
32 public class WriterAppender<E> extends UnsynchronizedAppenderBase<E> {
33
34 /**
35 * Immediate flush means that the underlying writer or output stream will be
36 * flushed at the end of each append operation. Immediate flush is slower but
37 * ensures that each append request is actually written. If
38 * <code>immediateFlush</code> is set to <code>false</code>, then there
39 * is a good chance that the last few logs events are not actually written to
40 * persistent media if and when the application crashes.
41 *
42 * <p>
43 * The <code>immediateFlush</code> variable is set to <code>true</code> by
44 * default.
45 */
46 private boolean immediateFlush = true;
47
48 /**
49 * The encoding to use when opening an InputStream.
50 * <p>
51 * The <code>encoding</code> variable is set to <code>null</null> by default
52 * which results in the use of the system's default encoding.
53 */
54 private String encoding;
55
56 /**
57 * This is the {@link Writer Writer} where we will write to.
58 */
59 private Writer writer;
60
61 /**
62 * The default constructor does nothing.
63 */
64 public WriterAppender() {
65 }
66
67 /**
68 * If the <b>ImmediateFlush</b> option is set to <code>true</code>, the
69 * appender will flush at the end of each write. This is the default behavior.
70 * If the option is set to <code>false</code>, then the underlying stream
71 * can defer writing to physical medium to a later time.
72 * <p>
73 * Avoiding the flush operation at the end of each append results in a
74 * performance gain of 10 to 20 percent. However, there is safety tradeoff
75 * involved in skipping flushing. Indeed, when flushing is skipped, then it is
76 * likely that the last few log events will not be recorded on disk when the
77 * application exits. This is a high price to pay even for a 20% performance
78 * gain.
79 */
80 public void setImmediateFlush(boolean value) {
81 immediateFlush = value;
82 }
83
84 /**
85 * Returns value of the <b>ImmediateFlush</b> option.
86 */
87 public boolean getImmediateFlush() {
88 return immediateFlush;
89 }
90
91 /**
92 * Checks that requires parameters are set and if everything is in order,
93 * activates this appender.
94 */
95 public void start() {
96 int errors = 0;
97 if (this.layout == null) {
98 addStatus(new ErrorStatus("No layout set for the appender named \""
99 + name + "\".", this));
100 errors++;
101 }
102
103 if (this.writer == null) {
104 addStatus(new ErrorStatus("No writer set for the appender named \""
105 + name + "\".", this));
106 errors++;
107 }
108 // only error free appenders should be activated
109 if (errors == 0) {
110 super.start();
111 }
112 }
113
114 @Override
115 protected void append(E eventObject) {
116 if (!isStarted()) {
117 return;
118 }
119
120 subAppend(eventObject);
121 }
122
123 /**
124 * Stop this appender instance. The underlying stream or writer is also
125 * closed.
126 *
127 * <p>
128 * Stopped appenders cannot be reused.
129 */
130 public synchronized void stop() {
131 closeWriter();
132 super.stop();
133 }
134
135 /**
136 * Close the underlying {@link java.io.Writer}.
137 */
138 protected void closeWriter() {
139 if (this.writer != null) {
140 try {
141 // before closing we have to output out layout's footer
142 writeFooter();
143 this.writer.close();
144 this.writer = null;
145 } catch (IOException e) {
146 addStatus(new ErrorStatus("Could not close writer for WriterAppener.",
147 this, e));
148 }
149 }
150 }
151
152 /**
153 * Returns an OutputStreamWriter when passed an OutputStream. The encoding
154 * used will depend on the value of the <code>encoding</code> property. If
155 * the encoding value is specified incorrectly the writer will be opened using
156 * the default system encoding (an error message will be printed to the
157 * loglog.
158 */
159 protected OutputStreamWriter createWriter(OutputStream os) {
160 OutputStreamWriter retval = null;
161
162 String enc = getEncoding();
163
164 try {
165 if (enc != null) {
166 retval = new OutputStreamWriter(os, enc);
167 } else {
168 retval = new OutputStreamWriter(os);
169 }
170 } catch (IOException e) {
171 addStatus(new ErrorStatus("Error initializing output writer.", this, e));
172 if (enc != null) {
173 addStatus(new ErrorStatus("Unsupported encoding?", this));
174 }
175 }
176 return retval;
177 }
178
179 public String getEncoding() {
180 return encoding;
181 }
182
183 public void setEncoding(String value) {
184 encoding = value;
185 }
186
187 void writeHeader() {
188 if (layout != null && (this.writer != null)) {
189 try {
190 StringBuilder sb = new StringBuilder();
191 appendIfNotNull(sb, layout.getFileHeader());
192 appendIfNotNull(sb, layout.getPresentationHeader());
193 if (sb.length() > 0) {
194 sb.append(CoreConstants.LINE_SEPARATOR);
195 // If at least one of file header or presentation header were not
196 // null, then append a line separator.
197 // This should be useful in most cases and should not hurt.
198 writerWrite(sb.toString(), true);
199 }
200
201 } catch (IOException ioe) {
202 this.started = false;
203 addStatus(new ErrorStatus("Failed to write header for appender named ["
204 + name + "].", this, ioe));
205 }
206 }
207 }
208
209 private void appendIfNotNull(StringBuilder sb, String s) {
210 if (s != null) {
211 sb.append(s);
212 }
213 }
214
215 void writeFooter() {
216 if (layout != null && this.writer != null) {
217 try {
218 StringBuilder sb = new StringBuilder();
219 appendIfNotNull(sb, layout.getPresentationFooter());
220 appendIfNotNull(sb, layout.getFileFooter());
221 if (sb.length() > 0) {
222 writerWrite(sb.toString(), true); // force flush
223 }
224 } catch (IOException ioe) {
225 this.started = false;
226 addStatus(new ErrorStatus("Failed to write footer for appender named ["
227 + name + "].", this, ioe));
228 }
229 }
230 }
231
232 /**
233 * <p>
234 * Sets the Writer where the log output will go. The specified Writer must be
235 * opened by the user and be writable. The <code>java.io.Writer</code> will
236 * be closed when the appender instance is closed.
237 *
238 * @param writer
239 * An already opened Writer.
240 */
241 public synchronized void setWriter(Writer writer) {
242 // close any previously opened writer
243 closeWriter();
244
245 this.writer = writer;
246 writeHeader();
247 }
248
249 protected void writerWrite(String s, boolean flush) throws IOException {
250 this.writer.write(s);
251 if (flush) {
252 this.writer.flush();
253 }
254 }
255
256 /**
257 * Actual writing occurs here.
258 * <p>
259 * Most subclasses of <code>WriterAppender</code> will need to override this
260 * method.
261 *
262 * @since 0.9.0
263 */
264 protected void subAppend(E event) {
265 if (!isStarted()) {
266 return;
267 }
268
269 try {
270 String output = this.layout.doLayout(event);
271 synchronized (this) {
272 writerWrite(output, this.immediateFlush);
273 }
274 } catch (IOException ioe) {
275 // as soon as an exception occurs, move to non-started state
276 // and add a single ErrorStatus to the SM.
277 this.started = false;
278 addStatus(new ErrorStatus("IO failure in appender", this, ioe));
279 }
280 }
281 }