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        // only error free appenders should be activated
081        if (errors == 0) {
082            super.start();
083        }
084    }
085
086    public void setLayout(Layout<E> layout) {
087        addWarn("This appender no longer admits a layout as a sub-component, set an encoder instead.");
088        addWarn("To ensure compatibility, wrapping your layout in LayoutWrappingEncoder.");
089        addWarn("See also " + CODES_URL + "#layoutInsteadOfEncoder for details");
090        LayoutWrappingEncoder<E> lwe = new LayoutWrappingEncoder<E>();
091        lwe.setLayout(layout);
092        lwe.setContext(context);
093        this.encoder = lwe;
094    }
095
096    @Override
097    protected void append(E eventObject) {
098        if (!isStarted()) {
099            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}