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.Property;
31  import org.apache.logging.log4j.core.config.plugins.Plugin;
32  import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
33  import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
34  import org.apache.logging.log4j.core.net.Advertiser;
35  import org.apache.logging.log4j.core.util.Booleans;
36  import org.apache.logging.log4j.core.util.Integers;
37  
38  /**
39   * Memory Mapped File Appender.
40   *
41   * @since 2.1
42   */
43  @Plugin(name = "MemoryMappedFile", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
44  public final class MemoryMappedFileAppender extends AbstractOutputStreamAppender<MemoryMappedFileManager> {
45  
46      /**
47       * Builds RandomAccessFileAppender instances.
48       *
49       * @param <B>
50       *            The type to build
51       */
52      public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B>
53              implements org.apache.logging.log4j.core.util.Builder<MemoryMappedFileAppender> {
54  
55          @PluginBuilderAttribute("fileName")
56          private String fileName;
57  
58          @PluginBuilderAttribute("append")
59          private boolean append = true;
60  
61          @PluginBuilderAttribute("regionLength")
62          private int regionLength = MemoryMappedFileManager.DEFAULT_REGION_LENGTH;
63  
64          @PluginBuilderAttribute("advertise")
65          private boolean advertise;
66  
67          @PluginBuilderAttribute("advertiseURI")
68          private String advertiseURI;
69  
70          @Override
71          public MemoryMappedFileAppender build() {
72              final String name = getName();
73              final int actualRegionLength = determineValidRegionLength(name, regionLength);
74  
75              if (name == null) {
76                  LOGGER.error("No name provided for MemoryMappedFileAppender");
77                  return null;
78              }
79  
80              if (fileName == null) {
81                  LOGGER.error("No filename provided for MemoryMappedFileAppender with name " + name);
82                  return null;
83              }
84              final Layout<? extends Serializable> layout = getOrCreateLayout();
85              final MemoryMappedFileManager manager = MemoryMappedFileManager.getFileManager(fileName, append, isImmediateFlush(),
86                      actualRegionLength, advertiseURI, layout);
87              if (manager == null) {
88                  return null;
89              }
90  
91              return new MemoryMappedFileAppender(name, layout, getFilter(), manager, fileName, isIgnoreExceptions(), false,
92                      advertise ? getConfiguration().getAdvertiser() : null, getPropertyArray());
93          }
94  
95          public B setFileName(final String fileName) {
96              this.fileName = fileName;
97              return asBuilder();
98          }
99  
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      * Write the log entry rolling over the file when required.
159      *
160      * @param event The LogEvent.
161      */
162     @Override
163     public void append(final LogEvent event) {
164 
165         // Leverage the nice batching behaviour of async Loggers/Appenders:
166         // we can signal the file manager that it needs to flush the buffer
167         // to disk at the end of a batch.
168         // From a user's point of view, this means that all log events are
169         // _always_ available in the log file, without incurring the overhead
170         // of immediateFlush=true.
171         getManager().setEndOfBatch(event.isEndOfBatch()); // FIXME manager's EndOfBatch threadlocal can be deleted
172         super.append(event); // TODO should only call force() if immediateFlush && endOfBatch?
173     }
174 
175     /**
176      * Returns the file name this appender is associated with.
177      *
178      * @return The File name.
179      */
180     public String getFileName() {
181         return this.fileName;
182     }
183 
184     /**
185      * Returns the length of the memory mapped region.
186      *
187      * @return the length of the memory mapped region
188      */
189     public int getRegionLength() {
190         return getManager().getRegionLength();
191     }
192 
193     /**
194      * Create a Memory Mapped File Appender.
195      *
196      * @param fileName The name and path of the file.
197      * @param append "True" if the file should be appended to, "false" if it should be overwritten. The default is
198      *            "true".
199      * @param name The name of the Appender.
200      * @param immediateFlush "true" if the contents should be flushed on every write, "false" otherwise. The default is
201      *            "false".
202      * @param regionLengthStr The buffer size, defaults to {@value MemoryMappedFileManager#DEFAULT_REGION_LENGTH}.
203      * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
204      *            are propagated to the caller.
205      * @param layout The layout to use to format the event. If no layout is provided the default PatternLayout will be
206      *            used.
207      * @param filter The filter, if any, to use.
208      * @param advertise "true" if the appender configuration should be advertised, "false" otherwise.
209      * @param advertiseURI The advertised URI which can be used to retrieve the file contents.
210      * @param config The Configuration.
211      * @return The FileAppender.
212      * @deprecated Use {@link #newBuilder()}.
213      */
214     @Deprecated
215     public static <B extends Builder<B>> MemoryMappedFileAppender createAppender(
216             // @formatter:off
217             final String fileName, //
218             final String append, //
219             final String name, //
220             final String immediateFlush, //
221             final String regionLengthStr, //
222             final String ignore, //
223             final Layout<? extends Serializable> layout, //
224             final Filter filter, //
225             final String advertise, //
226             final String advertiseURI, //
227             final Configuration config) {
228             // @formatter:on
229 
230         final boolean isAppend = Booleans.parseBoolean(append, true);
231         final boolean isImmediateFlush = Booleans.parseBoolean(immediateFlush, false);
232         final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
233         final boolean isAdvertise = Boolean.parseBoolean(advertise);
234         final int regionLength = Integers.parseInt(regionLengthStr, MemoryMappedFileManager.DEFAULT_REGION_LENGTH);
235 
236         // @formatter:off
237         return MemoryMappedFileAppender.<B>newBuilder()
238         .setAdvertise(isAdvertise)
239         .setAdvertiseURI(advertiseURI)
240         .setAppend(isAppend)
241         .setConfiguration(config)
242         .setFileName(fileName).setFilter(filter).setIgnoreExceptions(ignoreExceptions)
243             .withImmediateFlush(isImmediateFlush).setLayout(layout).setName(name)
244             .setRegionLength(regionLength)
245             .build();
246         // @formatter:on
247     }
248 
249     @PluginBuilderFactory
250     public static <B extends Builder<B>> B newBuilder() {
251         return new Builder<B>().asBuilder();
252     }
253 
254     /**
255      * Converts the specified region length to a valid value.
256      */
257     private static int determineValidRegionLength(final String name, final int regionLength) {
258         if (regionLength > MAX_REGION_LENGTH) {
259             LOGGER.info("MemoryMappedAppender[{}] Reduced region length from {} to max length: {}", name, regionLength,
260                     MAX_REGION_LENGTH);
261             return MAX_REGION_LENGTH;
262         }
263         if (regionLength < MIN_REGION_LENGTH) {
264             LOGGER.info("MemoryMappedAppender[{}] Expanded region length from {} to min length: {}", name, regionLength,
265                     MIN_REGION_LENGTH);
266             return MIN_REGION_LENGTH;
267         }
268         final int result = Integers.ceilingNextPowerOfTwo(regionLength);
269         if (regionLength != result) {
270             LOGGER.info("MemoryMappedAppender[{}] Rounded up region length from {} to next power of two: {}", name,
271                     regionLength, result);
272         }
273         return result;
274     }
275 }