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;
023import java.util.zip.Deflater;
024
025import org.apache.logging.log4j.core.Appender;
026import org.apache.logging.log4j.core.Core;
027import org.apache.logging.log4j.core.Filter;
028import org.apache.logging.log4j.core.Layout;
029import org.apache.logging.log4j.core.LogEvent;
030import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy;
031import org.apache.logging.log4j.core.appender.rolling.DirectWriteRolloverStrategy;
032import org.apache.logging.log4j.core.appender.rolling.RollingFileManager;
033import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy;
034import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy;
035import org.apache.logging.log4j.core.appender.rolling.DirectFileRolloverStrategy;
036import org.apache.logging.log4j.core.config.Configuration;
037import org.apache.logging.log4j.core.config.plugins.Plugin;
038import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
039import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
040import org.apache.logging.log4j.core.config.plugins.PluginElement;
041import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
042import org.apache.logging.log4j.core.net.Advertiser;
043import org.apache.logging.log4j.core.util.Booleans;
044import org.apache.logging.log4j.core.util.Integers;
045
046/**
047 * An appender that writes to files and can roll over at intervals.
048 */
049@Plugin(name = RollingFileAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
050public final class RollingFileAppender extends AbstractOutputStreamAppender<RollingFileManager> {
051
052    public static final String PLUGIN_NAME = "RollingFile";
053
054    /**
055     * Builds FileAppender instances.
056     * 
057     * @param <B>
058     *            The type to build
059     */
060    public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B>
061            implements org.apache.logging.log4j.core.util.Builder<RollingFileAppender> {
062
063        @PluginBuilderAttribute
064        private String fileName;
065
066        @PluginBuilderAttribute
067        @Required
068        private String filePattern;
069
070        @PluginBuilderAttribute
071        private boolean append = true;
072
073        @PluginBuilderAttribute
074        private boolean locking;
075
076        @PluginElement("Policy") 
077        @Required
078        private TriggeringPolicy policy;
079        
080        @PluginElement("Strategy") 
081        private RolloverStrategy strategy;
082
083        @PluginBuilderAttribute
084        private boolean advertise;
085
086        @PluginBuilderAttribute
087        private String advertiseUri;
088
089        @PluginBuilderAttribute
090        private boolean createOnDemand;
091
092        @Override
093        public RollingFileAppender build() {
094            // Even though some variables may be annotated with @Required, we must still perform validation here for
095            // call sites that build builders programmatically.
096            final boolean isBufferedIo = isBufferedIo();
097            final int bufferSize = getBufferSize();
098            if (getName() == null) {
099                LOGGER.error("RollingFileAppender '{}': No name provided.", getName());
100                return null;
101            }
102
103            if (!isBufferedIo && bufferSize > 0) {
104                LOGGER.warn("RollingFileAppender '{}': The bufferSize is set to {} but bufferedIO is not true", getName(), bufferSize);
105            }
106
107            if (filePattern == null) {
108                LOGGER.error("RollingFileAppender '{}': No file name pattern provided.", getName());
109                return null;
110            }
111
112            if (policy == null) {
113                LOGGER.error("RollingFileAppender '{}': No TriggeringPolicy provided.", getName());
114                return null;
115            }
116
117            if (strategy == null) {
118                if (fileName != null) {
119                    strategy = DefaultRolloverStrategy.createStrategy(null, null, null,
120                            String.valueOf(Deflater.DEFAULT_COMPRESSION), null, true, getConfiguration());
121                } else {
122                    strategy = DirectWriteRolloverStrategy.createStrategy(null,
123                            String.valueOf(Deflater.DEFAULT_COMPRESSION), null, true, getConfiguration());
124                }
125            } else if (fileName == null && !(strategy instanceof DirectFileRolloverStrategy)) {
126                LOGGER.error("RollingFileAppender '{}': When no file name is provided a DirectFilenameRolloverStrategy must be configured");
127                return null;
128            }
129
130            final Layout<? extends Serializable> layout = getOrCreateLayout();
131            final RollingFileManager manager = RollingFileManager.getFileManager(fileName, filePattern, append,
132                    isBufferedIo, policy, strategy, advertiseUri, layout, bufferSize, isImmediateFlush(),
133                    createOnDemand, getConfiguration());
134            if (manager == null) {
135                return null;
136            }
137
138            manager.initialize();
139
140            return new RollingFileAppender(getName(), layout, getFilter(), manager, fileName, filePattern,
141                    isIgnoreExceptions(), isImmediateFlush(), advertise ? getConfiguration().getAdvertiser() : null);
142        }
143
144        public String getAdvertiseUri() {
145            return advertiseUri;
146        }
147
148        public String getFileName() {
149            return fileName;
150        }
151
152        public boolean isAdvertise() {
153            return advertise;
154        }
155
156        public boolean isAppend() {
157            return append;
158        }
159
160        public boolean isCreateOnDemand() {
161            return createOnDemand;
162        }
163
164        public boolean isLocking() {
165            return locking;
166        }
167
168        public B withAdvertise(final boolean advertise) {
169            this.advertise = advertise;
170            return asBuilder();
171        }
172
173        public B withAdvertiseUri(final String advertiseUri) {
174            this.advertiseUri = advertiseUri;
175            return asBuilder();
176        }
177
178        public B withAppend(final boolean append) {
179            this.append = append;
180            return asBuilder();
181        }
182
183        public B withFileName(final String fileName) {
184            this.fileName = fileName;
185            return asBuilder();
186        }
187
188        public B withCreateOnDemand(final boolean createOnDemand) {
189            this.createOnDemand = createOnDemand;
190            return asBuilder();
191        }
192
193        public B withLocking(final boolean locking) {
194            this.locking = locking;
195            return asBuilder();
196        }
197
198        public String getFilePattern() {
199            return filePattern;
200        }
201
202        public TriggeringPolicy getPolicy() {
203            return policy;
204        }
205
206        public RolloverStrategy getStrategy() {
207            return strategy;
208        }
209
210        public B withFilePattern(final String filePattern) {
211            this.filePattern = filePattern;
212            return asBuilder();
213        }
214
215        public B withPolicy(final TriggeringPolicy policy) {
216            this.policy = policy;
217            return asBuilder();
218        }
219
220        public B withStrategy(final RolloverStrategy strategy) {
221            this.strategy = strategy;
222            return asBuilder();
223        }
224
225    }
226    
227    private static final int DEFAULT_BUFFER_SIZE = 8192;
228
229    private final String fileName;
230    private final String filePattern;
231    private Object advertisement;
232    private final Advertiser advertiser;
233
234    private RollingFileAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
235            final RollingFileManager manager, final String fileName, final String filePattern,
236            final boolean ignoreExceptions, final boolean immediateFlush, final Advertiser advertiser) {
237        super(name, layout, filter, ignoreExceptions, immediateFlush, manager);
238        if (advertiser != null) {
239            final Map<String, String> configuration = new HashMap<>(layout.getContentFormat());
240            configuration.put("contentType", layout.getContentType());
241            configuration.put("name", name);
242            advertisement = advertiser.advertise(configuration);
243        }
244        this.fileName = fileName;
245        this.filePattern = filePattern;
246        this.advertiser = advertiser;
247    }
248
249    @Override
250    public boolean stop(final long timeout, final TimeUnit timeUnit) {
251        setStopping();
252        final boolean stopped = super.stop(timeout, timeUnit, false);
253        if (advertiser != null) {
254            advertiser.unadvertise(advertisement);
255        }
256        setStopped();
257        return stopped;
258    }
259
260    /**
261     * Writes the log entry rolling over the file when required.
262
263     * @param event The LogEvent.
264     */
265    @Override
266    public void append(final LogEvent event) {
267        getManager().checkRollover(event);
268        super.append(event);
269    }
270
271    /**
272     * Returns the File name for the Appender.
273     * @return The file name.
274     */
275    public String getFileName() {
276        return fileName;
277    }
278
279    /**
280     * Returns the file pattern used when rolling over.
281     * @return The file pattern.
282     */
283    public String getFilePattern() {
284        return filePattern;
285    }
286
287    /**
288     * Returns the triggering policy.
289     * @param <T> TriggeringPolicy type
290     * @return The TriggeringPolicy
291     */
292    public <T extends TriggeringPolicy> T getTriggeringPolicy() {
293        return getManager().getTriggeringPolicy();
294    }
295
296    /**
297     * Creates a RollingFileAppender.
298     * @param fileName The name of the file that is actively written to. (required).
299     * @param filePattern The pattern of the file name to use on rollover. (required).
300     * @param append If true, events are appended to the file. If false, the file
301     * is overwritten when opened. Defaults to "true"
302     * @param name The name of the Appender (required).
303     * @param bufferedIO When true, I/O will be buffered. Defaults to "true".
304     * @param bufferSizeStr buffer size for buffered IO (default is 8192).
305     * @param immediateFlush When true, events are immediately flushed. Defaults to "true".
306     * @param policy The triggering policy. (required).
307     * @param strategy The rollover strategy. Defaults to DefaultRolloverStrategy.
308     * @param layout The layout to use (defaults to the default PatternLayout).
309     * @param filter The Filter or null.
310     * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise
311     *               they are propagated to the caller.
312     * @param advertise "true" if the appender configuration should be advertised, "false" otherwise.
313     * @param advertiseUri The advertised URI which can be used to retrieve the file contents.
314     * @param config The Configuration.
315     * @return A RollingFileAppender.
316     * @deprecated Use {@link #newBuilder()}.
317     */
318    @Deprecated
319    public static <B extends Builder<B>> RollingFileAppender createAppender(
320            // @formatter:off
321            final String fileName,
322            final String filePattern,
323            final String append,
324            final String name,
325            final String bufferedIO,
326            final String bufferSizeStr,
327            final String immediateFlush,
328            final TriggeringPolicy policy,
329            final RolloverStrategy strategy,
330            final Layout<? extends Serializable> layout,
331            final Filter filter,
332            final String ignore,
333            final String advertise,
334            final String advertiseUri,
335            final Configuration config) {
336            // @formatter:on
337        final int bufferSize = Integers.parseInt(bufferSizeStr, DEFAULT_BUFFER_SIZE);
338        // @formatter:off
339        return RollingFileAppender.<B>newBuilder()
340                .withAdvertise(Boolean.parseBoolean(advertise))
341                .withAdvertiseUri(advertiseUri)
342                .withAppend(Booleans.parseBoolean(append, true))
343                .withBufferedIo(Booleans.parseBoolean(bufferedIO, true))
344                .withBufferSize(bufferSize)
345                .setConfiguration(config)
346                .withFileName(fileName)
347                .withFilePattern(filePattern)
348                .withFilter(filter)
349                .withIgnoreExceptions(Booleans.parseBoolean(ignore, true))
350                .withImmediateFlush(Booleans.parseBoolean(immediateFlush, true))
351                .withLayout(layout)
352                .withCreateOnDemand(false)
353                .withLocking(false)
354                .withName(name)
355                .withPolicy(policy)
356                .withStrategy(strategy)
357                .build();
358        // @formatter:on
359    }
360
361    @PluginBuilderFactory
362    public static <B extends Builder<B>> B newBuilder() {
363        return new Builder<B>().asBuilder();
364    }
365}