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 an
40       * {@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 streamWriteLock = 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 closed.
107      * 
108      * <p>
109      * Stopped appenders cannot be reused.
110      */
111     public void stop() {
112         if(!isStarted())
113             return;
114 
115         streamWriteLock.lock();
116         try {
117             closeOutputStream();
118             super.stop();
119         } finally {
120             streamWriteLock.unlock();
121         }
122     }
123 
124     /**
125      * Close the underlying {@link OutputStream}.
126      */
127     protected void closeOutputStream() {
128         if (this.outputStream != null) {
129             try {
130                 // before closing we have to output out layout's footer
131                 encoderClose();
132                 this.outputStream.close();
133                 this.outputStream = null;
134             } catch (IOException e) {
135                 addStatus(new ErrorStatus("Could not close output stream for OutputStreamAppender.", this, e));
136             }
137         }
138     }
139 
140     void encoderClose() {
141         if (encoder != null && this.outputStream != null) {
142             try {
143                 byte[] footer = encoder.footerBytes();
144                 writeBytes(footer);
145             } catch (IOException ioe) {
146                 this.started = false;
147                 addStatus(new ErrorStatus("Failed to write footer for appender named [" + name + "].", this, ioe));
148             }
149         }
150     }
151 
152     /**
153      * <p>
154      * Sets the @link OutputStream} where the log output will go. The specified
155      * <code>OutputStream</code> must be opened by the user and be writable. The
156      * <code>OutputStream</code> will be closed when the appender instance is
157      * closed.
158      * 
159      * @param outputStream An already opened OutputStream.
160      */
161     public void setOutputStream(OutputStream outputStream) {
162         streamWriteLock.lock();
163         try {
164             // close any previously opened output stream
165             closeOutputStream();
166             this.outputStream = outputStream;
167             if (encoder == null) {
168                 addWarn("Encoder has not been set. Cannot invoke its init method.");
169                 return;
170             }
171 
172             encoderInit();
173         } finally {
174             streamWriteLock.unlock();
175         }
176     }
177 
178     void encoderInit() {
179         if (encoder != null && this.outputStream != null) {
180             try {
181                 byte[] header = encoder.headerBytes();
182                 writeBytes(header);
183             } catch (IOException ioe) {
184                 this.started = false;
185                 addStatus(
186                         new ErrorStatus("Failed to initialize encoder for appender named [" + name + "].", this, ioe));
187             }
188         }
189     }
190 
191     protected void writeOut(E event) throws IOException {
192         byte[] byteArray = this.encoder.encode(event);
193         writeBytes(byteArray);
194     }
195 
196     private void writeBytes(byte[] byteArray) throws IOException {
197         if (byteArray == null || byteArray.length == 0)
198             return;
199 
200         streamWriteLock.lock();
201         try {
202             writeByteArrayToOutputStreamWithPossibleFlush(byteArray);
203         } finally {
204             streamWriteLock.unlock();
205         }
206     }
207 
208     /**
209      * A simple method to write to an outputStream and flush the stream if immediateFlush is set to true.
210      *
211      * @since 1.3.9/1.4.9
212      */
213     protected final void writeByteArrayToOutputStreamWithPossibleFlush(byte[] byteArray) throws IOException {
214         this.outputStream.write(byteArray);
215         if (immediateFlush) {
216             this.outputStream.flush();
217         }
218     }
219 
220     /**
221      * Actual writing occurs here.
222      * <p>
223      * Most subclasses of <code>WriterAppender</code> will need to override this
224      * method.
225      * 
226      * @since 0.9.0
227      */
228     protected void subAppend(E event) {
229         if (!isStarted()) {
230             return;
231         }
232         try {
233             // this step avoids LBCLASSIC-139
234             if (event instanceof DeferredProcessingAware) {
235                 ((DeferredProcessingAware) event).prepareForDeferredProcessing();
236             }
237             writeOut(event);
238 
239         } catch (IOException ioe) {
240             // as soon as an exception occurs, move to non-started state
241             // and add a single ErrorStatus to the SM.
242             this.started = false;
243             addStatus(new ErrorStatus("IO failure in appender", this, ioe));
244         }
245     }
246 
247     public Encoder<E> getEncoder() {
248         return encoder;
249     }
250 
251     public void setEncoder(Encoder<E> encoder) {
252         this.encoder = encoder;
253     }
254 
255     public boolean isImmediateFlush() {
256         return immediateFlush;
257     }
258 
259     public void setImmediateFlush(boolean immediateFlush) {
260         this.immediateFlush = immediateFlush;
261     }
262 
263 }