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