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;
023
024import org.apache.logging.log4j.core.Appender;
025import org.apache.logging.log4j.core.Core;
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.config.Configuration;
030import org.apache.logging.log4j.core.config.Property;
031import org.apache.logging.log4j.core.config.plugins.Plugin;
032import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
033import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
034import org.apache.logging.log4j.core.net.Advertiser;
035import org.apache.logging.log4j.core.util.Booleans;
036import org.apache.logging.log4j.core.util.Integers;
037
038/**
039 * Memory Mapped File Appender.
040 *
041 * @since 2.1
042 */
043@Plugin(name = "MemoryMappedFile", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
044public final class MemoryMappedFileAppender extends AbstractOutputStreamAppender<MemoryMappedFileManager> {
045
046    /**
047     * Builds RandomAccessFileAppender instances.
048     *
049     * @param <B>
050     *            The type to build
051     */
052    public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B>
053            implements org.apache.logging.log4j.core.util.Builder<MemoryMappedFileAppender> {
054
055        @PluginBuilderAttribute("fileName")
056        private String fileName;
057
058        @PluginBuilderAttribute("append")
059        private boolean append = true;
060
061        @PluginBuilderAttribute("regionLength")
062        private int regionLength = MemoryMappedFileManager.DEFAULT_REGION_LENGTH;
063
064        @PluginBuilderAttribute("advertise")
065        private boolean advertise;
066
067        @PluginBuilderAttribute("advertiseURI")
068        private String advertiseURI;
069
070        @Override
071        public MemoryMappedFileAppender build() {
072            final String name = getName();
073            final int actualRegionLength = determineValidRegionLength(name, regionLength);
074
075            if (name == null) {
076                LOGGER.error("No name provided for MemoryMappedFileAppender");
077                return null;
078            }
079
080            if (fileName == null) {
081                LOGGER.error("No filename provided for MemoryMappedFileAppender with name " + name);
082                return null;
083            }
084            final Layout<? extends Serializable> layout = getOrCreateLayout();
085            final MemoryMappedFileManager manager = MemoryMappedFileManager.getFileManager(fileName, append, isImmediateFlush(),
086                    actualRegionLength, advertiseURI, layout);
087            if (manager == null) {
088                return null;
089            }
090
091            return new MemoryMappedFileAppender(name, layout, getFilter(), manager, fileName, isIgnoreExceptions(), false,
092                    advertise ? getConfiguration().getAdvertiser() : null, getPropertyArray());
093        }
094
095        public B setFileName(final String fileName) {
096            this.fileName = fileName;
097            return asBuilder();
098        }
099
100        public B setAppend(final boolean append) {
101            this.append = append;
102            return asBuilder();
103        }
104
105        public B setRegionLength(final int regionLength) {
106            this.regionLength = regionLength;
107            return asBuilder();
108        }
109
110        public B setAdvertise(final boolean advertise) {
111            this.advertise = advertise;
112            return asBuilder();
113        }
114
115        public B setAdvertiseURI(final String advertiseURI) {
116            this.advertiseURI = advertiseURI;
117            return asBuilder();
118        }
119
120    }
121
122    private static final int BIT_POSITION_1GB = 30; // 2^30 ~= 1GB
123    private static final int MAX_REGION_LENGTH = 1 << BIT_POSITION_1GB;
124    private static final int MIN_REGION_LENGTH = 256;
125
126    private final String fileName;
127    private Object advertisement;
128    private final Advertiser advertiser;
129
130    private MemoryMappedFileAppender(final String name, final Layout<? extends Serializable> layout,
131            final Filter filter, final MemoryMappedFileManager manager, final String filename,
132            final boolean ignoreExceptions, final boolean immediateFlush, final Advertiser advertiser,
133            final Property[] properties) {
134        super(name, layout, filter, ignoreExceptions, immediateFlush, properties, manager);
135        if (advertiser != null) {
136            final Map<String, String> configuration = new HashMap<>(layout.getContentFormat());
137            configuration.putAll(manager.getContentFormat());
138            configuration.put("contentType", layout.getContentType());
139            configuration.put("name", name);
140            advertisement = advertiser.advertise(configuration);
141        }
142        this.fileName = filename;
143        this.advertiser = advertiser;
144    }
145
146    @Override
147    public boolean stop(final long timeout, final TimeUnit timeUnit) {
148        setStopping();
149        super.stop(timeout, timeUnit, false);
150        if (advertiser != null) {
151            advertiser.unadvertise(advertisement);
152        }
153        setStopped();
154        return true;
155    }
156
157    /**
158     * Write the log entry rolling over the file when required.
159     *
160     * @param event The LogEvent.
161     */
162    @Override
163    public void append(final LogEvent event) {
164
165        // Leverage the nice batching behaviour of async Loggers/Appenders:
166        // we can signal the file manager that it needs to flush the buffer
167        // to disk at the end of a batch.
168        // From a user's point of view, this means that all log events are
169        // _always_ available in the log file, without incurring the overhead
170        // of immediateFlush=true.
171        getManager().setEndOfBatch(event.isEndOfBatch()); // FIXME manager's EndOfBatch threadlocal can be deleted
172        super.append(event); // TODO should only call force() if immediateFlush && endOfBatch?
173    }
174
175    /**
176     * Returns the file name this appender is associated with.
177     *
178     * @return The File name.
179     */
180    public String getFileName() {
181        return this.fileName;
182    }
183
184    /**
185     * Returns the length of the memory mapped region.
186     *
187     * @return the length of the memory mapped region
188     */
189    public int getRegionLength() {
190        return getManager().getRegionLength();
191    }
192
193    /**
194     * Create a Memory Mapped File Appender.
195     *
196     * @param fileName The name and path of the file.
197     * @param append "True" if the file should be appended to, "false" if it should be overwritten. The default is
198     *            "true".
199     * @param name The name of the Appender.
200     * @param immediateFlush "true" if the contents should be flushed on every write, "false" otherwise. The default is
201     *            "false".
202     * @param regionLengthStr The buffer size, defaults to {@value MemoryMappedFileManager#DEFAULT_REGION_LENGTH}.
203     * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
204     *            are propagated to the caller.
205     * @param layout The layout to use to format the event. If no layout is provided the default PatternLayout will be
206     *            used.
207     * @param filter The filter, if any, to use.
208     * @param advertise "true" if the appender configuration should be advertised, "false" otherwise.
209     * @param advertiseURI The advertised URI which can be used to retrieve the file contents.
210     * @param config The Configuration.
211     * @return The FileAppender.
212     * @deprecated Use {@link #newBuilder()}.
213     */
214    @Deprecated
215    public static <B extends Builder<B>> MemoryMappedFileAppender createAppender(
216            // @formatter:off
217            final String fileName, //
218            final String append, //
219            final String name, //
220            final String immediateFlush, //
221            final String regionLengthStr, //
222            final String ignore, //
223            final Layout<? extends Serializable> layout, //
224            final Filter filter, //
225            final String advertise, //
226            final String advertiseURI, //
227            final Configuration config) {
228            // @formatter:on
229
230        final boolean isAppend = Booleans.parseBoolean(append, true);
231        final boolean isImmediateFlush = Booleans.parseBoolean(immediateFlush, false);
232        final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
233        final boolean isAdvertise = Boolean.parseBoolean(advertise);
234        final int regionLength = Integers.parseInt(regionLengthStr, MemoryMappedFileManager.DEFAULT_REGION_LENGTH);
235
236        // @formatter:off
237        return MemoryMappedFileAppender.<B>newBuilder()
238        .setAdvertise(isAdvertise)
239        .setAdvertiseURI(advertiseURI)
240        .setAppend(isAppend)
241        .setConfiguration(config)
242        .setFileName(fileName).setFilter(filter).setIgnoreExceptions(ignoreExceptions)
243            .withImmediateFlush(isImmediateFlush).setLayout(layout).setName(name)
244            .setRegionLength(regionLength)
245            .build();
246        // @formatter:on
247    }
248
249    @PluginBuilderFactory
250    public static <B extends Builder<B>> B newBuilder() {
251        return new Builder<B>().asBuilder();
252    }
253
254    /**
255     * Converts the specified region length to a valid value.
256     */
257    private static int determineValidRegionLength(final String name, final int regionLength) {
258        if (regionLength > MAX_REGION_LENGTH) {
259            LOGGER.info("MemoryMappedAppender[{}] Reduced region length from {} to max length: {}", name, regionLength,
260                    MAX_REGION_LENGTH);
261            return MAX_REGION_LENGTH;
262        }
263        if (regionLength < MIN_REGION_LENGTH) {
264            LOGGER.info("MemoryMappedAppender[{}] Expanded region length from {} to min length: {}", name, regionLength,
265                    MIN_REGION_LENGTH);
266            return MIN_REGION_LENGTH;
267        }
268        final int result = Integers.ceilingNextPowerOfTwo(regionLength);
269        if (regionLength != result) {
270            LOGGER.info("MemoryMappedAppender[{}] Rounded up region length from {} to next power of two: {}", name,
271                    regionLength, result);
272        }
273        return result;
274    }
275}