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 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 closed.
107      * 
108      * <p>
109      * Stopped appenders cannot be reused.
110      */
111     public void stop() {
112         lock.lock();
113         try {
114             closeOutputStream();
115             super.stop();
116         } finally {
117             lock.unlock();
118         }
119     }
120 
121     /**
122      * Close the underlying {@link OutputStream}.
123      */
124     protected void closeOutputStream() {
125         if (this.outputStream != null) {
126             try {
127                 // before closing we have to output out layout's footer
128                 encoderClose();
129                 this.outputStream.close();
130                 this.outputStream = null;
131             } catch (IOException e) {
132                 addStatus(new ErrorStatus("Could not close output stream for OutputStreamAppender.", this, e));
133             }
134         }
135     }
136 
137     void encoderClose() {
138         if (encoder != null && this.outputStream != null) {
139             try {
140                 byte[] footer = encoder.footerBytes();
141                 writeBytes(footer);
142             } catch (IOException ioe) {
143                 this.started = false;
144                 addStatus(new ErrorStatus("Failed to write footer for appender named [" + name + "].", this, ioe));
145             }
146         }
147     }
148 
149     /**
150      * <p>
151      * Sets the @link OutputStream} where the log output will go. The specified
152      * <code>OutputStream</code> must be opened by the user and be writable. The
153      * <code>OutputStream</code> will be closed when the appender instance is
154      * closed.
155      * 
156      * @param outputStream An already opened OutputStream.
157      */
158     public void setOutputStream(OutputStream outputStream) {
159         lock.lock();
160         try {
161             // close any previously opened output stream
162             closeOutputStream();
163             this.outputStream = outputStream;
164             if (encoder == null) {
165                 addWarn("Encoder has not been set. Cannot invoke its init method.");
166                 return;
167             }
168 
169             encoderInit();
170         } finally {
171             lock.unlock();
172         }
173     }
174 
175     void encoderInit() {
176         if (encoder != null && this.outputStream != null) {
177             try {
178                 byte[] header = encoder.headerBytes();
179                 writeBytes(header);
180             } catch (IOException ioe) {
181                 this.started = false;
182                 addStatus(
183                         new ErrorStatus("Failed to initialize encoder for appender named [" + name + "].", this, ioe));
184             }
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 }