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