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.Serializable;
020import java.util.concurrent.TimeUnit;
021
022import org.apache.logging.log4j.core.Filter;
023import org.apache.logging.log4j.core.Layout;
024import org.apache.logging.log4j.core.LogEvent;
025import org.apache.logging.log4j.core.config.Property;
026import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
027import org.apache.logging.log4j.core.util.Constants;
028
029/**
030 * Appends log events as bytes to a byte output stream. The stream encoding is defined in the layout.
031 *
032 * @param <M> The kind of {@link OutputStreamManager} under management
033 */
034public abstract class AbstractOutputStreamAppender<M extends OutputStreamManager> extends AbstractAppender {
035
036    /**
037     * Subclasses can extend this abstract Builder.
038     *
039     * @param <B> The type to build.
040     */
041    public abstract static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B> {
042
043        @PluginBuilderAttribute
044        private boolean bufferedIo = true;
045
046        @PluginBuilderAttribute
047        private int bufferSize = Constants.ENCODER_BYTE_BUFFER_SIZE;
048
049        @PluginBuilderAttribute
050        private boolean immediateFlush = true;
051
052        public int getBufferSize() {
053            return bufferSize;
054        }
055
056        public boolean isBufferedIo() {
057            return bufferedIo;
058        }
059
060        public boolean isImmediateFlush() {
061            return immediateFlush;
062        }
063
064        public B withImmediateFlush(final boolean immediateFlush) {
065            this.immediateFlush = immediateFlush;
066            return asBuilder();
067        }
068
069        public B withBufferedIo(final boolean bufferedIo) {
070            this.bufferedIo = bufferedIo;
071            return asBuilder();
072        }
073
074        public B withBufferSize(final int bufferSize) {
075            this.bufferSize = bufferSize;
076            return asBuilder();
077        }
078
079    }
080
081    /**
082     * Immediate flush means that the underlying writer or output stream will be flushed at the end of each append
083     * operation. Immediate flush is slower but ensures that each append request is actually written. If
084     * <code>immediateFlush</code> is set to {@code false}, then there is a good chance that the last few logs events
085     * are not actually written to persistent media if and when the application crashes.
086     */
087    private final boolean immediateFlush;
088
089    private final M manager;
090
091    /**
092     * Instantiates a WriterAppender and set the output destination to a new {@link java.io.OutputStreamWriter}
093     * initialized with <code>os</code> as its {@link java.io.OutputStream}.
094     *
095     * @param name The name of the Appender.
096     * @param layout The layout to format the message.
097     * @param manager The OutputStreamManager.
098     * @deprecated Use {@link #AbstractOutputStreamAppender(String, Layout, Filter, boolean, boolean, Property[], OutputStreamManager)}
099     */
100    @Deprecated
101    protected AbstractOutputStreamAppender(final String name, final Layout<? extends Serializable> layout,
102            final Filter filter, final boolean ignoreExceptions, final boolean immediateFlush, final M manager) {
103        super(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY);
104        this.manager = manager;
105        this.immediateFlush = immediateFlush;
106    }
107
108    /**
109     * Instantiates a WriterAppender and set the output destination to a new {@link java.io.OutputStreamWriter}
110     * initialized with <code>os</code> as its {@link java.io.OutputStream}.
111     *
112     * @param name The name of the Appender.
113     * @param layout The layout to format the message.
114     * @param properties optional properties
115     * @param manager The OutputStreamManager.
116     */
117    protected AbstractOutputStreamAppender(final String name, final Layout<? extends Serializable> layout,
118            final Filter filter, final boolean ignoreExceptions, final boolean immediateFlush,
119            final Property[] properties, final M manager) {
120        super(name, filter, layout, ignoreExceptions, properties);
121        this.manager = manager;
122        this.immediateFlush = immediateFlush;
123    }
124
125    /**
126     * Gets the immediate flush setting.
127     *
128     * @return immediate flush.
129     */
130    public boolean getImmediateFlush() {
131        return immediateFlush;
132    }
133
134    /**
135     * Gets the manager.
136     *
137     * @return the manager.
138     */
139    public M getManager() {
140        return manager;
141    }
142
143    @Override
144    public void start() {
145        if (getLayout() == null) {
146            LOGGER.error("No layout set for the appender named [" + getName() + "].");
147        }
148        if (manager == null) {
149            LOGGER.error("No OutputStreamManager set for the appender named [" + getName() + "].");
150        }
151        super.start();
152    }
153
154    @Override
155    public boolean stop(final long timeout, final TimeUnit timeUnit) {
156        return stop(timeout, timeUnit, true);
157    }
158
159    @Override
160    protected boolean stop(final long timeout, final TimeUnit timeUnit, final boolean changeLifeCycleState) {
161        boolean stopped = super.stop(timeout, timeUnit, changeLifeCycleState);
162        stopped &= manager.stop(timeout, timeUnit);
163        if (changeLifeCycleState) {
164            setStopped();
165        }
166        LOGGER.debug("Appender {} stopped with status {}", getName(), stopped);
167        return stopped;
168    }
169
170    /**
171     * Actual writing occurs here.
172     * <p>
173     * Most subclasses of <code>AbstractOutputStreamAppender</code> will need to override this method.
174     * </p>
175     *
176     * @param event The LogEvent.
177     */
178    @Override
179    public void append(final LogEvent event) {
180        try {
181            tryAppend(event);
182        } catch (final AppenderLoggingException ex) {
183            error("Unable to write to stream " + manager.getName() + " for appender " + getName(), event, ex);
184            throw ex;
185        }
186    }
187
188    private void tryAppend(final LogEvent event) {
189        if (Constants.ENABLE_DIRECT_ENCODERS) {
190            directEncodeEvent(event);
191        } else {
192            writeByteArrayToManager(event);
193        }
194    }
195
196    protected void directEncodeEvent(final LogEvent event) {
197        getLayout().encode(event, manager);
198        if (this.immediateFlush || event.isEndOfBatch()) {
199            manager.flush();
200        }
201    }
202
203    protected void writeByteArrayToManager(final LogEvent event) {
204        final byte[] bytes = getLayout().toByteArray(event);
205        if (bytes != null && bytes.length > 0) {
206            manager.write(bytes, this.immediateFlush || event.isEndOfBatch());
207        }
208    }
209}