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