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