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