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     */
017    package org.apache.logging.log4j.core.appender;
018    
019    import java.io.IOException;
020    import java.io.OutputStream;
021    
022    import 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     */
028    public 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    }