View Javadoc
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 }