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

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

There is a newer version: 01.02.00-RPM
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.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.jmx.RingBufferAdmin;
import org.apache.logging.log4j.core.util.Constants;
import org.apache.logging.log4j.status.StatusLogger;

import com.lmax.disruptor.EventFactory;
import com.lmax.disruptor.EventTranslatorTwoArg;
import com.lmax.disruptor.ExceptionHandler;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.Sequence;
import com.lmax.disruptor.SequenceReportingEventHandler;
import com.lmax.disruptor.WaitStrategy;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;

/**
 * Helper class decoupling the {@code AsyncLoggerConfig} class from the LMAX Disruptor library.
 * 

* {@code AsyncLoggerConfig} is a plugin, and will be loaded even if users do not configure any {@code } or * {@code } elements in the configuration. If {@code AsyncLoggerConfig} has inner classes that extend or * implement classes from the Disruptor library, a {@code NoClassDefFoundError} is thrown if the Disruptor jar is not in * the classpath when the PluginManager loads the {@code AsyncLoggerConfig} plugin from the pre-defined plugins * definition file. *

* This class serves to make the dependency on the Disruptor optional, so that these classes are only loaded when the * {@code AsyncLoggerConfig} is actually used. */ public class AsyncLoggerConfigDisruptor implements AsyncLoggerConfigDelegate { private static final int MAX_DRAIN_ATTEMPTS_BEFORE_SHUTDOWN = 200; private static final int SLEEP_MILLIS_BETWEEN_DRAIN_ATTEMPTS = 50; private static final Logger LOGGER = StatusLogger.getLogger(); /** * RingBuffer events contain all information necessary to perform the work in a separate thread. */ private static class Log4jEventWrapper { private AsyncLoggerConfig loggerConfig; private LogEvent event; /** * Release references held by ring buffer to allow objects to be garbage-collected. */ public void clear() { loggerConfig = null; event = null; } } /** * EventHandler performs the work in a separate thread. */ private static class Log4jEventWrapperHandler implements SequenceReportingEventHandler { private static final int NOTIFY_PROGRESS_THRESHOLD = 50; private Sequence sequenceCallback; private int counter; @Override public void setSequenceCallback(final Sequence sequenceCallback) { this.sequenceCallback = sequenceCallback; } @Override public void onEvent(final Log4jEventWrapper event, final long sequence, final boolean endOfBatch) throws Exception { event.event.setEndOfBatch(endOfBatch); event.loggerConfig.asyncCallAppenders(event.event); event.clear(); notifyIntermediateProgress(sequence); } /** * Notify the BatchEventProcessor that the sequence has progressed. Without this callback the sequence would not * be progressed until the batch has completely finished. */ private void notifyIntermediateProgress(final long sequence) { if (++counter > NOTIFY_PROGRESS_THRESHOLD) { sequenceCallback.set(sequence); counter = 0; } } } /** * Factory used to populate the RingBuffer with events. These event objects are then re-used during the life of the * RingBuffer. */ private static final EventFactory FACTORY = new EventFactory() { @Override public Log4jEventWrapper newInstance() { return new Log4jEventWrapper(); } }; /** * Object responsible for passing on data to a specific RingBuffer event. */ private static final EventTranslatorTwoArg TRANSLATOR = new EventTranslatorTwoArg() { @Override public void translateTo(final Log4jEventWrapper ringBufferElement, final long sequence, final LogEvent logEvent, final AsyncLoggerConfig loggerConfig) { ringBufferElement.event = logEvent; ringBufferElement.loggerConfig = loggerConfig; } }; private static final ThreadFactory THREAD_FACTORY = new DaemonThreadFactory("AsyncLoggerConfig-"); private volatile Disruptor disruptor; private ExecutorService executor; private long backgroundThreadId; // LOG4J2-471 public AsyncLoggerConfigDisruptor() { } /** * Increases the reference count and creates and starts a new Disruptor and associated thread if none currently * exists. * * @see #release() */ public synchronized void start() { if (disruptor != null) { LOGGER.trace("AsyncLoggerConfigHelper not starting new disruptor for this configuration, " + "using existing object."); return; } LOGGER.trace("AsyncLoggerConfigHelper creating new disruptor for this configuration."); final int ringBufferSize = DisruptorUtil.calculateRingBufferSize("AsyncLoggerConfig.RingBufferSize"); final WaitStrategy waitStrategy = DisruptorUtil.createWaitStrategy("AsyncLoggerConfig.WaitStrategy"); executor = Executors.newSingleThreadExecutor(THREAD_FACTORY); backgroundThreadId = DisruptorUtil.getExecutorThreadId(executor); disruptor = new Disruptor<>(FACTORY, ringBufferSize, executor, ProducerType.MULTI, waitStrategy); final ExceptionHandler errorHandler = DisruptorUtil.getExceptionHandler( "AsyncLoggerConfig.ExceptionHandler", Log4jEventWrapper.class); disruptor.handleExceptionsWith(errorHandler); final Log4jEventWrapperHandler[] handlers = {new Log4jEventWrapperHandler()}; disruptor.handleEventsWith(handlers); LOGGER.debug("Starting AsyncLoggerConfig disruptor for this configuration with ringbufferSize={}, " + "waitStrategy={}, exceptionHandler={}...", disruptor.getRingBuffer().getBufferSize(), waitStrategy .getClass().getSimpleName(), errorHandler); disruptor.start(); } /** * Decreases the reference count. If the reference count reached zero, the Disruptor and its associated thread are * shut down and their references set to {@code null}. */ public synchronized void stop() { final Disruptor temp = disruptor; if (temp == null) { LOGGER.trace("AsyncLoggerConfigHelper: disruptor for this configuration already shut down."); return; // disruptor was already shut down by another thread } LOGGER.trace("AsyncLoggerConfigHelper: shutting down disruptor for this configuration."); // We 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 // 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 LOGGER.trace("AsyncLoggerConfigHelper: shutting down disruptor executor for this configuration."); executor.shutdown(); // finally, kill the processor thread executor = null; // release reference to allow GC } /** * Returns {@code true} if the specified disruptor still has unprocessed events. */ private static boolean hasBacklog(final Disruptor theDisruptor) { final RingBuffer ringBuffer = theDisruptor.getRingBuffer(); return !ringBuffer.hasAvailableCapacity(ringBuffer.getBufferSize()); } /* * (non-Javadoc) * * @see * org.apache.logging.log4j.core.async.AsyncLoggerConfigDelegate#tryCallAppendersInBackground(org.apache.logging * .log4j.core.LogEvent) */ @Override public boolean tryCallAppendersInBackground(final LogEvent event, final AsyncLoggerConfig asyncLoggerConfig) { final Disruptor temp = disruptor; if (!hasLog4jBeenShutDown(temp)) { // LOG4J2-471: prevent deadlock when RingBuffer is full and object // being logged calls Logger.log() from its toString() method if (isCalledFromAppenderThreadAndBufferFull(temp)) { // bypass RingBuffer and invoke Appender directly return false; } enqueueEvent(event, asyncLoggerConfig); } return true; } /** * Returns {@code true} if the specified disruptor is null. */ private boolean hasLog4jBeenShutDown(final Disruptor aDisruptor) { if (aDisruptor == null) { // LOG4J2-639 LOGGER.fatal("Ignoring log event after log4j was shut down"); return true; } return false; } private void enqueueEvent(final LogEvent event, final AsyncLoggerConfig asyncLoggerConfig) { // LOG4J2-639: catch NPE if disruptor field was set to null after our check above try { final LogEvent logEvent = prepareEvent(event); enqueue(logEvent, asyncLoggerConfig); } catch (final NullPointerException npe) { LOGGER.fatal("Ignoring log event after log4j was shut down."); } } private LogEvent prepareEvent(final LogEvent event) { final LogEvent logEvent = ensureImmutable(event); if (!Constants.FORMAT_MESSAGES_IN_BACKGROUND) { // LOG4J2-898: user may choose logEvent.getMessage().getFormattedMessage(); // LOG4J2-763: ask message to freeze parameters } return logEvent; } private void enqueue(final LogEvent logEvent, final AsyncLoggerConfig asyncLoggerConfig) { // 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.getRingBuffer().publishEvent(TRANSLATOR, logEvent, asyncLoggerConfig); } private LogEvent ensureImmutable(final LogEvent event) { LogEvent result = event; if (event instanceof RingBufferLogEvent) { // Deal with special case where both types of Async Loggers are used together: // RingBufferLogEvents are created by the all-loggers-async type, but // this event is also consumed by the some-loggers-async type (this class). // The original event will be re-used and modified in an application thread later, // so take a snapshot of it, which can be safely processed in the // some-loggers-async background thread. result = ((RingBufferLogEvent) event).createMemento(); } return result; } /** * Returns true if the specified ringbuffer is full and the Logger.log() call was made from the appender thread. */ private boolean isCalledFromAppenderThreadAndBufferFull(Disruptor theDisruptor) { return currentThreadIsAppenderThread() && theDisruptor.getRingBuffer().remainingCapacity() == 0; } /** * Returns {@code true} if the current thread is the Disruptor background thread, {@code false} otherwise. * * @return whether this thread is the Disruptor background thread. */ private boolean currentThreadIsAppenderThread() { return Thread.currentThread().getId() == backgroundThreadId; } /* * (non-Javadoc) * * @see org.apache.logging.log4j.core.async.AsyncLoggerConfigDelegate#createRingBufferAdmin(java.lang.String, * java.lang.String) */ @Override public RingBufferAdmin createRingBufferAdmin(final String contextName, final String loggerConfigName) { return RingBufferAdmin.forAsyncLoggerConfig(disruptor.getRingBuffer(), contextName, loggerConfigName); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy