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