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.async;
18  
19  import java.util.Arrays;
20  import java.util.List;
21  import java.util.concurrent.TimeUnit;
22  
23  import org.apache.logging.log4j.Level;
24  import org.apache.logging.log4j.LogManager;
25  import org.apache.logging.log4j.core.Core;
26  import org.apache.logging.log4j.core.Filter;
27  import org.apache.logging.log4j.core.LogEvent;
28  import org.apache.logging.log4j.core.config.AppenderRef;
29  import org.apache.logging.log4j.core.config.Configuration;
30  import org.apache.logging.log4j.core.config.LoggerConfig;
31  import org.apache.logging.log4j.core.config.Node;
32  import org.apache.logging.log4j.core.config.Property;
33  import org.apache.logging.log4j.core.config.plugins.Plugin;
34  import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
35  import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
36  import org.apache.logging.log4j.core.config.plugins.PluginElement;
37  import org.apache.logging.log4j.core.config.plugins.PluginFactory;
38  import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
39  import org.apache.logging.log4j.core.jmx.RingBufferAdmin;
40  import org.apache.logging.log4j.core.util.Booleans;
41  import org.apache.logging.log4j.spi.AbstractLogger;
42  import org.apache.logging.log4j.util.Strings;
43  
44  /**
45   * Asynchronous Logger object that is created via configuration and can be
46   * combined with synchronous loggers.
47   * <p>
48   * AsyncLoggerConfig is a logger designed for high throughput and low latency
49   * logging. It does not perform any I/O in the calling (application) thread, but
50   * instead hands off the work to another thread as soon as possible. The actual
51   * logging is performed in the background thread. It uses the LMAX Disruptor
52   * library for inter-thread communication. (<a
53   * href="http://lmax-exchange.github.com/disruptor/"
54   * >http://lmax-exchange.github.com/disruptor/</a>)
55   * <p>
56   * To use AsyncLoggerConfig, specify {@code <asyncLogger>} or
57   * {@code <asyncRoot>} in configuration.
58   * <p>
59   * Note that for performance reasons, this logger does not include source
60   * location by default. You need to specify {@code includeLocation="true"} in
61   * the configuration or any %class, %location or %line conversion patterns in
62   * your log4j.xml configuration will produce either a "?" character or no output
63   * at all.
64   * <p>
65   * For best performance, use AsyncLoggerConfig with the RandomAccessFileAppender or
66   * RollingRandomAccessFileAppender, with immediateFlush=false. These appenders have
67   * built-in support for the batching mechanism used by the Disruptor library,
68   * and they will flush to disk at the end of each batch. This means that even
69   * with immediateFlush=false, there will never be any items left in the buffer;
70   * all log events will all be written to disk in a very efficient manner.
71   */
72  @Plugin(name = "asyncLogger", category = Node.CATEGORY, printObject = true)
73  public class AsyncLoggerConfig extends LoggerConfig {
74  
75      private static final ThreadLocal<Boolean> ASYNC_LOGGER_ENTERED = new ThreadLocal<Boolean>() {
76          @Override
77          protected Boolean initialValue() {
78              return Boolean.FALSE;
79          }
80      };
81  
82      private final AsyncLoggerConfigDelegate delegate;
83  
84      protected AsyncLoggerConfig(final String name,
85              final List<AppenderRef> appenders, final Filter filter,
86              final Level level, final boolean additive,
87              final Property[] properties, final Configuration config,
88              final boolean includeLocation) {
89          super(name, appenders, filter, level, additive, properties, config,
90                  includeLocation);
91          delegate = config.getAsyncLoggerConfigDelegate();
92          delegate.setLogEventFactory(getLogEventFactory());
93      }
94  
95      @Override
96      protected void log(final LogEvent event, final LoggerConfigPredicate predicate) {
97          // See LOG4J2-2301
98          if (predicate == LoggerConfigPredicate.ALL &&
99                  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 }