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.Logger; 029import org.apache.logging.log4j.core.config.AppenderRef; 030import org.apache.logging.log4j.core.config.Configuration; 031import org.apache.logging.log4j.core.config.LoggerConfig; 032import org.apache.logging.log4j.core.config.Node; 033import org.apache.logging.log4j.core.config.Property; 034import org.apache.logging.log4j.core.config.plugins.Plugin; 035import org.apache.logging.log4j.core.config.plugins.PluginAttribute; 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.impl.Log4jLogEvent; 040import org.apache.logging.log4j.core.jmx.RingBufferAdmin; 041import org.apache.logging.log4j.core.util.Booleans; 042import org.apache.logging.log4j.message.Message; 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 the LMAX Disruptor 053 * library for inter-thread communication. (<a 054 * href="http://lmax-exchange.github.com/disruptor/" 055 * >http://lmax-exchange.github.com/disruptor/</a>) 056 * <p> 057 * To use AsyncLoggerConfig, specify {@code <asyncLogger>} or 058 * {@code <asyncRoot>} in configuration. 059 * <p> 060 * Note that for performance reasons, this logger does not include source 061 * location by default. You need to specify {@code includeLocation="true"} in 062 * the configuration or any %class, %location or %line conversion patterns in 063 * your log4j.xml configuration will produce either a "?" character or no output 064 * at all. 065 * <p> 066 * For best performance, use AsyncLoggerConfig with the RandomAccessFileAppender or 067 * RollingRandomAccessFileAppender, with immediateFlush=false. These appenders have 068 * built-in support for the batching mechanism used by the Disruptor library, 069 * and they will flush to disk at the end of each batch. This means that even 070 * with immediateFlush=false, there will never be any items left in the buffer; 071 * all log events will all be written to disk in a very efficient manner. 072 */ 073@Plugin(name = "asyncLogger", category = Node.CATEGORY, printObject = true) 074public class AsyncLoggerConfig extends LoggerConfig { 075 076 private final AsyncLoggerConfigDelegate delegate; 077 078 protected AsyncLoggerConfig(final String name, 079 final List<AppenderRef> appenders, final Filter filter, 080 final Level level, final boolean additive, 081 final Property[] properties, final Configuration config, 082 final boolean includeLocation) { 083 super(name, appenders, filter, level, additive, properties, config, 084 includeLocation); 085 delegate = config.getAsyncLoggerConfigDelegate(); 086 delegate.setLogEventFactory(getLogEventFactory()); 087 } 088 089 /** 090 * Passes on the event to a separate thread that will call 091 * {@link #asyncCallAppenders(LogEvent)}. 092 */ 093 @Override 094 protected void callAppenders(final LogEvent event) { 095 populateLazilyInitializedFields(event); 096 097 if (!delegate.tryEnqueue(event, this)) { 098 handleQueueFull(event); 099 } 100 } 101 102 private void handleQueueFull(final LogEvent event) { 103 if (Logger.getRecursionDepth() > 1) { // LOG4J2-1518, LOG4J2-2031 104 // If queue is full AND we are in a recursive call, call appender directly to prevent deadlock 105 final Message message = AsyncQueueFullMessageUtil.transform(event.getMessage()); 106 callAppendersInCurrentThread(new Log4jLogEvent.Builder(event).setMessage(message).build()); 107 } else { 108 // otherwise, we leave it to the user preference 109 final EventRoute eventRoute = delegate.getEventRoute(event.getLevel()); 110 eventRoute.logMessage(this, event); 111 } 112 } 113 114 private void populateLazilyInitializedFields(final LogEvent event) { 115 event.getSource(); 116 event.getThreadName(); 117 } 118 119 void callAppendersInCurrentThread(final LogEvent event) { 120 super.callAppenders(event); 121 } 122 123 void callAppendersInBackgroundThread(final LogEvent event) { 124 delegate.enqueueEvent(event, this); 125 } 126 127 /** Called by AsyncLoggerConfigHelper.RingBufferLog4jEventHandler. */ 128 void asyncCallAppenders(final LogEvent event) { 129 super.callAppenders(event); 130 } 131 132 private String displayName() { 133 return LogManager.ROOT_LOGGER_NAME.equals(getName()) ? LoggerConfig.ROOT : getName(); 134 } 135 136 @Override 137 public void start() { 138 LOGGER.trace("AsyncLoggerConfig[{}] starting...", displayName()); 139 super.start(); 140 } 141 142 @Override 143 public boolean stop(final long timeout, final TimeUnit timeUnit) { 144 setStopping(); 145 super.stop(timeout, timeUnit, false); 146 LOGGER.trace("AsyncLoggerConfig[{}] stopping...", displayName()); 147 setStopped(); 148 return true; 149 } 150 151 /** 152 * Creates and returns a new {@code RingBufferAdmin} that instruments the 153 * ringbuffer of this {@code AsyncLoggerConfig}. 154 * 155 * @param contextName name of the {@code LoggerContext} 156 * @return a new {@code RingBufferAdmin} that instruments the ringbuffer 157 */ 158 public RingBufferAdmin createRingBufferAdmin(final String contextName) { 159 return delegate.createRingBufferAdmin(contextName, getName()); 160 } 161 162 /** 163 * Factory method to create a LoggerConfig. 164 * 165 * @param additivity True if additive, false otherwise. 166 * @param levelName The Level to be associated with the Logger. 167 * @param loggerName The name of the Logger. 168 * @param includeLocation "true" if location should be passed downstream 169 * @param refs An array of Appender names. 170 * @param properties Properties to pass to the Logger. 171 * @param config The Configuration. 172 * @param filter A Filter. 173 * @return A new LoggerConfig. 174 */ 175 @PluginFactory 176 public static LoggerConfig createLogger( 177 @PluginAttribute("additivity") final String additivity, 178 @PluginAttribute("level") final String levelName, 179 @PluginAttribute("name") final String loggerName, 180 @PluginAttribute("includeLocation") final String includeLocation, 181 @PluginElement("AppenderRef") final AppenderRef[] refs, 182 @PluginElement("Properties") final Property[] properties, 183 @PluginConfiguration final Configuration config, 184 @PluginElement("Filter") final Filter filter) { 185 if (loggerName == null) { 186 LOGGER.error("Loggers cannot be configured without a name"); 187 return null; 188 } 189 190 final List<AppenderRef> appenderRefs = Arrays.asList(refs); 191 Level level; 192 try { 193 level = Level.toLevel(levelName, Level.ERROR); 194 } catch (final Exception ex) { 195 LOGGER.error( 196 "Invalid Log level specified: {}. Defaulting to Error", 197 levelName); 198 level = Level.ERROR; 199 } 200 final String name = loggerName.equals(LoggerConfig.ROOT) ? Strings.EMPTY : loggerName; 201 final boolean additive = Booleans.parseBoolean(additivity, true); 202 203 return new AsyncLoggerConfig(name, appenderRefs, filter, level, 204 additive, properties, config, includeLocation(includeLocation)); 205 } 206 207 // Note: for asynchronous loggers, includeLocation default is FALSE 208 protected static boolean includeLocation(final String includeLocationConfigValue) { 209 return Boolean.parseBoolean(includeLocationConfigValue); 210 } 211 212 /** 213 * An asynchronous root Logger. 214 */ 215 @Plugin(name = "asyncRoot", category = Core.CATEGORY_NAME, printObject = true) 216 public static class RootLogger extends LoggerConfig { 217 218 @PluginFactory 219 public static LoggerConfig createLogger( 220 @PluginAttribute("additivity") final String additivity, 221 @PluginAttribute("level") final String levelName, 222 @PluginAttribute("includeLocation") final String includeLocation, 223 @PluginElement("AppenderRef") final AppenderRef[] refs, 224 @PluginElement("Properties") final Property[] properties, 225 @PluginConfiguration final Configuration config, 226 @PluginElement("Filter") final Filter filter) { 227 final List<AppenderRef> appenderRefs = Arrays.asList(refs); 228 Level level; 229 try { 230 level = Level.toLevel(levelName, Level.ERROR); 231 } catch (final Exception ex) { 232 LOGGER.error( 233 "Invalid Log level specified: {}. Defaulting to Error", 234 levelName); 235 level = Level.ERROR; 236 } 237 final boolean additive = Booleans.parseBoolean(additivity, true); 238 239 return new AsyncLoggerConfig(LogManager.ROOT_LOGGER_NAME, 240 appenderRefs, filter, level, additive, properties, config, 241 AsyncLoggerConfig.includeLocation(includeLocation)); 242 } 243 } 244}