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