001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.core;
015
016import static ch.qos.logback.core.CoreConstants.CODES_URL;
017
018import java.io.IOException;
019import java.io.OutputStream;
020import java.util.concurrent.locks.ReentrantLock;
021
022import ch.qos.logback.core.encoder.Encoder;
023import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
024import ch.qos.logback.core.spi.DeferredProcessingAware;
025import ch.qos.logback.core.status.ErrorStatus;
026
027/**
028 * OutputStreamAppender appends events to a {@link OutputStream}. This class
029 * provides basic services that other appenders build upon.
030 * 
031 * For more information about this appender, please refer to the online manual
032 * at http://logback.qos.ch/manual/appenders.html#OutputStreamAppender
033 * 
034 * @author Ceki Gülcü
035 */
036public class OutputStreamAppender<E> extends UnsynchronizedAppenderBase<E> {
037
038    /**
039     * It is the encoder which is ultimately responsible for writing the event to an
040     * {@link OutputStream}.
041     */
042    protected Encoder<E> encoder;
043
044    /**
045     * All synchronization in this class is done via the lock object.
046     */
047    protected final ReentrantLock streamWriteLock = new ReentrantLock(false);
048
049    /**
050     * This is the {@link OutputStream outputStream} where output will be written.
051     */
052    private OutputStream outputStream;
053
054    boolean immediateFlush = true;
055
056    /**
057     * The underlying output stream used by this appender.
058     * 
059     * @return
060     */
061    public OutputStream getOutputStream() {
062        return outputStream;
063    }
064
065    /**
066     * Checks that requires parameters are set and if everything is in order,
067     * activates this appender.
068     */
069    public void start() {
070        int errors = 0;
071        if (this.encoder == null) {
072            addStatus(new ErrorStatus("No encoder set for the appender named \"" + name + "\".", this));
073            errors++;
074        }
075
076        if (this.outputStream == null) {
077            addStatus(new ErrorStatus("No output stream set for the appender named \"" + name + "\".", this));
078            errors++;
079        }
080
081        if (encoder == null) {
082            addWarn("Encoder has not been set. Cannot invoke its init method.");
083            errors++;
084        }
085
086
087        // only error free appenders should be activated
088        if (errors == 0) {
089            super.start();
090            encoderInit();
091        }
092    }
093
094    public void setLayout(Layout<E> layout) {
095        addWarn("This appender no longer admits a layout as a sub-component, set an encoder instead.");
096        addWarn("To ensure compatibility, wrapping your layout in LayoutWrappingEncoder.");
097        addWarn("See also " + CODES_URL + "#layoutInsteadOfEncoder for details");
098        LayoutWrappingEncoder<E> lwe = new LayoutWrappingEncoder<E>();
099        lwe.setLayout(layout);
100        lwe.setContext(context);
101        this.encoder = lwe;
102    }
103
104    @Override
105    protected void append(E eventObject) {
106        if (!isStarted()) {
107            return;
108        }
109
110        subAppend(eventObject);
111    }
112
113    /**
114     * Stop this appender instance. The underlying stream or writer is also closed.
115     * 
116     * <p>
117     * Stopped appenders cannot be reused.
118     */
119    public void stop() {
120        if(!isStarted())
121            return;
122
123        streamWriteLock.lock();
124        try {
125            closeOutputStream();
126            super.stop();
127        } finally {
128            streamWriteLock.unlock();
129        }
130    }
131
132    /**
133     * Close the underlying {@link OutputStream}.
134     */
135    protected void closeOutputStream() {
136        if (this.outputStream != null) {
137            try {
138                // before closing we have to output out layout's footer
139                encoderClose();
140                this.outputStream.close();
141                this.outputStream = null;
142            } catch (IOException e) {
143                addStatus(new ErrorStatus("Could not close output stream for OutputStreamAppender.", this, e));
144            }
145        }
146    }
147
148    void encoderClose() {
149        if (encoder != null && this.outputStream != null) {
150            try {
151                byte[] footer = encoder.footerBytes();
152                writeBytes(footer);
153            } catch (IOException ioe) {
154                this.started = false;
155                addStatus(new ErrorStatus("Failed to write footer for appender named [" + name + "].", this, ioe));
156            }
157        }
158    }
159
160    /**
161     * <p>
162     * Sets the @link OutputStream} where the log output will go. The specified
163     * <code>OutputStream</code> must be opened by the user and be writable. The
164     * <code>OutputStream</code> will be closed when the appender instance is
165     * closed.
166     * 
167     * @param outputStream An already opened OutputStream.
168     */
169    public void setOutputStream(OutputStream outputStream) {
170        streamWriteLock.lock();
171        try {
172            // close any previously opened output stream
173            closeOutputStream();
174            this.outputStream = outputStream;
175
176        } finally {
177            streamWriteLock.unlock();
178        }
179    }
180
181    void encoderInit() {
182        if (encoder != null && this.outputStream != null) {
183            try {
184                byte[] header = encoder.headerBytes();
185                writeBytes(header);
186            } catch (IOException ioe) {
187                this.started = false;
188                addStatus(
189                        new ErrorStatus("Failed to initialize encoder for appender named [" + name + "].", this, ioe));
190            }
191        }
192    }
193
194    protected void writeOut(E event) throws IOException {
195        byte[] byteArray = this.encoder.encode(event);
196        writeBytes(byteArray);
197    }
198
199    private void writeBytes(byte[] byteArray) throws IOException {
200        if (byteArray == null || byteArray.length == 0)
201            return;
202
203        streamWriteLock.lock();
204
205        try {
206            if(isStarted()) {
207                writeByteArrayToOutputStreamWithPossibleFlush(byteArray);
208            }
209        } finally {
210            streamWriteLock.unlock();
211        }
212    }
213
214    /**
215     * A simple method to write to an outputStream and flush the stream if immediateFlush is set to true.
216     *
217     * @since 1.3.9/1.4.9
218     */
219    protected final void writeByteArrayToOutputStreamWithPossibleFlush(byte[] byteArray) throws IOException {
220        this.outputStream.write(byteArray);
221        if (immediateFlush) {
222            this.outputStream.flush();
223        }
224    }
225
226    /**
227     * Actual writing occurs here.
228     * <p>
229     * Most subclasses of <code>WriterAppender</code> will need to override this
230     * method.
231     * 
232     * @since 0.9.0
233     */
234    protected void subAppend(E event) {
235        if (!isStarted()) {
236            return;
237        }
238        try {
239            // this step avoids LBCLASSIC-139
240            if (event instanceof DeferredProcessingAware) {
241                ((DeferredProcessingAware) event).prepareForDeferredProcessing();
242            }
243            writeOut(event);
244
245        } catch (IOException ioe) {
246            // as soon as an exception occurs, move to non-started state
247            // and add a single ErrorStatus to the SM.
248            this.started = false;
249            addStatus(new ErrorStatus("IO failure in appender", this, ioe));
250        }
251    }
252
253    public Encoder<E> getEncoder() {
254        return encoder;
255    }
256
257    public void setEncoder(Encoder<E> encoder) {
258        this.encoder = encoder;
259    }
260
261    public boolean isImmediateFlush() {
262        return immediateFlush;
263    }
264
265    public void setImmediateFlush(boolean immediateFlush) {
266        this.immediateFlush = immediateFlush;
267    }
268
269}