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.async; 018 019import java.util.Arrays; 020import java.util.List; 021import java.util.concurrent.TimeUnit; 022 023import org.apache.logging.log4j.Level; 024import org.apache.logging.log4j.LogManager; 025import org.apache.logging.log4j.core.Core; 026import org.apache.logging.log4j.core.Filter; 027import org.apache.logging.log4j.core.LogEvent; 028import org.apache.logging.log4j.core.config.AppenderRef; 029import org.apache.logging.log4j.core.config.Configuration; 030import org.apache.logging.log4j.core.config.LoggerConfig; 031import org.apache.logging.log4j.core.config.Node; 032import org.apache.logging.log4j.core.config.Property; 033import org.apache.logging.log4j.core.config.plugins.Plugin; 034import org.apache.logging.log4j.core.config.plugins.PluginAttribute; 035import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; 036import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; 037import org.apache.logging.log4j.core.config.plugins.PluginElement; 038import org.apache.logging.log4j.core.config.plugins.PluginFactory; 039import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; 040import org.apache.logging.log4j.core.jmx.RingBufferAdmin; 041import org.apache.logging.log4j.core.util.Booleans; 042import org.apache.logging.log4j.spi.AbstractLogger; 043import org.apache.logging.log4j.util.Strings; 044 045/** 046 * Asynchronous Logger object that is created via configuration and can be 047 * combined with synchronous loggers. 048 * <p> 049 * AsyncLoggerConfig is a logger designed for high throughput and low latency 050 * logging. It does not perform any I/O in the calling (application) thread, but 051 * instead hands off the work to another thread as soon as possible. The actual 052 * logging is performed in the background thread. It uses 053 * <a href="https://lmax-exchange.github.io/disruptor/">LMAX Disruptor</a> for 054 * inter-thread communication. 055 * <p> 056 * To use AsyncLoggerConfig, specify {@code <asyncLogger>} or 057 * {@code <asyncRoot>} in configuration. 058 * <p> 059 * Note that for performance reasons, this logger does not include source 060 * location by default. You need to specify {@code includeLocation="true"} in 061 * the configuration or any %class, %location or %line conversion patterns in 062 * your log4j.xml configuration will produce either a "?" character or no output 063 * at all. 064 * <p> 065 * For best performance, use AsyncLoggerConfig with the RandomAccessFileAppender or 066 * RollingRandomAccessFileAppender, with immediateFlush=false. These appenders have 067 * built-in support for the batching mechanism used by the Disruptor library, 068 * and they will flush to disk at the end of each batch. This means that even 069 * with immediateFlush=false, there will never be any items left in the buffer; 070 * all log events will all be written to disk in a very efficient manner. 071 */ 072@Plugin(name = "asyncLogger", category = Node.CATEGORY, printObject = true) 073public class AsyncLoggerConfig extends LoggerConfig { 074 075 @PluginBuilderFactory 076 public static <B extends Builder<B>> B newAsyncBuilder() { 077 return new Builder<B>().asBuilder(); 078 } 079 080 public static class Builder<B extends Builder<B>> extends LoggerConfig.Builder<B> { 081 082 @Override 083 public LoggerConfig build() { 084 final String name = getLoggerName().equals(ROOT) ? Strings.EMPTY : getLoggerName(); 085 LevelAndRefs container = LoggerConfig.getLevelAndRefs(getLevel(), getRefs(), getLevelAndRefs(), 086 getConfig()); 087 return new AsyncLoggerConfig(name, container.refs,getFilter(), container.level, isAdditivity(), 088 getProperties(), getConfig(), includeLocation(getIncludeLocation())); 089 } 090 } 091 092 private static final ThreadLocal<Boolean> ASYNC_LOGGER_ENTERED = new ThreadLocal<Boolean>() { 093 @Override 094 protected Boolean initialValue() { 095 return Boolean.FALSE; 096 } 097 }; 098 099 private final AsyncLoggerConfigDelegate delegate; 100 101 protected AsyncLoggerConfig(final String name, 102 final List<AppenderRef> appenders, final Filter filter, 103 final Level level, final boolean additive, 104 final Property[] properties, final Configuration config, 105 final boolean includeLocation) { 106 super(name, appenders, filter, level, additive, properties, config, 107 includeLocation); 108 delegate = config.getAsyncLoggerConfigDelegate(); 109 delegate.setLogEventFactory(getLogEventFactory()); 110 } 111 112 // package-protected for testing 113 AsyncLoggerConfigDelegate getAsyncLoggerConfigDelegate() { 114 return delegate; 115 } 116 117 @Override 118 protected void log(final LogEvent event, final LoggerConfigPredicate predicate) { 119 // See LOG4J2-2301 120 if (predicate == LoggerConfigPredicate.ALL && 121 ASYNC_LOGGER_ENTERED.get() == Boolean.FALSE && 122 // Optimization: AsyncLoggerConfig is identical to LoggerConfig 123 // when no appenders are present. Avoid splitting for synchronous 124 // and asynchronous execution paths until encountering an 125 // AsyncLoggerConfig with appenders. 126 hasAppenders()) { 127 // This is the first AsnycLoggerConfig encountered by this LogEvent 128 ASYNC_LOGGER_ENTERED.set(Boolean.TRUE); 129 try { 130 // Detect the first time we encounter an AsyncLoggerConfig. We must log 131 // to all non-async loggers first. 132 super.log(event, LoggerConfigPredicate.SYNCHRONOUS_ONLY); 133 // Then pass the event to the background thread where 134 // all async logging is executed. It is important this 135 // happens at most once and after all synchronous loggers 136 // have been invoked, because we lose parameter references 137 // from reusable messages. 138 logToAsyncDelegate(event); 139 } finally { 140 ASYNC_LOGGER_ENTERED.set(Boolean.FALSE); 141 } 142 } else { 143 super.log(event, predicate); 144 } 145 } 146 147 @Override 148 protected void callAppenders(final LogEvent event) { 149 super.callAppenders(event); 150 } 151 152 private void logToAsyncDelegate(final LogEvent event) { 153 if (!isFiltered(event)) { 154 // Passes on the event to a separate thread that will call 155 // asyncCallAppenders(LogEvent). 156 populateLazilyInitializedFields(event); 157 if (!delegate.tryEnqueue(event, this)) { 158 handleQueueFull(event); 159 } 160 } 161 } 162 163 private void handleQueueFull(final LogEvent event) { 164 if (AbstractLogger.getRecursionDepth() > 1) { // LOG4J2-1518, LOG4J2-2031 165 // If queue is full AND we are in a recursive call, call appender directly to prevent deadlock 166 AsyncQueueFullMessageUtil.logWarningToStatusLogger(); 167 logToAsyncLoggerConfigsOnCurrentThread(event); 168 } else { 169 // otherwise, we leave it to the user preference 170 final EventRoute eventRoute = delegate.getEventRoute(event.getLevel()); 171 eventRoute.logMessage(this, event); 172 } 173 } 174 175 private void populateLazilyInitializedFields(final LogEvent event) { 176 event.getSource(); 177 event.getThreadName(); 178 } 179 180 void logInBackgroundThread(final LogEvent event) { 181 delegate.enqueueEvent(event, this); 182 } 183 184 /** 185 * Called by AsyncLoggerConfigHelper.RingBufferLog4jEventHandler. 186 * 187 * This method will log the provided event to only configs of type {@link AsyncLoggerConfig} (not 188 * default {@link LoggerConfig} definitions), which will be invoked on the <b>calling thread</b>. 189 */ 190 void logToAsyncLoggerConfigsOnCurrentThread(final LogEvent event) { 191 log(event, LoggerConfigPredicate.ASYNCHRONOUS_ONLY); 192 } 193 194 private String displayName() { 195 return LogManager.ROOT_LOGGER_NAME.equals(getName()) ? LoggerConfig.ROOT : getName(); 196 } 197 198 @Override 199 public void start() { 200 LOGGER.trace("AsyncLoggerConfig[{}] starting...", displayName()); 201 super.start(); 202 } 203 204 @Override 205 public boolean stop(final long timeout, final TimeUnit timeUnit) { 206 setStopping(); 207 super.stop(timeout, timeUnit, false); 208 LOGGER.trace("AsyncLoggerConfig[{}] stopping...", displayName()); 209 setStopped(); 210 return true; 211 } 212 213 /** 214 * Creates and returns a new {@code RingBufferAdmin} that instruments the 215 * ringbuffer of this {@code AsyncLoggerConfig}. 216 * 217 * @param contextName name of the {@code LoggerContext} 218 * @return a new {@code RingBufferAdmin} that instruments the ringbuffer 219 */ 220 public RingBufferAdmin createRingBufferAdmin(final String contextName) { 221 return delegate.createRingBufferAdmin(contextName, getName()); 222 } 223 224 /** 225 * Factory method to create a LoggerConfig. 226 * 227 * @param additivity True if additive, false otherwise. 228 * @param levelName The Level to be associated with the Logger. 229 * @param loggerName The name of the Logger. 230 * @param includeLocation "true" if location should be passed downstream 231 * @param refs An array of Appender names. 232 * @param properties Properties to pass to the Logger. 233 * @param config The Configuration. 234 * @param filter A Filter. 235 * @return A new LoggerConfig. 236 * @deprecated use {@link #createLogger(boolean, Level, String, String, AppenderRef[], Property[], Configuration, Filter)} 237 */ 238 @Deprecated 239 public static LoggerConfig createLogger( 240 final String additivity, 241 final String levelName, 242 final String loggerName, 243 final String includeLocation, 244 final AppenderRef[] refs, 245 final Property[] properties, 246 final Configuration config, 247 final Filter filter) { 248 if (loggerName == null) { 249 LOGGER.error("Loggers cannot be configured without a name"); 250 return null; 251 } 252 253 final List<AppenderRef> appenderRefs = Arrays.asList(refs); 254 Level level; 255 try { 256 level = Level.toLevel(levelName, Level.ERROR); 257 } catch (final Exception ex) { 258 LOGGER.error( 259 "Invalid Log level specified: {}. Defaulting to Error", 260 levelName); 261 level = Level.ERROR; 262 } 263 final String name = loggerName.equals(LoggerConfig.ROOT) ? Strings.EMPTY : loggerName; 264 final boolean additive = Booleans.parseBoolean(additivity, true); 265 266 return new AsyncLoggerConfig(name, appenderRefs, filter, level, 267 additive, properties, config, includeLocation(includeLocation)); 268 } 269 270 /** 271 * Factory method to create a LoggerConfig. 272 * 273 * @param additivity True if additive, false otherwise. 274 * @param level The Level to be associated with the Logger. 275 * @param loggerName The name of the Logger. 276 * @param includeLocation "true" if location should be passed downstream 277 * @param refs An array of Appender names. 278 * @param properties Properties to pass to the Logger. 279 * @param config The Configuration. 280 * @param filter A Filter. 281 * @return A new LoggerConfig. 282 * @since 3.0 283 */ 284 @Deprecated 285 public static LoggerConfig createLogger( 286 @PluginAttribute(value = "additivity", defaultBoolean = true) final boolean additivity, 287 @PluginAttribute("level") final Level level, 288 @Required(message = "Loggers cannot be configured without a name") @PluginAttribute("name") final String loggerName, 289 @PluginAttribute("includeLocation") final String includeLocation, 290 @PluginElement("AppenderRef") final AppenderRef[] refs, 291 @PluginElement("Properties") final Property[] properties, 292 @PluginConfiguration final Configuration config, 293 @PluginElement("Filter") final Filter filter) { 294 final String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName; 295 return new AsyncLoggerConfig(name, Arrays.asList(refs), filter, level, additivity, properties, config, 296 includeLocation(includeLocation)); 297 } 298 299 // Note: for asynchronous loggers, includeLocation default is FALSE 300 protected static boolean includeLocation(final String includeLocationConfigValue) { 301 return Boolean.parseBoolean(includeLocationConfigValue); 302 } 303 304 /** 305 * An asynchronous root Logger. 306 */ 307 @Plugin(name = "asyncRoot", category = Core.CATEGORY_NAME, printObject = true) 308 public static class RootLogger extends LoggerConfig { 309 310 @PluginBuilderFactory 311 public static <B extends Builder<B>> B newAsyncRootBuilder() { 312 return new Builder<B>().asBuilder(); 313 } 314 315 public static class Builder<B extends Builder<B>> extends RootLogger.Builder<B> { 316 317 @Override 318 public LoggerConfig build() { 319 LevelAndRefs container = LoggerConfig.getLevelAndRefs(getLevel(), getRefs(), getLevelAndRefs(), 320 getConfig()); 321 return new AsyncLoggerConfig(LogManager.ROOT_LOGGER_NAME, container.refs, getFilter(), container.level, 322 isAdditivity(), getProperties(), getConfig(), includeLocation(getIncludeLocation())); 323 } 324 } 325 326 /** 327 * @deprecated use {@link #createLogger(String, Level, String, AppenderRef[], Property[], Configuration, Filter)} 328 */ 329 @Deprecated 330 public static LoggerConfig createLogger( 331 final String additivity, 332 final String levelName, 333 final String includeLocation, 334 final AppenderRef[] refs, 335 final Property[] properties, 336 final Configuration config, 337 final Filter filter) { 338 final List<AppenderRef> appenderRefs = Arrays.asList(refs); 339 Level level = null; 340 try { 341 level = Level.toLevel(levelName, Level.ERROR); 342 } catch (final Exception ex) { 343 LOGGER.error("Invalid Log level specified: {}. Defaulting to Error", levelName); 344 level = Level.ERROR; 345 } 346 final boolean additive = Booleans.parseBoolean(additivity, true); 347 return new AsyncLoggerConfig(LogManager.ROOT_LOGGER_NAME, 348 appenderRefs, filter, level, additive, properties, config, 349 AsyncLoggerConfig.includeLocation(includeLocation)); 350 } 351 352 /** 353 * 354 */ 355 @Deprecated 356 public static LoggerConfig createLogger( 357 @PluginAttribute("additivity") final String additivity, 358 @PluginAttribute("level") final Level level, 359 @PluginAttribute("includeLocation") final String includeLocation, 360 @PluginElement("AppenderRef") final AppenderRef[] refs, 361 @PluginElement("Properties") final Property[] properties, 362 @PluginConfiguration final Configuration config, 363 @PluginElement("Filter") final Filter filter) { 364 final List<AppenderRef> appenderRefs = Arrays.asList(refs); 365 final Level actualLevel = level == null ? Level.ERROR : level; 366 final boolean additive = Booleans.parseBoolean(additivity, true); 367 return new AsyncLoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs, filter, actualLevel, additive, 368 properties, config, AsyncLoggerConfig.includeLocation(includeLocation)); 369 } 370 } 371}