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