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 lock = 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        lock.lock();
116        try {
117            closeOutputStream();
118            super.stop();
119        } finally {
120            lock.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        lock.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            lock.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        lock.lock();
201        try {
202            this.outputStream.write(byteArray);
203            if (immediateFlush) {
204                this.outputStream.flush();
205            }
206        } finally {
207            lock.unlock();
208        }
209    }
210
211    /**
212     * Actual writing occurs here.
213     * <p>
214     * Most subclasses of <code>WriterAppender</code> will need to override this
215     * method.
216     * 
217     * @since 0.9.0
218     */
219    protected void subAppend(E event) {
220        if (!isStarted()) {
221            return;
222        }
223        try {
224            // this step avoids LBCLASSIC-139
225            if (event instanceof DeferredProcessingAware) {
226                ((DeferredProcessingAware) event).prepareForDeferredProcessing();
227            }
228            writeOut(event);
229
230        } catch (IOException ioe) {
231            // as soon as an exception occurs, move to non-started state
232            // and add a single ErrorStatus to the SM.
233            this.started = false;
234            addStatus(new ErrorStatus("IO failure in appender", this, ioe));
235        }
236    }
237
238    public Encoder<E> getEncoder() {
239        return encoder;
240    }
241
242    public void setEncoder(Encoder<E> encoder) {
243        this.encoder = encoder;
244    }
245
246    public boolean isImmediateFlush() {
247        return immediateFlush;
248    }
249
250    public void setImmediateFlush(boolean immediateFlush) {
251        this.immediateFlush = immediateFlush;
252    }
253
254}