001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.logging.log4j.core.appender;
018
019import java.io.IOException;
020import java.io.OutputStream;
021import java.nio.ByteBuffer;
022import java.util.Objects;
023
024import org.apache.logging.log4j.core.Layout;
025import org.apache.logging.log4j.core.layout.ByteBufferDestination;
026import org.apache.logging.log4j.core.util.Constants;
027
028/**
029 * Manages an OutputStream so that it can be shared by multiple Appenders and will
030 * allow appenders to reconfigure without requiring a new stream.
031 */
032public class OutputStreamManager extends AbstractManager implements ByteBufferDestination {
033    protected final Layout<?> layout;
034    protected ByteBuffer byteBuffer;
035    private volatile OutputStream os;
036    private boolean skipFooter = false;
037
038    protected OutputStreamManager(final OutputStream os, final String streamName, final Layout<?> layout,
039            final boolean writeHeader) {
040        this(os, streamName, layout, writeHeader, ByteBuffer.wrap(new byte[Constants.ENCODER_BYTE_BUFFER_SIZE]));
041    }
042
043    /**
044     *
045     * @param os
046     * @param streamName
047     * @param layout
048     * @param writeHeader
049     * @param byteBuffer
050     * @since 2.6
051     */
052    protected OutputStreamManager(final OutputStream os, final String streamName, final Layout<?> layout,
053            final boolean writeHeader, final ByteBuffer byteBuffer) {
054        super(streamName);
055        this.os = os;
056        this.layout = layout;
057        if (writeHeader && layout != null) {
058            final byte[] header = layout.getHeader();
059            if (header != null) {
060                try {
061                    this.os.write(header, 0, header.length);
062                } catch (final IOException e) {
063                    logError("unable to write header", e);
064                }
065            }
066        }
067        this.byteBuffer = Objects.requireNonNull(byteBuffer, "byteBuffer");
068    }
069
070    /**
071     * Creates a Manager.
072     *
073     * @param name The name of the stream to manage.
074     * @param data The data to pass to the Manager.
075     * @param factory The factory to use to create the Manager.
076     * @param <T> The type of the OutputStreamManager.
077     * @return An OutputStreamManager.
078     */
079    public static <T> OutputStreamManager getManager(final String name, final T data,
080                                                 final ManagerFactory<? extends OutputStreamManager, T> factory) {
081        return AbstractManager.getManager(name, factory, data);
082    }
083
084    /**
085     * Indicate whether the footer should be skipped or not.
086     * @param skipFooter true if the footer should be skipped.
087     */
088    public void skipFooter(boolean skipFooter) {
089        this.skipFooter = skipFooter;
090    }
091
092    /**
093     * Default hook to write footer during close.
094     */
095    @Override
096    public void releaseSub() {
097        writeFooter();
098        close();
099    }
100
101    /**
102     * Writes the footer.
103     */
104    protected void writeFooter() {
105        if (layout == null || skipFooter) {
106            return;
107        }
108        final byte[] footer = layout.getFooter();
109        if (footer != null) {
110            write(footer);
111        }
112    }
113
114    /**
115     * Returns the status of the stream.
116     * @return true if the stream is open, false if it is not.
117     */
118    public boolean isOpen() {
119        return getCount() > 0;
120    }
121
122    protected OutputStream getOutputStream() {
123        return os;
124    }
125
126    protected void setOutputStream(final OutputStream os) {
127        final byte[] header = layout.getHeader();
128        if (header != null) {
129            try {
130                os.write(header, 0, header.length);
131                this.os = os; // only update field if os.write() succeeded
132            } catch (final IOException ioe) {
133                logError("unable to write header", ioe);
134            }
135        } else {
136            this.os = os;
137        }
138    }
139
140    /**
141     * Some output streams synchronize writes while others do not.
142     * @param bytes The serialized Log event.
143     * @throws AppenderLoggingException if an error occurs.
144     */
145    protected void write(final byte[] bytes)  {
146        write(bytes, 0, bytes.length, false);
147    }
148
149    /**
150     * Some output streams synchronize writes while others do not.
151     * @param bytes The serialized Log event.
152     * @param immediateFlush If true, flushes after writing.
153     * @throws AppenderLoggingException if an error occurs.
154     */
155    protected void write(final byte[] bytes, final boolean immediateFlush)  {
156        write(bytes, 0, bytes.length, immediateFlush);
157    }
158
159    /**
160     * Some output streams synchronize writes while others do not. Synchronizing here insures that
161     * log events won't be intertwined.
162     * @param bytes The serialized Log event.
163     * @param offset The offset into the byte array.
164     * @param length The number of bytes to write.
165     * @throws AppenderLoggingException if an error occurs.
166     */
167    protected void write(final byte[] bytes, final int offset, final int length) {
168        write(bytes, offset, length, false);
169    }
170
171    /**
172     * Some output streams synchronize writes while others do not. Synchronizing here insures that
173     * log events won't be intertwined.
174     * @param bytes The serialized Log event.
175     * @param offset The offset into the byte array.
176     * @param length The number of bytes to write.
177     * @param immediateFlush flushes immediately after writing.
178     * @throws AppenderLoggingException if an error occurs.
179     */
180    protected synchronized void write(final byte[] bytes, final int offset, final int length, final boolean immediateFlush) {
181        if (immediateFlush && byteBuffer.position() == 0) {
182            writeToDestination(bytes, offset, length);
183            flushDestination();
184            return;
185        }
186        if (length >= byteBuffer.capacity()) {
187            // if request length exceeds buffer capacity, flush the buffer and write the data directly
188            flush();
189            writeToDestination(bytes, offset, length);
190        } else {
191            if (length > byteBuffer.remaining()) {
192                flush();
193            }
194            byteBuffer.put(bytes, offset, length);
195        }
196        if (immediateFlush) {
197            flush();
198        }
199    }
200
201    /**
202     * Writes the specified section of the specified byte array to the stream.
203     *
204     * @param bytes the array containing data
205     * @param offset from where to write
206     * @param length how many bytes to write
207     * @since 2.6
208     */
209    protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) {
210        try {
211            os.write(bytes, offset, length);
212        } catch (final IOException ex) {
213            final String msg = "Error writing to stream " + getName();
214            throw new AppenderLoggingException(msg, ex);
215        }
216    }
217
218    /**
219     * Calls {@code flush()} on the underlying output stream.
220     * @since 2.6
221     */
222    protected synchronized void flushDestination() {
223        try {
224            os.flush();
225        } catch (final IOException ex) {
226            final String msg = "Error flushing stream " + getName();
227            throw new AppenderLoggingException(msg, ex);
228        }
229    }
230
231    /**
232     * Drains the ByteBufferDestination's buffer into the destination. By default this calls
233     * {@link OutputStreamManager#write(byte[], int, int, boolean)} with the buffer contents.
234     * The underlying stream is not {@linkplain OutputStream#flush() flushed}.
235     *
236     * @see #flushDestination()
237     * @since 2.6
238     */
239    protected synchronized void flushBuffer(final ByteBuffer buf) {
240        buf.flip();
241        if (buf.limit() > 0) {
242            writeToDestination(buf.array(), 0, buf.limit());
243        }
244        buf.clear();
245    }
246
247    /**
248     * Flushes any buffers.
249     */
250    public synchronized void flush() {
251        flushBuffer(byteBuffer);
252        flushDestination();
253    }
254
255    protected synchronized void close() {
256        flush();
257        final OutputStream stream = os; // access volatile field only once per method
258        if (stream == System.out || stream == System.err) {
259            return;
260        }
261        try {
262            stream.close();
263        } catch (final IOException ex) {
264            logError("unable to close stream", ex);
265        }
266    }
267
268    /**
269     * Returns this {@code ByteBufferDestination}'s buffer.
270     * @return the buffer
271     * @since 2.6
272     */
273    @Override
274    public ByteBuffer getByteBuffer() {
275        return byteBuffer;
276    }
277
278    /**
279     * Drains the ByteBufferDestination's buffer into the destination. By default this calls
280     * {@link #flushBuffer(ByteBuffer)} with the specified buffer. Subclasses may override.
281     * <p>
282     * Do not call this method lightly! For some subclasses this is a very expensive operation. For example,
283     * {@link MemoryMappedFileManager} will assume this method was called because the end of the mapped region
284     * was reached during a text encoding operation and will {@linkplain MemoryMappedFileManager#remap() remap} its
285     * buffer.
286     * </p><p>
287     * To just flush the buffered contents to the underlying stream, call
288     * {@link #flushBuffer(ByteBuffer)} directly instead.
289     * </p>
290     *
291     * @param buf the buffer whose contents to write the the destination
292     * @return the specified buffer
293     * @since 2.6
294     */
295    @Override
296    public ByteBuffer drain(final ByteBuffer buf) {
297        flushBuffer(buf);
298        return buf;
299    }
300}