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