001/*
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2026, 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 v2.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.rolling.LengthCounter;
025import ch.qos.logback.core.spi.DeferredProcessingAware;
026import ch.qos.logback.core.status.ErrorStatus;
027
028/**
029 * OutputStreamAppender appends events to a {@link OutputStream}. This class
030 * provides basic services that other appenders build upon.
031 * 
032 * For more information about this appender, please refer to the online manual
033 * at http://logback.qos.ch/manual/appenders.html#OutputStreamAppender
034 * 
035 * @author Ceki Gülcü
036 */
037public class OutputStreamAppender<E> extends UnsynchronizedAppenderBase<E> {
038
039    /**
040     * It is the encoder which is ultimately responsible for writing the event to an
041     * {@link OutputStream}.
042     */
043    protected Encoder<E> encoder;
044
045    /**
046     * All synchronization in this class is done via the lock object.
047     */
048    protected final ReentrantLock streamWriteLock = new ReentrantLock(false);
049
050    /**
051     * This is the {@link OutputStream outputStream} where output will be written.
052     */
053    private OutputStream outputStream;
054
055    boolean immediateFlush = true;
056
057    boolean statefulEncoder = false;
058
059    /**
060     * The underlying output stream used by this appender.
061     * 
062     * @return
063     */
064    public OutputStream getOutputStream() {
065        return outputStream;
066    }
067
068    /**
069     * Checks that requires parameters are set and if everything is in order,
070     * activates this appender.
071     */
072    public void start() {
073        int errors = 0;
074        if (this.encoder == null) {
075            addError("No encoder set for the appender named \"" + name + "\".");
076            addWarn("Encoder has not been set. Cannot invoke its init method.");
077            errors++;
078        }
079
080        if (this.outputStream == null) {
081            addError("No output stream set for the appender named \"" + name + "\".");
082            errors++;
083        }
084
085
086        // only error free appenders should be activated
087        if (errors == 0) {
088            this.statefulEncoder = encoder.isStateful();
089            // Hold the lock across started=true and headerBytes so a concurrent
090            // append cannot encode before the header is written.
091            streamWriteLock.lock();
092            try {
093                super.start();
094                encoderInit();
095            } finally {
096                streamWriteLock.unlock();
097            }
098        }
099    }
100
101    public void setLayout(Layout<E> layout) {
102        addWarn("This appender no longer admits a layout as a sub-component, set an encoder instead.");
103        addWarn("To ensure compatibility, wrapping your layout in LayoutWrappingEncoder.");
104        addWarn("See also " + CODES_URL + "#layoutInsteadOfEncoder for details");
105        LayoutWrappingEncoder<E> lwe = new LayoutWrappingEncoder<E>();
106        lwe.setLayout(layout);
107        lwe.setContext(context);
108        this.encoder = lwe;
109    }
110
111    @Override
112    protected void append(E eventObject) {
113        if (!isStarted()) {
114            return;
115        }
116
117        subAppend(eventObject);
118    }
119
120    /**
121     * Stop this appender instance. The underlying stream or writer is also closed.
122     * 
123     * <p>Stopped appenders cannot be reused.</p>
124     */
125    public void stop() {
126        if(!isStarted())
127            return;
128
129        streamWriteLock.lock();
130        try {
131            closeOutputStream();
132            super.stop();
133        } finally {
134            streamWriteLock.unlock();
135        }
136    }
137
138    /**
139     * Close the underlying {@link OutputStream}.
140     */
141    protected void closeOutputStream() {
142        if (this.outputStream != null) {
143            try {
144                // before closing we have to output out encooder's footer
145                encoderClose();
146                this.outputStream.close();
147                this.outputStream = null;
148            } catch (IOException e) {
149                addStatus(new ErrorStatus("Could not close output stream for OutputStreamAppender.", this, e));
150            }
151        }
152    }
153
154    /**
155     * Write the encoder's footer to the underlying {@link OutputStream}.
156     *
157     * <p>It is assumed that the caller has acquired the streamWriteLock.</p>
158     */
159    void encoderClose() {
160        if (encoder != null && this.outputStream != null) {
161            try {
162                byte[] footer = encoder.footerBytes();
163                writeBytes(footer);
164            } catch (IOException ioe) {
165                this.started = false;
166                addStatus(new ErrorStatus("Failed to write footer for appender named [" + name + "].", this, ioe));
167            }
168        }
169    }
170
171    /**
172     * <p>
173     * Sets the {@link OutputStream} where the log output will go. The specified
174     * <code>OutputStream</code> must be opened by the user and be writable. The
175     * <code>OutputStream</code> will be closed when the appender instance is
176     * closed.
177     * </p>
178     *
179     * @param outputStream An already opened OutputStream.
180     */
181    public void setOutputStream(OutputStream outputStream) {
182        streamWriteLock.lock();
183        try {
184            // close any previously opened output stream
185            closeOutputStream();
186            this.outputStream = outputStream;
187
188            // the first call to setOutputStream() is made on a non started appender
189            // However, in subsequent calls to setOutputStream() the appender will be in
190            // started state. In subsequent calls, in particular when opening a file after rollover,
191            // we have to output the header hence the call to encoderInit().
192            if(isStarted()) {
193                encoderInit();
194            }
195        } finally {
196            streamWriteLock.unlock();
197        }
198    }
199
200    /**
201     * <p>Write the encoder's header to the underlying {@link OutputStream}.</p>
202     *
203     * <p>It is assumed that the caller has acquired the streamWriteLock.</p>
204     */
205    void encoderInit() {
206        if (encoder != null && this.outputStream != null) {
207            try {
208                byte[] header = encoder.headerBytes();
209                writeBytes(header);
210            } catch (IOException ioe) {
211                this.started = false;
212                addError("Failed to initialize encoder for appender named [" + name + "].", ioe);
213            }
214        }
215    }
216
217    protected void writeOut(E event) throws IOException {
218        if (statefulEncoder) {
219            writeOutStateful(event);
220            return;
221        }
222        writeBytes(this.encoder.encode(event));
223    }
224
225    private void writeOutStateful(E event) throws IOException {
226        streamWriteLock.lock();
227        try {
228            // Recheck under the lock: stop() may have written the footer since
229            // subAppend() observed isStarted().
230            if (!isStarted()) {
231                return;
232            }
233            writeBytes(this.encoder.encode(event));
234        } finally {
235            streamWriteLock.unlock();
236        }
237    }
238
239    private void writeBytes(byte[] byteArray) throws IOException {
240        if (byteArray == null || byteArray.length == 0)
241            return;
242
243        streamWriteLock.lock();
244        try {
245            // guard against appender with stop() invoked in parallel
246            if(isStarted()) {
247                writeByteArrayToOutputStreamWithPossibleFlush(byteArray);
248                updateByteCount(byteArray);
249            }
250        } finally {
251            streamWriteLock.unlock();
252        }
253    }
254
255    protected void updateByteCount(byte[] byteArray) {
256    }
257
258    /**
259     * A simple method to write to an outputStream and flush the stream if immediateFlush is set to true.
260     *
261     * @since 1.3.9/1.4.9
262     */
263    protected final void writeByteArrayToOutputStreamWithPossibleFlush(byte[] byteArray) throws IOException {
264        this.outputStream.write(byteArray);
265        if (immediateFlush) {
266            this.outputStream.flush();
267        }
268    }
269
270    /**
271     * Actual writing occurs here.
272     * <p>
273     * Most subclasses of <code>WriterAppender</code> will need to override this
274     * method.
275     * 
276     * @since 0.9.0
277     */
278    protected void subAppend(E event) {
279        if (!isStarted()) {
280            return;
281        }
282        try {
283            // this step avoids LBCLASSIC-139
284            if (event instanceof DeferredProcessingAware) {
285                ((DeferredProcessingAware) event).prepareForDeferredProcessing();
286            }
287            writeOut(event);
288
289        } catch (IOException ioe) {
290            // as soon as an exception occurs, move to non-started state
291            // and add a single ErrorStatus to the SM.
292            this.started = false;
293            addStatus(new ErrorStatus("IO failure in appender", this, ioe));
294        }
295    }
296
297    public Encoder<E> getEncoder() {
298        return encoder;
299    }
300
301    public void setEncoder(Encoder<E> encoder) {
302        this.encoder = encoder;
303    }
304
305    public boolean isImmediateFlush() {
306        return immediateFlush;
307    }
308
309    public void setImmediateFlush(boolean immediateFlush) {
310        this.immediateFlush = immediateFlush;
311    }
312
313}