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.RollingRandomAccessFileManager;
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.net.Advertiser;
042import org.apache.logging.log4j.core.util.Booleans;
043import org.apache.logging.log4j.core.util.Integers;
044
045/**
046 * An appender that writes to random access files and can roll over at
047 * intervals.
048 */
049@Plugin(name = "RollingRandomAccessFile", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
050public final class RollingRandomAccessFileAppender extends AbstractOutputStreamAppender<RollingRandomAccessFileManager> {
051
052    public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B>
053            implements org.apache.logging.log4j.core.util.Builder<RollingRandomAccessFileAppender> {
054
055        public Builder() {
056            super();
057            withBufferSize(RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE);
058            withIgnoreExceptions(true);
059            withImmediateFlush(true);
060        }
061
062        @PluginBuilderAttribute("fileName")
063        private String fileName;
064
065        @PluginBuilderAttribute("filePattern")
066        private String filePattern;
067
068        @PluginBuilderAttribute("append")
069        private boolean append = true;
070
071        @PluginElement("Policy")
072        private TriggeringPolicy policy;
073
074        @PluginElement("Strategy")
075        private RolloverStrategy strategy;
076
077        @PluginBuilderAttribute("advertise")
078        private boolean advertise;
079
080        @PluginBuilderAttribute("advertiseURI")
081        private String advertiseURI;
082
083        @PluginBuilderAttribute
084        private String filePermissions;
085
086        @PluginBuilderAttribute
087        private String fileOwner;
088
089        @PluginBuilderAttribute
090        private String fileGroup;
091
092        @Override
093        public RollingRandomAccessFileAppender build() {
094            final String name = getName();
095            if (name == null) {
096                LOGGER.error("No name provided for FileAppender");
097                return null;
098            }
099
100            if (strategy == null) {
101                if (fileName != null) {
102                    strategy = DefaultRolloverStrategy.newBuilder()
103                            .withCompressionLevelStr(String.valueOf(Deflater.DEFAULT_COMPRESSION))
104                            .withConfig(getConfiguration())
105                            .build();
106                } else {
107                    strategy = DirectWriteRolloverStrategy.newBuilder()
108                            .withCompressionLevelStr(String.valueOf(Deflater.DEFAULT_COMPRESSION))
109                            .withConfig(getConfiguration())
110                            .build();
111                }
112            } else if (fileName == null && !(strategy instanceof DirectFileRolloverStrategy)) {
113                LOGGER.error("RollingFileAppender '{}': When no file name is provided a DirectFilenameRolloverStrategy must be configured");
114                return null;
115            }
116
117            if (filePattern == null) {
118                LOGGER.error("No filename pattern provided for FileAppender with name " + name);
119                return null;
120            }
121
122            if (policy == null) {
123                LOGGER.error("A TriggeringPolicy must be provided");
124                return null;
125            }
126
127            final Layout<? extends Serializable> layout = getOrCreateLayout();
128
129            final boolean immediateFlush = isImmediateFlush();
130            final int bufferSize = getBufferSize();
131            final RollingRandomAccessFileManager manager = RollingRandomAccessFileManager
132                    .getRollingRandomAccessFileManager(fileName, filePattern, append, immediateFlush, bufferSize, policy,
133                            strategy, advertiseURI, layout,
134                            filePermissions, fileOwner, fileGroup, getConfiguration());
135            if (manager == null) {
136                return null;
137            }
138
139            manager.initialize();
140
141            return new RollingRandomAccessFileAppender(name, layout,getFilter(), manager, fileName, filePattern,
142                    isIgnoreExceptions(), immediateFlush, bufferSize, advertise ? getConfiguration().getAdvertiser() : null);
143        }
144
145        public B withFileName(final String fileName) {
146            this.fileName = fileName;
147            return asBuilder();
148        }
149
150        public B withFilePattern(final String filePattern) {
151            this.filePattern = filePattern;
152            return asBuilder();
153        }
154
155        public B withAppend(final boolean append) {
156            this.append = append;
157            return asBuilder();
158        }
159
160        public B withPolicy(final TriggeringPolicy policy) {
161            this.policy = policy;
162            return asBuilder();
163        }
164
165        public B withStrategy(final RolloverStrategy strategy) {
166            this.strategy = strategy;
167            return asBuilder();
168        }
169
170        public B withAdvertise(final boolean advertise) {
171            this.advertise = advertise;
172            return asBuilder();
173        }
174
175        public B withAdvertiseURI(final String advertiseURI) {
176            this.advertiseURI = advertiseURI;
177            return asBuilder();
178        }
179
180        public B withFilePermissions(final String filePermissions) {
181            this.filePermissions = filePermissions;
182            return asBuilder();
183        }
184
185        public B withFileOwner(final String fileOwner) {
186            this.fileOwner = fileOwner;
187            return asBuilder();
188        }
189
190        public B withFileGroup(final String fileGroup) {
191            this.fileGroup = fileGroup;
192            return asBuilder();
193        }
194
195    }
196    
197    private final String fileName;
198    private final String filePattern;
199    private final Object advertisement;
200    private final Advertiser advertiser;
201
202    private RollingRandomAccessFileAppender(final String name, final Layout<? extends Serializable> layout,
203            final Filter filter, final RollingRandomAccessFileManager manager, final String fileName,
204            final String filePattern, final boolean ignoreExceptions,
205            final boolean immediateFlush, final int bufferSize, final Advertiser advertiser) {
206        super(name, layout, filter, ignoreExceptions, immediateFlush, manager);
207        if (advertiser != null) {
208            final Map<String, String> configuration = new HashMap<>(layout.getContentFormat());
209            configuration.put("contentType", layout.getContentType());
210            configuration.put("name", name);
211            advertisement = advertiser.advertise(configuration);
212        } else {
213            advertisement = null;
214        }
215        this.fileName = fileName;
216        this.filePattern = filePattern;
217        this.advertiser = advertiser;
218    }
219
220    @Override
221    public boolean stop(final long timeout, final TimeUnit timeUnit) {
222        setStopping();
223        super.stop(timeout, timeUnit, false);
224        if (advertiser != null) {
225            advertiser.unadvertise(advertisement);
226        }
227        setStopped();
228        return true;
229    }
230
231    /**
232     * Write the log entry rolling over the file when required.
233     *
234     * @param event The LogEvent.
235     */
236    @Override
237    public void append(final LogEvent event) {
238        final RollingRandomAccessFileManager manager = getManager();
239        manager.checkRollover(event);
240
241        // Leverage the nice batching behaviour of async Loggers/Appenders:
242        // we can signal the file manager that it needs to flush the buffer
243        // to disk at the end of a batch.
244        // From a user's point of view, this means that all log events are
245        // _always_ available in the log file, without incurring the overhead
246        // of immediateFlush=true.
247        manager.setEndOfBatch(event.isEndOfBatch()); // FIXME manager's EndOfBatch threadlocal can be deleted
248
249        // LOG4J2-1292 utilize gc-free Layout.encode() method: taken care of in superclass
250        super.append(event);
251    }
252
253    /**
254     * Returns the File name for the Appender.
255     *
256     * @return The file name.
257     */
258    public String getFileName() {
259        return fileName;
260    }
261
262    /**
263     * Returns the file pattern used when rolling over.
264     *
265     * @return The file pattern.
266     */
267    public String getFilePattern() {
268        return filePattern;
269    }
270
271    /**
272     * Returns the size of the file manager's buffer.
273     * @return the buffer size
274     */
275    public int getBufferSize() {
276        return getManager().getBufferSize();
277    }
278
279    /**
280     * Create a RollingRandomAccessFileAppender.
281     *
282     * @param fileName The name of the file that is actively written to.
283     *            (required).
284     * @param filePattern The pattern of the file name to use on rollover.
285     *            (required).
286     * @param append If true, events are appended to the file. If false, the
287     *            file is overwritten when opened. Defaults to "true"
288     * @param name The name of the Appender (required).
289     * @param immediateFlush When true, events are immediately flushed. Defaults
290     *            to "true".
291     * @param bufferSizeStr The buffer size, defaults to {@value RollingRandomAccessFileManager#DEFAULT_BUFFER_SIZE}.
292     * @param policy The triggering policy. (required).
293     * @param strategy The rollover strategy. Defaults to
294     *            DefaultRolloverStrategy.
295     * @param layout The layout to use (defaults to the default PatternLayout).
296     * @param filter The Filter or null.
297     * @param ignoreExceptions If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise
298     *               they are propagated to the caller.
299     * @param advertise "true" if the appender configuration should be
300     *            advertised, "false" otherwise.
301     * @param advertiseURI The advertised URI which can be used to retrieve the
302     *            file contents.
303     * @param configuration The Configuration.
304     * @return A RollingRandomAccessFileAppender.
305     * @deprecated Use {@link #newBuilder()}.
306     */
307    @Deprecated
308    public static <B extends Builder<B>> RollingRandomAccessFileAppender createAppender(
309            final String fileName,
310            final String filePattern,
311            final String append,
312            final String name,
313            final String immediateFlush,
314            final String bufferSizeStr,
315            final TriggeringPolicy policy,
316            final RolloverStrategy strategy,
317            final Layout<? extends Serializable> layout,
318            final Filter filter,
319            final String ignoreExceptions,
320            final String advertise,
321            final String advertiseURI,
322            final Configuration configuration) {
323
324        final boolean isAppend = Booleans.parseBoolean(append, true);
325        final boolean isIgnoreExceptions = Booleans.parseBoolean(ignoreExceptions, true);
326        final boolean isImmediateFlush = Booleans.parseBoolean(immediateFlush, true);
327        final boolean isAdvertise = Boolean.parseBoolean(advertise);
328        final int bufferSize = Integers.parseInt(bufferSizeStr, RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE);
329
330        return RollingRandomAccessFileAppender.<B>newBuilder()
331           .withAdvertise(isAdvertise)
332           .withAdvertiseURI(advertiseURI)
333           .withAppend(isAppend)
334           .withBufferSize(bufferSize)
335           .setConfiguration(configuration)
336           .withFileName(fileName)
337           .withFilePattern(filePattern)
338           .withFilter(filter)
339           .withIgnoreExceptions(isIgnoreExceptions)
340           .withImmediateFlush(isImmediateFlush)
341           .withLayout(layout)
342           .withName(name)
343           .withPolicy(policy)
344           .withStrategy(strategy)
345           .build();
346    }
347    
348    @PluginBuilderFactory
349    public static <B extends Builder<B>> B newBuilder() {
350        return new Builder<B>().asBuilder();
351    }
352
353}