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;
022
023import org.apache.logging.log4j.core.Filter;
024import org.apache.logging.log4j.core.Layout;
025import org.apache.logging.log4j.core.LogEvent;
026import org.apache.logging.log4j.core.config.Configuration;
027import org.apache.logging.log4j.core.config.plugins.Plugin;
028import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
029import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
030import org.apache.logging.log4j.core.config.plugins.PluginElement;
031import org.apache.logging.log4j.core.config.plugins.PluginFactory;
032import org.apache.logging.log4j.core.layout.PatternLayout;
033import org.apache.logging.log4j.core.net.Advertiser;
034import org.apache.logging.log4j.core.util.Booleans;
035import org.apache.logging.log4j.core.util.Integers;
036
037/**
038 * Memory Mapped File Appender.
039 *
040 * @since 2.1
041 */
042@Plugin(name = "MemoryMappedFile", category = "Core", elementType = "appender", printObject = true)
043public final class MemoryMappedFileAppender extends AbstractOutputStreamAppender<MemoryMappedFileManager> {
044
045    private static final int BIT_POSITION_1GB = 30; // 2^30 ~= 1GB
046    private static final int MAX_REGION_LENGTH = 1 << BIT_POSITION_1GB;
047    private static final int MIN_REGION_LENGTH = 256;
048
049    private final String fileName;
050    private Object advertisement;
051    private final Advertiser advertiser;
052
053    private MemoryMappedFileAppender(final String name, final Layout<? extends Serializable> layout,
054            final Filter filter, final MemoryMappedFileManager manager, final String filename,
055            final boolean ignoreExceptions, final boolean immediateFlush, final Advertiser advertiser) {
056        super(name, layout, filter, ignoreExceptions, immediateFlush, manager);
057        if (advertiser != null) {
058            final Map<String, String> configuration = new HashMap<>(layout.getContentFormat());
059            configuration.putAll(manager.getContentFormat());
060            configuration.put("contentType", layout.getContentType());
061            configuration.put("name", name);
062            advertisement = advertiser.advertise(configuration);
063        }
064        this.fileName = filename;
065        this.advertiser = advertiser;
066    }
067
068    @Override
069    public void stop() {
070        super.stop();
071        if (advertiser != null) {
072            advertiser.unadvertise(advertisement);
073        }
074    }
075
076    /**
077     * Write the log entry rolling over the file when required.
078     *
079     * @param event The LogEvent.
080     */
081    @Override
082    public void append(final LogEvent event) {
083
084        // Leverage the nice batching behaviour of async Loggers/Appenders:
085        // we can signal the file manager that it needs to flush the buffer
086        // to disk at the end of a batch.
087        // From a user's point of view, this means that all log events are
088        // _always_ available in the log file, without incurring the overhead
089        // of immediateFlush=true.
090        getManager().setEndOfBatch(event.isEndOfBatch()); // FIXME manager's EndOfBatch threadlocal can be deleted
091        super.append(event); // TODO should only call force() if immediateFlush && endOfBatch?
092    }
093
094    /**
095     * Returns the file name this appender is associated with.
096     *
097     * @return The File name.
098     */
099    public String getFileName() {
100        return this.fileName;
101    }
102
103    /**
104     * Returns the length of the memory mapped region.
105     *
106     * @return the length of the memory mapped region
107     */
108    public int getRegionLength() {
109        return getManager().getRegionLength();
110    }
111
112    /**
113     * Create a Memory Mapped File Appender.
114     *
115     * @param fileName The name and path of the file.
116     * @param append "True" if the file should be appended to, "false" if it should be overwritten. The default is
117     *            "true".
118     * @param name The name of the Appender.
119     * @param immediateFlush "true" if the contents should be flushed on every write, "false" otherwise. The default is
120     *            "true".
121     * @param regionLengthStr The buffer size, defaults to {@value MemoryMappedFileManager#DEFAULT_REGION_LENGTH}.
122     * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
123     *            are propagated to the caller.
124     * @param layout The layout to use to format the event. If no layout is provided the default PatternLayout will be
125     *            used.
126     * @param filter The filter, if any, to use.
127     * @param advertise "true" if the appender configuration should be advertised, "false" otherwise.
128     * @param advertiseURI The advertised URI which can be used to retrieve the file contents.
129     * @param config The Configuration.
130     * @return The FileAppender.
131     */
132    @PluginFactory
133    public static MemoryMappedFileAppender createAppender(
134// @formatter:off
135            @PluginAttribute("fileName") final String fileName, //
136            @PluginAttribute("append") final String append, //
137            @PluginAttribute("name") final String name, //
138            @PluginAttribute("immediateFlush") final String immediateFlush, //
139            @PluginAttribute("regionLength") final String regionLengthStr, //
140            @PluginAttribute("ignoreExceptions") final String ignore, //
141            @PluginElement("Layout") Layout<? extends Serializable> layout, //
142            @PluginElement("Filter") final Filter filter, //
143            @PluginAttribute("advertise") final String advertise, //
144            @PluginAttribute("advertiseURI") final String advertiseURI, //
145            @PluginConfiguration final Configuration config) {
146        // @formatter:on
147
148        final boolean isAppend = Booleans.parseBoolean(append, true);
149        final boolean isForce = Booleans.parseBoolean(immediateFlush, false);
150        final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
151        final boolean isAdvertise = Boolean.parseBoolean(advertise);
152        final int regionLength = Integers.parseInt(regionLengthStr, MemoryMappedFileManager.DEFAULT_REGION_LENGTH);
153        final int actualRegionLength = determineValidRegionLength(name, regionLength);
154
155        if (name == null) {
156            LOGGER.error("No name provided for MemoryMappedFileAppender");
157            return null;
158        }
159
160        if (fileName == null) {
161            LOGGER.error("No filename provided for MemoryMappedFileAppender with name " + name);
162            return null;
163        }
164        if (layout == null) {
165            layout = PatternLayout.createDefaultLayout();
166        }
167        final MemoryMappedFileManager manager = MemoryMappedFileManager.getFileManager(fileName, isAppend, isForce,
168                actualRegionLength, advertiseURI, layout);
169        if (manager == null) {
170            return null;
171        }
172
173        return new MemoryMappedFileAppender(name, layout, filter, manager, fileName, ignoreExceptions, false,
174                isAdvertise ? config.getAdvertiser() : null);
175    }
176
177    /**
178     * Converts the specified region length to a valid value.
179     */
180    private static int determineValidRegionLength(final String name, final int regionLength) {
181        if (regionLength > MAX_REGION_LENGTH) {
182            LOGGER.info("MemoryMappedAppender[{}] Reduced region length from {} to max length: {}", name, regionLength,
183                    MAX_REGION_LENGTH);
184            return MAX_REGION_LENGTH;
185        }
186        if (regionLength < MIN_REGION_LENGTH) {
187            LOGGER.info("MemoryMappedAppender[{}] Expanded region length from {} to min length: {}", name, regionLength,
188                    MIN_REGION_LENGTH);
189            return MIN_REGION_LENGTH;
190        }
191        final int result = Integers.ceilingNextPowerOfTwo(regionLength);
192        if (regionLength != result) {
193            LOGGER.info("MemoryMappedAppender[{}] Rounded up region length from {} to next power of two: {}", name,
194                    regionLength, result);
195        }
196        return result;
197    }
198}