1 /**
2  * Logback: the reliable, generic, fast and flexible logging framework.
3  * Copyright (C) 1999-2015, 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 static ch.qos.logback.core.CoreConstants.CODES_URL;
17
18 import java.io.IOException;
19 import java.io.OutputStream;
20 import java.util.concurrent.locks.ReentrantLock;
21
22 import ch.qos.logback.core.encoder.Encoder;
23 import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
24 import ch.qos.logback.core.spi.DeferredProcessingAware;
25 import ch.qos.logback.core.status.ErrorStatus;
26
27 /**
28  * OutputStreamAppender appends events to a {@link OutputStream}. This class
29  * provides basic services that other appenders build upon.
30  * 
31  * For more information about this appender, please refer to the online manual
32  * at http://logback.qos.ch/manual/appenders.html#OutputStreamAppender
33  * 
34  * @author Ceki Gülcü
35  */

36 public class OutputStreamAppender<E> extends UnsynchronizedAppenderBase<E> {
37
38     /**
39      * It is the encoder which is ultimately responsible for writing the event to
40      * an {@link OutputStream}.
41      */

42     protected Encoder<E> encoder;
43
44     /**
45      * All synchronization in this class is done via the lock object.
46      */

47     protected final ReentrantLock lock = new ReentrantLock(false);
48
49     /**
50      * This is the {@link OutputStream outputStream} where output will be written.
51      */

52     private OutputStream outputStream;
53
54     boolean immediateFlush = true;
55
56     /**
57     * The underlying output stream used by this appender.
58     * 
59     * @return
60     */

61     public OutputStream getOutputStream() {
62         return outputStream;
63     }
64
65     /**
66      * Checks that requires parameters are set and if everything is in order,
67      * activates this appender.
68      */

69     public void start() {
70         int errors = 0;
71         if (this.encoder == null) {
72             addStatus(new ErrorStatus("No encoder set for the appender named \"" + name + "\"."this));
73             errors++;
74         }
75
76         if (this.outputStream == null) {
77             addStatus(new ErrorStatus("No output stream set for the appender named \"" + name + "\"."this));
78             errors++;
79         }
80         // only error free appenders should be activated
81         if (errors == 0) {
82             super.start();
83         }
84     }
85
86     public void setLayout(Layout<E> layout) {
87         addWarn("This appender no longer admits a layout as a sub-component, set an encoder instead.");
88         addWarn("To ensure compatibility, wrapping your layout in LayoutWrappingEncoder.");
89         addWarn("See also " + CODES_URL + "#layoutInsteadOfEncoder for details");
90         LayoutWrappingEncoder<E> lwe = new LayoutWrappingEncoder<E>();
91         lwe.setLayout(layout);
92         lwe.setContext(context);
93         this.encoder = lwe;
94     }
95
96     @Override
97     protected void append(E eventObject) {
98         if (!isStarted()) {
99             return;
100         }
101
102         subAppend(eventObject);
103     }
104
105     /**
106      * Stop this appender instance. The underlying stream or writer is also
107      * closed.
108      * 
109      * <p>
110      * Stopped appenders cannot be reused.
111      */

112     public void stop() {
113         lock.lock();
114         try {
115             closeOutputStream();
116             super.stop();
117         } finally {
118             lock.unlock();
119         }
120     }
121
122     /**
123      * Close the underlying {@link OutputStream}.
124      */

125     protected void closeOutputStream() {
126         if (this.outputStream != null) {
127             try {
128                 // before closing we have to output out layout's footer
129                 encoderClose();
130                 this.outputStream.close();
131                 this.outputStream = null;
132             } catch (IOException e) {
133                 addStatus(new ErrorStatus("Could not close output stream for OutputStreamAppender."this, e));
134             }
135         }
136     }
137
138     void encoderClose() {
139         if (encoder != null && this.outputStream != null) {
140             try {
141                 byte[] footer = encoder.footerBytes();
142                 writeBytes(footer);
143             } catch (IOException ioe) {
144                 this.started = false;
145                 addStatus(new ErrorStatus("Failed to write footer for appender named [" + name + "]."this, ioe));
146             }
147         }
148     }
149
150     /**
151      * <p>
152      * Sets the @link OutputStream} where the log output will go. The specified
153      * <code>OutputStream</code> must be opened by the user and be writable. The
154      * <code>OutputStream</code> will be closed when the appender instance is
155      * closed.
156      * 
157      * @param outputStream
158      *          An already opened OutputStream.
159      */

160     public void setOutputStream(OutputStream outputStream) {
161         lock.lock();
162         try {
163             // close any previously opened output stream
164             closeOutputStream();
165             this.outputStream = outputStream;
166             if (encoder == null) {
167                 addWarn("Encoder has not been set. Cannot invoke its init method.");
168                 return;
169             }
170
171             encoderInit();
172         } finally {
173             lock.unlock();
174         }
175     }
176
177     void encoderInit() {
178         if (encoder != null && this.outputStream != null) {
179             try {
180                 byte[] header = encoder.headerBytes();
181                 writeBytes(header);
182             } catch (IOException ioe) {
183                 this.started = false;
184                 addStatus(new ErrorStatus("Failed to initialize encoder for appender named [" + name + "]."this, ioe));
185             }
186         }
187     }
188     protected void writeOut(E event) throws IOException {
189         byte[] byteArray = this.encoder.encode(event);
190         writeBytes(byteArray);
191     }
192
193     private void writeBytes(byte[] byteArray) throws IOException {
194         if(byteArray == null || byteArray.length == 0)
195             return;
196         
197         lock.lock();
198         try {
199             this.outputStream.write(byteArray);
200             if (immediateFlush) {
201                 this.outputStream.flush();
202             }
203         } finally {
204             lock.unlock();
205         }
206     }
207
208     /**
209      * Actual writing occurs here.
210      * <p>
211      * Most subclasses of <code>WriterAppender</code> will need to override this
212      * method.
213      * 
214      * @since 0.9.0
215      */

216     protected void subAppend(E event) {
217         if (!isStarted()) {
218             return;
219         }
220         try {
221             // this step avoids LBCLASSIC-139
222             if (event instanceof DeferredProcessingAware) {
223                 ((DeferredProcessingAware) event).prepareForDeferredProcessing();
224             }
225             // the synchronization prevents the OutputStream from being closed while we
226             // are writing. It also prevents multiple threads from entering the same
227             // converter. Converters assume that they are in a synchronized block.
228             // lock.lock();
229
230             byte[] byteArray = this.encoder.encode(event);
231             writeBytes(byteArray);
232
233         } catch (IOException ioe) {
234             // as soon as an exception occurs, move to non-started state
235             // and add a single ErrorStatus to the SM.
236             this.started = false;
237             addStatus(new ErrorStatus("IO failure in appender"this, ioe));
238         }
239     }
240
241     public Encoder<E> getEncoder() {
242         return encoder;
243     }
244
245     public void setEncoder(Encoder<E> encoder) {
246         this.encoder = encoder;
247     }
248
249     public boolean isImmediateFlush() {
250         return immediateFlush;
251     }
252
253     public void setImmediateFlush(boolean immediateFlush) {
254         this.immediateFlush = immediateFlush;
255     }
256
257 }
258