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.HashMap;
021import java.util.Map;
022import java.util.concurrent.TimeUnit;
023
024import org.apache.logging.log4j.core.Appender;
025import org.apache.logging.log4j.core.Filter;
026import org.apache.logging.log4j.core.Layout;
027import org.apache.logging.log4j.core.config.Configuration;
028import org.apache.logging.log4j.core.config.plugins.Plugin;
029import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
030import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
031import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
032import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
033import org.apache.logging.log4j.core.net.Advertiser;
034import org.apache.logging.log4j.core.util.Booleans;
035import org.apache.logging.log4j.core.util.Integers;
036
037/**
038 * File Appender.
039 */
040@Plugin(name = FileAppender.PLUGIN_NAME, category = "Core", elementType = Appender.ELEMENT_TYPE, printObject = true)
041public final class FileAppender extends AbstractOutputStreamAppender<FileManager> {
042
043    public static final String PLUGIN_NAME = "File";
044
045    /**
046     * Builds FileAppender instances.
047     * 
048     * @param <B>
049     *            This builder class
050     */
051    public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B>
052            implements org.apache.logging.log4j.core.util.Builder<FileAppender> {
053
054        @PluginBuilderAttribute
055        @Required
056        private String fileName;
057
058        @PluginBuilderAttribute
059        private boolean append = true;
060
061        @PluginBuilderAttribute
062        private boolean locking;
063
064        @PluginBuilderAttribute
065        private boolean advertise;
066
067        @PluginBuilderAttribute
068        private String advertiseUri;
069
070        @PluginBuilderAttribute
071        private boolean createOnDemand;
072
073        @PluginConfiguration
074        private Configuration configuration;
075
076        @Override
077        public FileAppender build() {
078            boolean bufferedIo = isBufferedIo();
079            final int bufferSize = getBufferSize();
080            if (locking && bufferedIo) {
081                LOGGER.warn("Locking and buffering are mutually exclusive. No buffering will occur for {}", fileName);
082                bufferedIo = false;
083            }
084            if (!bufferedIo && bufferSize > 0) {
085                LOGGER.warn("The bufferSize is set to {} but bufferedIo is false: {}", bufferSize, bufferedIo);
086            }
087            final Layout<? extends Serializable> layout = getOrCreateLayout();
088
089            final FileManager manager = FileManager.getFileManager(fileName, append, locking, bufferedIo, createOnDemand,
090                    advertiseUri, layout, bufferSize, configuration);
091            if (manager == null) {
092                return null;
093            }
094
095            return new FileAppender(getName(), layout, getFilter(), manager, fileName, isIgnoreExceptions(),
096                    !bufferedIo || isImmediateFlush(), advertise ? configuration.getAdvertiser() : null);
097        }
098
099        public String getAdvertiseUri() {
100            return advertiseUri;
101        }
102
103        public Configuration getConfiguration() {
104            return configuration;
105        }
106
107        public String getFileName() {
108            return fileName;
109        }
110
111        public boolean isAdvertise() {
112            return advertise;
113        }
114
115        public boolean isAppend() {
116            return append;
117        }
118
119        public boolean isCreateOnDemand() {
120            return createOnDemand;
121        }
122
123        public boolean isLocking() {
124            return locking;
125        }
126
127        public B withAdvertise(final boolean advertise) {
128            this.advertise = advertise;
129            return asBuilder();
130        }
131
132        public B withAdvertiseUri(final String advertiseUri) {
133            this.advertiseUri = advertiseUri;
134            return asBuilder();
135        }
136
137        public B withAppend(final boolean append) {
138            this.append = append;
139            return asBuilder();
140        }
141
142        public B withConfiguration(final Configuration config) {
143            this.configuration = config;
144            return asBuilder();
145        }
146
147        public B withFileName(final String fileName) {
148            this.fileName = fileName;
149            return asBuilder();
150        }
151
152        public B withCreateOnDemand(final boolean createOnDemand) {
153            this.createOnDemand = createOnDemand;
154            return asBuilder();
155        }
156
157        public B withLocking(final boolean locking) {
158            this.locking = locking;
159            return asBuilder();
160        }
161
162    }
163    
164    private static final int DEFAULT_BUFFER_SIZE = 8192;
165    
166    /**
167     * Create a File Appender.
168     * @param fileName The name and path of the file.
169     * @param append "True" if the file should be appended to, "false" if it should be overwritten.
170     * The default is "true".
171     * @param locking "True" if the file should be locked. The default is "false".
172     * @param name The name of the Appender.
173     * @param immediateFlush "true" if the contents should be flushed on every write, "false" otherwise. The default
174     * is "true".
175     * @param ignoreExceptions If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise
176     *               they are propagated to the caller.
177     * @param bufferedIo "true" if I/O should be buffered, "false" otherwise. The default is "true".
178     * @param bufferSizeStr buffer size for buffered IO (default is 8192).
179     * @param layout The layout to use to format the event. If no layout is provided the default PatternLayout
180     * will be used.
181     * @param filter The filter, if any, to use.
182     * @param advertise "true" if the appender configuration should be advertised, "false" otherwise.
183     * @param advertiseUri The advertised URI which can be used to retrieve the file contents.
184     * @param config The Configuration
185     * @return The FileAppender.
186     * @deprecated Use {@link #newBuilder()}
187     */
188    @Deprecated
189    public static FileAppender createAppender(
190            // @formatter:off
191            final String fileName,
192            final String append,
193            final String locking,
194            final String name,
195            final String immediateFlush,
196            final String ignoreExceptions,
197            final String bufferedIo,
198            final String bufferSizeStr,
199            final Layout<? extends Serializable> layout,
200            final Filter filter,
201            final String advertise,
202            final String advertiseUri,
203            final Configuration config) {
204        return newBuilder()
205            .withAdvertise(Boolean.parseBoolean(advertise))
206            .withAdvertiseUri(advertiseUri)
207            .withAppend(Booleans.parseBoolean(append, true))
208            .withBufferedIo(Booleans.parseBoolean(bufferedIo, true))
209            .withBufferSize(Integers.parseInt(bufferSizeStr, DEFAULT_BUFFER_SIZE))
210            .withConfiguration(config)
211            .withFileName(fileName)
212            .withFilter(filter)
213            .withIgnoreExceptions(Booleans.parseBoolean(ignoreExceptions, true))
214            .withImmediateFlush(Booleans.parseBoolean(immediateFlush, true))
215            .withLayout(layout)
216            .withLocking(Boolean.parseBoolean(locking))
217            .withName(name)
218            .build();
219        // @formatter:on
220    }
221    
222    @PluginBuilderFactory
223    public static <B extends Builder<B>> B newBuilder() {
224        return new Builder<B>().asBuilder();
225    }
226    
227    private final String fileName;
228
229    private final Advertiser advertiser;
230
231    private final Object advertisement;
232
233    private FileAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
234            final FileManager manager, final String filename, final boolean ignoreExceptions,
235            final boolean immediateFlush, final Advertiser advertiser) {
236
237        super(name, layout, filter, ignoreExceptions, immediateFlush, manager);
238        if (advertiser != null) {
239            final Map<String, String> configuration = new HashMap<>(layout.getContentFormat());
240            configuration.putAll(manager.getContentFormat());
241            configuration.put("contentType", layout.getContentType());
242            configuration.put("name", name);
243            advertisement = advertiser.advertise(configuration);
244        } else {
245            advertisement = null;
246        }
247        this.fileName = filename;
248        this.advertiser = advertiser;
249    }
250
251    /**
252     * Returns the file name this appender is associated with.
253     * @return The File name.
254     */
255    public String getFileName() {
256        return this.fileName;
257    }
258
259    @Override
260    public boolean stop(final long timeout, final TimeUnit timeUnit) {
261        setStopping();
262        super.stop(timeout, timeUnit, false);
263        if (advertiser != null) {
264            advertiser.unadvertise(advertisement);
265        }
266        setStopped();
267        return true;
268    }
269}