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;
021
022import org.apache.logging.log4j.core.Layout;
023
024/**
025 * Manages an OutputStream so that it can be shared by multiple Appenders and will
026 * allow appenders to reconfigure without requiring a new stream.
027 */
028public class OutputStreamManager extends AbstractManager {
029
030    private volatile OutputStream os;
031    protected final Layout<?> layout;
032
033    protected OutputStreamManager(final OutputStream os, final String streamName, final Layout<?> layout,
034            final boolean writeHeader) {
035        super(streamName);
036        this.os = os;
037        this.layout = layout;
038        if (writeHeader && layout != null) {
039            final byte[] header = layout.getHeader();
040            if (header != null) {
041                try {
042                    this.os.write(header, 0, header.length);
043                } catch (final IOException ioe) {
044                    LOGGER.error("Unable to write header", ioe);
045                }
046            }
047        }
048    }
049
050    /**
051     * Creates a Manager.
052     *
053     * @param name The name of the stream to manage.
054     * @param data The data to pass to the Manager.
055     * @param factory The factory to use to create the Manager.
056     * @param <T> The type of the OutputStreamManager.
057     * @return An OutputStreamManager.
058     */
059    public static <T> OutputStreamManager getManager(final String name, final T data,
060                                                 final ManagerFactory<? extends OutputStreamManager, T> factory) {
061        return AbstractManager.getManager(name, factory, data);
062    }
063
064    /**
065     * Default hook to write footer during close.
066     */
067    @Override
068    public void releaseSub() {
069        writeFooter();
070        close();
071    }
072
073    /**
074     * Writes the footer.
075     */
076    protected void writeFooter() {
077        if (layout == null) {
078            return;
079        }
080        final byte[] footer = layout.getFooter();
081        if (footer != null) {
082            write(footer);
083        }
084    }
085
086    /**
087     * Returns the status of the stream.
088     * @return true if the stream is open, false if it is not.
089     */
090    public boolean isOpen() {
091        return getCount() > 0;
092    }
093
094    protected OutputStream getOutputStream() {
095        return os;
096    }
097
098    protected void setOutputStream(final OutputStream os) {
099        final byte[] header = layout.getHeader();
100        if (header != null) {
101            try {
102                os.write(header, 0, header.length);
103                this.os = os; // only update field if os.write() succeeded
104            } catch (final IOException ioe) {
105                LOGGER.error("Unable to write header", ioe);
106            }
107        } else {
108            this.os = os;
109        }
110    }
111
112    /**
113     * Some output streams synchronize writes while others do not. Synchronizing here insures that
114     * log events won't be intertwined.
115     * @param bytes The serialized Log event.
116     * @param offset The offset into the byte array.
117     * @param length The number of bytes to write.
118     * @throws AppenderLoggingException if an error occurs.
119     */
120    protected synchronized void write(final byte[] bytes, final int offset, final int length)  {
121        //System.out.println("write " + count);
122        try {
123            os.write(bytes, offset, length);
124        } catch (final IOException ex) {
125            final String msg = "Error writing to stream " + getName();
126            throw new AppenderLoggingException(msg, ex);
127        }
128    }
129
130    /**
131     * Some output streams synchronize writes while others do not.
132     * @param bytes The serialized Log event.
133     * @throws AppenderLoggingException if an error occurs.
134     */
135    protected void write(final byte[] bytes)  {
136        write(bytes, 0, bytes.length);
137    }
138
139    protected synchronized void close() {
140        final OutputStream stream = os; // access volatile field only once per method
141        if (stream == System.out || stream == System.err) {
142            return;
143        }
144        try {
145            stream.close();
146        } catch (final IOException ex) {
147            LOGGER.error("Unable to close stream " + getName() + ". " + ex);
148        }
149    }
150
151    /**
152     * Flushes any buffers.
153     */
154    public synchronized void flush() {
155        try {
156            os.flush();
157        } catch (final IOException ex) {
158            final String msg = "Error flushing stream " + getName();
159            throw new AppenderLoggingException(msg, ex);
160        }
161    }
162}