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