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