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.Logger;
29  import org.apache.logging.log4j.core.config.AppenderRef;
30  import org.apache.logging.log4j.core.config.Configuration;
31  import org.apache.logging.log4j.core.config.LoggerConfig;
32  import org.apache.logging.log4j.core.config.Node;
33  import org.apache.logging.log4j.core.config.Property;
34  import org.apache.logging.log4j.core.config.plugins.Plugin;
35  import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
36  import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
37  import org.apache.logging.log4j.core.config.plugins.PluginElement;
38  import org.apache.logging.log4j.core.config.plugins.PluginFactory;
39  import org.apache.logging.log4j.core.impl.Log4jLogEvent;
40  import org.apache.logging.log4j.core.jmx.RingBufferAdmin;
41  import org.apache.logging.log4j.core.util.Booleans;
42  import org.apache.logging.log4j.message.Message;
43  import org.apache.logging.log4j.util.Strings;
44  
45  /**
46   * Asynchronous Logger object that is created via configuration and can be
47   * combined with synchronous loggers.
48   * <p>
49   * AsyncLoggerConfig is a logger designed for high throughput and low latency
50   * logging. It does not perform any I/O in the calling (application) thread, but
51   * instead hands off the work to another thread as soon as possible. The actual
52   * logging is performed in the background thread. It uses the LMAX Disruptor
53   * library for inter-thread communication. (<a
54   * href="http://lmax-exchange.github.com/disruptor/"
55   * >http://lmax-exchange.github.com/disruptor/</a>)
56   * <p>
57   * To use AsyncLoggerConfig, specify {@code <asyncLogger>} or
58   * {@code <asyncRoot>} in configuration.
59   * <p>
60   * Note that for performance reasons, this logger does not include source
61   * location by default. You need to specify {@code includeLocation="true"} in
62   * the configuration or any %class, %location or %line conversion patterns in
63   * your log4j.xml configuration will produce either a "?" character or no output
64   * at all.
65   * <p>
66   * For best performance, use AsyncLoggerConfig with the RandomAccessFileAppender or
67   * RollingRandomAccessFileAppender, with immediateFlush=false. These appenders have
68   * built-in support for the batching mechanism used by the Disruptor library,
69   * and they will flush to disk at the end of each batch. This means that even
70   * with immediateFlush=false, there will never be any items left in the buffer;
71   * all log events will all be written to disk in a very efficient manner.
72   */
73  @Plugin(name = "asyncLogger", category = Node.CATEGORY, printObject = true)
74  public class AsyncLoggerConfig extends LoggerConfig {
75  
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      /**
90       * Passes on the event to a separate thread that will call
91       * {@link #asyncCallAppenders(LogEvent)}.
92       */
93      @Override
94      protected void callAppenders(final LogEvent event) {
95          populateLazilyInitializedFields(event);
96  
97          if (!delegate.tryEnqueue(event, this)) {
98              handleQueueFull(event);
99          }
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 }