| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache license, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the license for the specific language governing permissions and |
| * limitations under the license. |
| */ |
| package org.apache.logging.log4j.core.appender; |
| |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.Serializable; |
| import java.nio.Buffer; |
| import java.nio.ByteBuffer; |
| import java.util.Objects; |
| import java.util.concurrent.TimeUnit; |
| |
| import org.apache.logging.log4j.core.Layout; |
| import org.apache.logging.log4j.core.LoggerContext; |
| import org.apache.logging.log4j.core.layout.ByteBufferDestination; |
| import org.apache.logging.log4j.core.layout.ByteBufferDestinationHelper; |
| import org.apache.logging.log4j.core.util.Constants; |
| |
| /** |
| * Manages an OutputStream so that it can be shared by multiple Appenders and will |
| * allow appenders to reconfigure without requiring a new stream. |
| */ |
| public class OutputStreamManager extends AbstractManager implements ByteBufferDestination { |
| protected final Layout<?> layout; |
| protected ByteBuffer byteBuffer; |
| private volatile OutputStream outputStream; |
| private boolean skipFooter; |
| |
| protected OutputStreamManager(final OutputStream os, final String streamName, final Layout<?> layout, |
| final boolean writeHeader) { |
| this(os, streamName, layout, writeHeader, Constants.ENCODER_BYTE_BUFFER_SIZE); |
| } |
| |
| protected OutputStreamManager(final OutputStream os, final String streamName, final Layout<?> layout, |
| final boolean writeHeader, final int bufferSize) { |
| this(os, streamName, layout, writeHeader, ByteBuffer.wrap(new byte[bufferSize])); |
| } |
| |
| /** |
| * @since 2.6 |
| * @deprecated |
| */ |
| @Deprecated |
| protected OutputStreamManager(final OutputStream os, final String streamName, final Layout<?> layout, |
| final boolean writeHeader, final ByteBuffer byteBuffer) { |
| super(null, streamName); |
| this.outputStream = os; |
| this.layout = layout; |
| this.byteBuffer = Objects.requireNonNull(byteBuffer, "byteBuffer"); |
| if (writeHeader) { |
| writeHeader(os); |
| } |
| } |
| |
| /** |
| * @since 2.7 |
| */ |
| protected OutputStreamManager(final LoggerContext loggerContext, final OutputStream os, final String streamName, |
| final boolean createOnDemand, final Layout<? extends Serializable> layout, final boolean writeHeader, |
| final ByteBuffer byteBuffer) { |
| super(loggerContext, streamName); |
| if (createOnDemand && os != null) { |
| LOGGER.error( |
| "Invalid OutputStreamManager configuration for '{}': You cannot both set the OutputStream and request on-demand.", |
| streamName); |
| } |
| this.layout = layout; |
| this.byteBuffer = Objects.requireNonNull(byteBuffer, "byteBuffer"); |
| this.outputStream = os; |
| if (writeHeader) { |
| writeHeader(os); |
| } |
| } |
| |
| /** |
| * Creates a Manager. |
| * |
| * @param name The name of the stream to manage. |
| * @param data The data to pass to the Manager. |
| * @param factory The factory to use to create the Manager. |
| * @param <T> The type of the OutputStreamManager. |
| * @return An OutputStreamManager. |
| */ |
| public static <T> OutputStreamManager getManager(final String name, final T data, |
| final ManagerFactory<? extends OutputStreamManager, T> factory) { |
| return AbstractManager.getManager(name, factory, data); |
| } |
| |
| @SuppressWarnings("unused") |
| protected OutputStream createOutputStream() throws IOException { |
| throw new IllegalStateException(getClass().getCanonicalName() + " must implement createOutputStream()"); |
| } |
| |
| /** |
| * Indicate whether the footer should be skipped or not. |
| * @param skipFooter true if the footer should be skipped. |
| */ |
| public void skipFooter(final boolean skipFooter) { |
| this.skipFooter = skipFooter; |
| } |
| |
| /** |
| * Default hook to write footer during close. |
| */ |
| @Override |
| public boolean releaseSub(final long timeout, final TimeUnit timeUnit) { |
| writeFooter(); |
| return closeOutputStream(); |
| } |
| |
| protected void writeHeader(OutputStream os) { |
| if (layout != null && os != null) { |
| final byte[] header = layout.getHeader(); |
| if (header != null) { |
| try { |
| os.write(header, 0, header.length); |
| } catch (final IOException e) { |
| logError("Unable to write header", e); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Writes the footer. |
| */ |
| protected void writeFooter() { |
| if (layout == null || skipFooter) { |
| return; |
| } |
| final byte[] footer = layout.getFooter(); |
| if (footer != null) { |
| write(footer); |
| } |
| } |
| |
| /** |
| * Returns the status of the stream. |
| * @return true if the stream is open, false if it is not. |
| */ |
| public boolean isOpen() { |
| return getCount() > 0; |
| } |
| |
| public boolean hasOutputStream() { |
| return outputStream != null; |
| } |
| |
| protected OutputStream getOutputStream() throws IOException { |
| if (outputStream == null) { |
| outputStream = createOutputStream(); |
| } |
| return outputStream; |
| } |
| |
| protected void setOutputStream(final OutputStream os) { |
| this.outputStream = os; |
| } |
| |
| /** |
| * Some output streams synchronize writes while others do not. |
| * @param bytes The serialized Log event. |
| * @throws AppenderLoggingException if an error occurs. |
| */ |
| protected void write(final byte[] bytes) { |
| write(bytes, 0, bytes.length, false); |
| } |
| |
| /** |
| * Some output streams synchronize writes while others do not. |
| * @param bytes The serialized Log event. |
| * @param immediateFlush If true, flushes after writing. |
| * @throws AppenderLoggingException if an error occurs. |
| */ |
| protected void write(final byte[] bytes, final boolean immediateFlush) { |
| write(bytes, 0, bytes.length, immediateFlush); |
| } |
| |
| @Override |
| public void writeBytes(final byte[] data, final int offset, final int length) { |
| write(data, offset, length, false); |
| } |
| |
| /** |
| * Some output streams synchronize writes while others do not. Synchronizing here insures that |
| * log events won't be intertwined. |
| * @param bytes The serialized Log event. |
| * @param offset The offset into the byte array. |
| * @param length The number of bytes to write. |
| * @throws AppenderLoggingException if an error occurs. |
| */ |
| protected void write(final byte[] bytes, final int offset, final int length) { |
| writeBytes(bytes, offset, length); |
| } |
| |
| /** |
| * Some output streams synchronize writes while others do not. Synchronizing here insures that |
| * log events won't be intertwined. |
| * @param bytes The serialized Log event. |
| * @param offset The offset into the byte array. |
| * @param length The number of bytes to write. |
| * @param immediateFlush flushes immediately after writing. |
| * @throws AppenderLoggingException if an error occurs. |
| */ |
| protected synchronized void write(final byte[] bytes, final int offset, final int length, final boolean immediateFlush) { |
| if (immediateFlush && byteBuffer.position() == 0) { |
| writeToDestination(bytes, offset, length); |
| flushDestination(); |
| return; |
| } |
| if (length >= byteBuffer.capacity()) { |
| // if request length exceeds buffer capacity, flush the buffer and write the data directly |
| flush(); |
| writeToDestination(bytes, offset, length); |
| } else { |
| if (length > byteBuffer.remaining()) { |
| flush(); |
| } |
| byteBuffer.put(bytes, offset, length); |
| } |
| if (immediateFlush) { |
| flush(); |
| } |
| } |
| |
| /** |
| * Writes the specified section of the specified byte array to the stream. |
| * |
| * @param bytes the array containing data |
| * @param offset from where to write |
| * @param length how many bytes to write |
| * @since 2.6 |
| */ |
| protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) { |
| try { |
| getOutputStream().write(bytes, offset, length); |
| } catch (final IOException ex) { |
| throw new AppenderLoggingException("Error writing to stream " + getName(), ex); |
| } |
| } |
| |
| /** |
| * Calls {@code flush()} on the underlying output stream. |
| * @since 2.6 |
| */ |
| protected synchronized void flushDestination() { |
| final OutputStream stream = outputStream; // access volatile field only once per method |
| if (stream != null) { |
| try { |
| stream.flush(); |
| } catch (final IOException ex) { |
| throw new AppenderLoggingException("Error flushing stream " + getName(), ex); |
| } |
| } |
| } |
| |
| /** |
| * Drains the ByteBufferDestination's buffer into the destination. By default this calls |
| * {@link OutputStreamManager#write(byte[], int, int, boolean)} with the buffer contents. |
| * The underlying stream is not {@linkplain OutputStream#flush() flushed}. |
| * |
| * @see #flushDestination() |
| * @since 2.6 |
| */ |
| protected synchronized void flushBuffer(final ByteBuffer buf) { |
| ((Buffer) buf).flip(); |
| try { |
| if (buf.remaining() > 0) { |
| writeToDestination(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining()); |
| } |
| } finally { |
| buf.clear(); |
| } |
| } |
| |
| /** |
| * Flushes any buffers. |
| */ |
| public synchronized void flush() { |
| flushBuffer(byteBuffer); |
| flushDestination(); |
| } |
| |
| protected synchronized boolean closeOutputStream() { |
| flush(); |
| final OutputStream stream = outputStream; // access volatile field only once per method |
| if (stream == null || stream == System.out || stream == System.err) { |
| return true; |
| } |
| try { |
| stream.close(); |
| LOGGER.debug("OutputStream closed"); |
| } catch (final IOException ex) { |
| logError("Unable to close stream", ex); |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Returns this {@code ByteBufferDestination}'s buffer. |
| * @return the buffer |
| * @since 2.6 |
| */ |
| @Override |
| public ByteBuffer getByteBuffer() { |
| return byteBuffer; |
| } |
| |
| /** |
| * Drains the ByteBufferDestination's buffer into the destination. By default this calls |
| * {@link #flushBuffer(ByteBuffer)} with the specified buffer. Subclasses may override. |
| * <p> |
| * Do not call this method lightly! For some subclasses this is a very expensive operation. For example, |
| * {@link MemoryMappedFileManager} will assume this method was called because the end of the mapped region |
| * was reached during a text encoding operation and will {@linkplain MemoryMappedFileManager#remap() remap} its |
| * buffer. |
| * </p><p> |
| * To just flush the buffered contents to the underlying stream, call |
| * {@link #flushBuffer(ByteBuffer)} directly instead. |
| * </p> |
| * |
| * @param buf the buffer whose contents to write the destination |
| * @return the specified buffer |
| * @since 2.6 |
| */ |
| @Override |
| public ByteBuffer drain(final ByteBuffer buf) { |
| flushBuffer(buf); |
| return buf; |
| } |
| |
| @Override |
| public void writeBytes(final ByteBuffer data) { |
| if (data.remaining() == 0) { |
| return; |
| } |
| synchronized (this) { |
| ByteBufferDestinationHelper.writeToUnsynchronized(data, this); |
| } |
| } |
| } |