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