All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.logging.log4j.core.async.AsyncLogger Maven / Gradle / Ivy

There is a newer version: 1.1.1
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache license, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the license for the specific language governing permissions and
 * limitations under the license.
 */
package org.apache.logging.log4j.core.async;

import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
import org.apache.logging.log4j.core.jmx.RingBufferAdmin;
import org.apache.logging.log4j.core.util.Clock;
import org.apache.logging.log4j.core.util.ClockFactory;
import org.apache.logging.log4j.core.util.Integers;
import org.apache.logging.log4j.core.util.Loader;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.MessageFactory;
import org.apache.logging.log4j.message.TimestampMessage;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.PropertiesUtil;

import com.lmax.disruptor.BlockingWaitStrategy;
import com.lmax.disruptor.ExceptionHandler;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.SleepingWaitStrategy;
import com.lmax.disruptor.WaitStrategy;
import com.lmax.disruptor.YieldingWaitStrategy;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;

/**
 * AsyncLogger is a logger designed for high throughput and low latency logging.
 * It does not perform any I/O in the calling (application) thread, but instead
 * hands off the work to another thread as soon as possible. The actual logging
 * is performed in the background thread. It uses the LMAX Disruptor library for
 * inter-thread communication. (http://lmax-exchange.github.com/disruptor/)
 * 

* To use AsyncLogger, specify the System property * {@code -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector} * before you obtain a Logger, and all Loggers returned by LogManager.getLogger * will be AsyncLoggers. *

* Note that for performance reasons, this logger does not include source * location by default. You need to specify {@code includeLocation="true"} in * the configuration or any %class, %location or %line conversion patterns in * your log4j.xml configuration will produce either a "?" character or no output * at all. *

* For best performance, use AsyncLogger with the RandomAccessFileAppender or * RollingRandomAccessFileAppender, with immediateFlush=false. These appenders * have built-in support for the batching mechanism used by the Disruptor * library, and they will flush to disk at the end of each batch. This means * that even with immediateFlush=false, there will never be any items left in * the buffer; all log events will all be written to disk in a very efficient * manner. */ public class AsyncLogger extends Logger { private static final long serialVersionUID = 1L; private static final int SLEEP_MILLIS_BETWEEN_DRAIN_ATTEMPTS = 50; private static final int MAX_DRAIN_ATTEMPTS_BEFORE_SHUTDOWN = 200; private static final int RINGBUFFER_MIN_SIZE = 128; private static final int RINGBUFFER_DEFAULT_SIZE = 256 * 1024; private static final StatusLogger LOGGER = StatusLogger.getLogger(); private static final ThreadNameStrategy THREAD_NAME_STRATEGY = ThreadNameStrategy.create(); private static final ThreadLocal threadlocalInfo = new ThreadLocal(); static enum ThreadNameStrategy { // LOG4J2-467 CACHED { @Override public String getThreadName(final Info info) { return info.cachedThreadName; } }, UNCACHED { @Override public String getThreadName(final Info info) { return Thread.currentThread().getName(); } }; abstract String getThreadName(Info info); static ThreadNameStrategy create() { final String name = PropertiesUtil.getProperties().getStringProperty("AsyncLogger.ThreadNameStrategy", CACHED.name()); try { return ThreadNameStrategy.valueOf(name); } catch (final Exception ex) { LOGGER.debug("Using AsyncLogger.ThreadNameStrategy.CACHED: '{}' not valid: {}", name, ex.toString()); return CACHED; } } } private static volatile Disruptor disruptor; private static final Clock clock = ClockFactory.getClock(); private static final ExecutorService executor = Executors .newSingleThreadExecutor(new DaemonThreadFactory("AsyncLogger-")); static { initInfoForExecutorThread(); LOGGER.debug("AsyncLogger.ThreadNameStrategy={}", THREAD_NAME_STRATEGY); final int ringBufferSize = calculateRingBufferSize(); final WaitStrategy waitStrategy = createWaitStrategy(); disruptor = new Disruptor(RingBufferLogEvent.FACTORY, ringBufferSize, executor, ProducerType.MULTI, waitStrategy); disruptor.handleExceptionsWith(getExceptionHandler()); disruptor.handleEventsWith(new RingBufferLogEventHandler()); LOGGER.debug("Starting AsyncLogger disruptor with ringbuffer size {}...", disruptor.getRingBuffer() .getBufferSize()); disruptor.start(); } private static int calculateRingBufferSize() { int ringBufferSize = RINGBUFFER_DEFAULT_SIZE; final String userPreferredRBSize = PropertiesUtil.getProperties().getStringProperty("AsyncLogger.RingBufferSize", String.valueOf(ringBufferSize)); try { int size = Integer.parseInt(userPreferredRBSize); if (size < RINGBUFFER_MIN_SIZE) { size = RINGBUFFER_MIN_SIZE; LOGGER.warn("Invalid RingBufferSize {}, using minimum size {}.", userPreferredRBSize, RINGBUFFER_MIN_SIZE); } ringBufferSize = size; } catch (final Exception ex) { LOGGER.warn("Invalid RingBufferSize {}, using default size {}.", userPreferredRBSize, ringBufferSize); } return Integers.ceilingNextPowerOfTwo(ringBufferSize); } /** * Initialize an {@code Info} object that is threadlocal to the consumer/appender thread. * This Info object uniquely has attribute {@code isAppenderThread} set to {@code true}. * All other Info objects will have this attribute set to {@code false}. * This allows us to detect Logger.log() calls initiated from the appender thread, * which may cause deadlock when the RingBuffer is full. (LOG4J2-471) */ private static void initInfoForExecutorThread() { executor.submit(new Runnable(){ @Override public void run() { final boolean isAppenderThread = true; final Info info = new Info(new RingBufferLogEventTranslator(), // Thread.currentThread().getName(), isAppenderThread); threadlocalInfo.set(info); } }); } private static WaitStrategy createWaitStrategy() { final String strategy = PropertiesUtil.getProperties().getStringProperty("AsyncLogger.WaitStrategy"); LOGGER.debug("property AsyncLogger.WaitStrategy={}", strategy); if ("Sleep".equals(strategy)) { return new SleepingWaitStrategy(); } else if ("Yield".equals(strategy)) { return new YieldingWaitStrategy(); } else if ("Block".equals(strategy)) { return new BlockingWaitStrategy(); } LOGGER.debug("disruptor event handler uses BlockingWaitStrategy"); return new BlockingWaitStrategy(); } private static ExceptionHandler getExceptionHandler() { final String cls = PropertiesUtil.getProperties().getStringProperty("AsyncLogger.ExceptionHandler"); if (cls == null) { LOGGER.debug("No AsyncLogger.ExceptionHandler specified"); return null; } try { final ExceptionHandler result = Loader.newCheckedInstanceOf(cls, ExceptionHandler.class); LOGGER.debug("AsyncLogger.ExceptionHandler={}", result); return result; } catch (final Exception ignored) { LOGGER.debug("AsyncLogger.ExceptionHandler not set: error creating " + cls + ": ", ignored); return null; } } /** * Constructs an {@code AsyncLogger} with the specified context, name and * message factory. * * @param context context of this logger * @param name name of this logger * @param messageFactory message factory of this logger */ public AsyncLogger(final LoggerContext context, final String name, final MessageFactory messageFactory) { super(context, name, messageFactory); } /** * Tuple with the event translator and thread name for a thread. */ static class Info { private final RingBufferLogEventTranslator translator; private final String cachedThreadName; private final boolean isAppenderThread; public Info(final RingBufferLogEventTranslator translator, final String threadName, final boolean appenderThread) { this.translator = translator; this.cachedThreadName = threadName; this.isAppenderThread = appenderThread; } } @Override public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message, final Throwable thrown) { // TODO refactor to reduce size to <= 35 bytecodes to allow JVM to inline it Info info = threadlocalInfo.get(); if (info == null) { info = new Info(new RingBufferLogEventTranslator(), Thread.currentThread().getName(), false); threadlocalInfo.set(info); } final Disruptor temp = disruptor; if (temp == null) { // LOG4J2-639 LOGGER.fatal("Ignoring log event after log4j was shut down"); return; } // LOG4J2-471: prevent deadlock when RingBuffer is full and object // being logged calls Logger.log() from its toString() method if (info.isAppenderThread && temp.getRingBuffer().remainingCapacity() == 0) { // bypass RingBuffer and invoke Appender directly config.loggerConfig.log(getName(), fqcn, marker, level, message, thrown); return; } message.getFormattedMessage(); // LOG4J2-763: ask message to freeze parameters final boolean includeLocation = config.loggerConfig.isIncludeLocation(); info.translator.setValues(this, getName(), marker, fqcn, level, message, // // don't construct ThrowableProxy until required thrown, // // config properties are taken care of in the EventHandler // thread in the #actualAsyncLog method // needs shallow copy to be fast (LOG4J2-154) ThreadContext.getImmutableContext(), // // needs shallow copy to be fast (LOG4J2-154) ThreadContext.getImmutableStack(), // // Thread.currentThread().getName(), // // info.cachedThreadName, // THREAD_NAME_STRATEGY.getThreadName(info), // LOG4J2-467 // location: very expensive operation. LOG4J2-153: // Only include if "includeLocation=true" is specified, // exclude if not specified or if "false" was specified. includeLocation ? location(fqcn) : null, // System.currentTimeMillis()); // CoarseCachedClock: 20% faster than system clock, 16ms gaps // CachedClock: 10% faster than system clock, smaller gaps // LOG4J2-744 avoid calling clock altogether if message has the timestamp message instanceof TimestampMessage ? ((TimestampMessage) message).getTimestamp() : clock.currentTimeMillis()); // LOG4J2-639: catch NPE if disruptor field was set to null after our check above try { // Note: do NOT use the temp variable above! // That could result in adding a log event to the disruptor after it was shut down, // which could cause the publishEvent method to hang and never return. disruptor.publishEvent(info.translator); } catch (final NullPointerException npe) { LOGGER.fatal("Ignoring log event after log4j was shut down."); } } private static StackTraceElement location(final String fqcnOfLogger) { return Log4jLogEvent.calcLocation(fqcnOfLogger); } /** * This method is called by the EventHandler that processes the * RingBufferLogEvent in a separate thread. * * @param event the event to log */ public void actualAsyncLog(final RingBufferLogEvent event) { final Map properties = config.loggerConfig.getProperties(); event.mergePropertiesIntoContextMap(properties, config.config.getStrSubstitutor()); config.logEvent(event); } public static void stop() { final Disruptor temp = disruptor; // Must guarantee that publishing to the RingBuffer has stopped // before we call disruptor.shutdown() disruptor = null; // client code fails with NPE if log after stop = OK if (temp == null) { return; // stop() has already been called } // Calling Disruptor.shutdown() will wait until all enqueued events are fully processed, // but this waiting happens in a busy-spin. To avoid (postpone) wasting CPU, // we sleep in short chunks, up to 10 seconds, waiting for the ringbuffer to drain. for (int i = 0; hasBacklog(temp) && i < MAX_DRAIN_ATTEMPTS_BEFORE_SHUTDOWN; i++) { try { Thread.sleep(SLEEP_MILLIS_BETWEEN_DRAIN_ATTEMPTS); // give up the CPU for a while } catch (final InterruptedException e) { // ignored } } temp.shutdown(); // busy-spins until all events currently in the disruptor have been processed executor.shutdown(); // finally, kill the processor thread threadlocalInfo.remove(); // LOG4J2-323 } /** * Returns {@code true} if the specified disruptor still has unprocessed events. */ private static boolean hasBacklog(final Disruptor disruptor) { final RingBuffer ringBuffer = disruptor.getRingBuffer(); return !ringBuffer.hasAvailableCapacity(ringBuffer.getBufferSize()); } /** * Creates and returns a new {@code RingBufferAdmin} that instruments the * ringbuffer of the {@code AsyncLogger}. * * @param contextName name of the global {@code AsyncLoggerContext} */ public static RingBufferAdmin createRingBufferAdmin(final String contextName) { return RingBufferAdmin.forAsyncLogger(disruptor.getRingBuffer(), contextName); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy