View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.appender;
18  
19  import java.io.Serializable;
20  import java.util.HashMap;
21  import java.util.Map;
22  import java.util.concurrent.TimeUnit;
23  
24  import org.apache.logging.log4j.core.Appender;
25  import org.apache.logging.log4j.core.Core;
26  import org.apache.logging.log4j.core.Filter;
27  import org.apache.logging.log4j.core.Layout;
28  import org.apache.logging.log4j.core.LogEvent;
29  import org.apache.logging.log4j.core.config.Configuration;
30  import org.apache.logging.log4j.core.config.plugins.Plugin;
31  import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
32  import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
33  import org.apache.logging.log4j.core.net.Advertiser;
34  import org.apache.logging.log4j.core.util.Booleans;
35  import org.apache.logging.log4j.core.util.Integers;
36  
37  /**
38   * Memory Mapped File Appender.
39   *
40   * @since 2.1
41   */
42  @Plugin(name = "MemoryMappedFile", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
43  public final class MemoryMappedFileAppender extends AbstractOutputStreamAppender<MemoryMappedFileManager> {
44  
45      /**
46       * Builds RandomAccessFileAppender instances.
47       * 
48       * @param <B>
49       *            The type to build
50       */
51      public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B>
52              implements org.apache.logging.log4j.core.util.Builder<MemoryMappedFileAppender> {
53  
54          @PluginBuilderAttribute("fileName")
55          private String fileName;
56  
57          @PluginBuilderAttribute("append")
58          private boolean append = true;
59  
60          @PluginBuilderAttribute("regionLength")
61          private int regionLength = MemoryMappedFileManager.DEFAULT_REGION_LENGTH;
62  
63          @PluginBuilderAttribute("advertise")
64          private boolean advertise;
65  
66          @PluginBuilderAttribute("advertiseURI")
67          private String advertiseURI;
68  
69          @Override
70          public MemoryMappedFileAppender build() {
71              final String name = getName();
72              final int actualRegionLength = determineValidRegionLength(name, regionLength);
73  
74              if (name == null) {
75                  LOGGER.error("No name provided for MemoryMappedFileAppender");
76                  return null;
77              }
78  
79              if (fileName == null) {
80                  LOGGER.error("No filename provided for MemoryMappedFileAppender with name " + name);
81                  return null;
82              }
83              final Layout<? extends Serializable> layout = getOrCreateLayout();
84              final MemoryMappedFileManager manager = MemoryMappedFileManager.getFileManager(fileName, append, isImmediateFlush(),
85                      actualRegionLength, advertiseURI, layout);
86              if (manager == null) {
87                  return null;
88              }
89  
90              return new MemoryMappedFileAppender(name, layout, getFilter(), manager, fileName, isIgnoreExceptions(), false,
91                      advertise ? getConfiguration().getAdvertiser() : null);
92          }
93  
94          public B setFileName(final String fileName) {
95              this.fileName = fileName;
96              return asBuilder();
97          }
98  
99          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 }