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