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