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}