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     * Returns the file name this appender is associated with.
159     *
160     * @return The File name.
161     */
162    public String getFileName() {
163        return this.fileName;
164    }
165
166    /**
167     * Returns the length of the memory mapped region.
168     *
169     * @return the length of the memory mapped region
170     */
171    public int getRegionLength() {
172        return getManager().getRegionLength();
173    }
174
175    /**
176     * Create a Memory Mapped File Appender.
177     *
178     * @param fileName The name and path of the file.
179     * @param append "True" if the file should be appended to, "false" if it should be overwritten. The default is
180     *            "true".
181     * @param name The name of the Appender.
182     * @param immediateFlush "true" if the contents should be flushed on every write, "false" otherwise. The default is
183     *            "false".
184     * @param regionLengthStr The buffer size, defaults to {@value MemoryMappedFileManager#DEFAULT_REGION_LENGTH}.
185     * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
186     *            are propagated to the caller.
187     * @param layout The layout to use to format the event. If no layout is provided the default PatternLayout will be
188     *            used.
189     * @param filter The filter, if any, to use.
190     * @param advertise "true" if the appender configuration should be advertised, "false" otherwise.
191     * @param advertiseURI The advertised URI which can be used to retrieve the file contents.
192     * @param config The Configuration.
193     * @return The FileAppender.
194     * @deprecated Use {@link #newBuilder()}.
195     */
196    @Deprecated
197    public static <B extends Builder<B>> MemoryMappedFileAppender createAppender(
198            // @formatter:off
199            final String fileName, //
200            final String append, //
201            final String name, //
202            final String immediateFlush, //
203            final String regionLengthStr, //
204            final String ignore, //
205            final Layout<? extends Serializable> layout, //
206            final Filter filter, //
207            final String advertise, //
208            final String advertiseURI, //
209            final Configuration config) {
210            // @formatter:on
211
212        final boolean isAppend = Booleans.parseBoolean(append, true);
213        final boolean isImmediateFlush = Booleans.parseBoolean(immediateFlush, false);
214        final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
215        final boolean isAdvertise = Boolean.parseBoolean(advertise);
216        final int regionLength = Integers.parseInt(regionLengthStr, MemoryMappedFileManager.DEFAULT_REGION_LENGTH);
217
218        // @formatter:off
219        return MemoryMappedFileAppender.<B>newBuilder()
220        .setAdvertise(isAdvertise)
221        .setAdvertiseURI(advertiseURI)
222        .setAppend(isAppend)
223        .setConfiguration(config)
224        .setFileName(fileName).setFilter(filter).setIgnoreExceptions(ignoreExceptions)
225            .withImmediateFlush(isImmediateFlush).setLayout(layout).setName(name)
226            .setRegionLength(regionLength)
227            .build();
228        // @formatter:on
229    }
230
231    @PluginBuilderFactory
232    public static <B extends Builder<B>> B newBuilder() {
233        return new Builder<B>().asBuilder();
234    }
235
236    /**
237     * Converts the specified region length to a valid value.
238     */
239    private static int determineValidRegionLength(final String name, final int regionLength) {
240        if (regionLength > MAX_REGION_LENGTH) {
241            LOGGER.info("MemoryMappedAppender[{}] Reduced region length from {} to max length: {}", name, regionLength,
242                    MAX_REGION_LENGTH);
243            return MAX_REGION_LENGTH;
244        }
245        if (regionLength < MIN_REGION_LENGTH) {
246            LOGGER.info("MemoryMappedAppender[{}] Expanded region length from {} to min length: {}", name, regionLength,
247                    MIN_REGION_LENGTH);
248            return MIN_REGION_LENGTH;
249        }
250        final int result = Integers.ceilingNextPowerOfTwo(regionLength);
251        if (regionLength != result) {
252            LOGGER.info("MemoryMappedAppender[{}] Rounded up region length from {} to next power of two: {}", name,
253                    regionLength, result);
254        }
255        return result;
256    }
257}