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

net.logstash.logback.appender.AsyncDisruptorAppender Maven / Gradle / Ivy

Go to download

Provides logback encoders, layouts, and appenders to log in JSON and other formats supported by Jackson

There is a newer version: 8.0
Show newest version
/**
 * Licensed 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 net.logstash.logback.appender;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import ch.qos.logback.access.spi.IAccessEvent;
import ch.qos.logback.classic.AsyncAppender;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import ch.qos.logback.core.spi.DeferredProcessingAware;

import com.lmax.disruptor.BlockingWaitStrategy;
import com.lmax.disruptor.EventFactory;
import com.lmax.disruptor.EventHandler;
import com.lmax.disruptor.EventTranslatorOneArg;
import com.lmax.disruptor.ExceptionHandler;
import com.lmax.disruptor.LifecycleAware;
import com.lmax.disruptor.PhasedBackoffWaitStrategy;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.SleepingWaitStrategy;
import com.lmax.disruptor.TimeoutException;
import com.lmax.disruptor.WaitStrategy;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;

/**
 * An asynchronous appender that uses an LMAX Disruptor {@link RingBuffer}
 * as the interthread data exchange mechanism (as opposed to a {@link BlockingQueue}
 * used by logback's {@link AsyncAppender}).
 * 

* * See the LMAX Disruptor documentation * for more information about the advantages of using a {@link RingBuffer} over a {@link BlockingQueue}. *

* * This appender will never block the logging thread, since it uses * {@link RingBuffer#tryPublishEvent(EventTranslatorOneArg, Object)} * to publish events (rather than {@link RingBuffer#publishEvent(EventTranslatorOneArg, Object)}). *

* * If the RingBuffer is full, and the event cannot be published, * the event will be dropped. A warning message will be logged to * logback's context every {@link #droppedWarnFrequency} consecutive dropped events. *

* * A single handler thread will be used to handle the actual handling of the event. *

* * Subclasses are required to set the {@link #eventHandler} to define * the logic that executes in the handler thread. * For example, {@link DelegatingAsyncDisruptorAppender} for will delegate * appending of the event to another appender in the handler thread. *

* * By default, child threads created by this appender will be daemon threads, * and therefore allow the JVM to exit gracefully without * needing to explicitly shut down the appender. * Note that in this case, it is possible for appended log events to not * be handled (if the child thread has not had a chance to process them yet). * * By setting {@link #setDaemon(boolean)} to false, you can change this behavior. * When false, child threads created by this appender will not be daemon threads, * and therefore will prevent the JVM from shutting down * until the appender is explicitly shut down. * Set this to false if you want to ensure that every log event * prior to shutdown is handled. * * @param type of event ({@link ILoggingEvent} or {@link IAccessEvent}). */ public abstract class AsyncDisruptorAppender extends UnsynchronizedAppenderBase { public static final int DEFAULT_RING_BUFFER_SIZE = 8192; public static final ProducerType DEFAULT_PRODUCER_TYPE = ProducerType.MULTI; public static final WaitStrategy DEFAULT_WAIT_STRATEGY = new BlockingWaitStrategy(); public static final String DEFAULT_THREAD_NAME_PREFIX = "logback-async-disruptor-appender-"; public static final int DEFAULT_DROPPED_WARN_FREQUENCY = 1000; /** * The size of the {@link RingBuffer}. * Defaults to {@value #DEFAULT_RING_BUFFER_SIZE}. * If the handler thread is not as fast as the producing threads, * then the {@link RingBuffer} will eventually fill up, * at which point events will be dropped. *

* Must be a positive power of 2. */ private int ringBufferSize = DEFAULT_RING_BUFFER_SIZE; /** * The {@link ProducerType} to use to configure the disruptor. * By default this is {@link ProducerType#MULTI}. * Only set to {@link ProducerType#SINGLE} if only one thread * will ever be appending to this appender. */ private ProducerType producerType = DEFAULT_PRODUCER_TYPE; /** * The {@link WaitStrategy} to used by the RingBuffer * when pulling events to be processed by {@link #eventHandler}. *

* By default, a {@link BlockingWaitStrategy} is used, which is the most * CPU conservative, but results in a higher latency. * If you need lower latency (at the cost of higher CPU usage), * consider using a {@link SleepingWaitStrategy} or a {@link PhasedBackoffWaitStrategy}. */ private WaitStrategy waitStrategy = DEFAULT_WAIT_STRATEGY; /** * Used as a prefix by the {@link WorkerThreadFactory} to set the * handler thread name. * Defaults to {@value #DEFAULT_THREAD_NAME_PREFIX}. *

* * If you change the {@link #threadFactory}, then this * value may not be honored. */ private String threadNamePrefix = DEFAULT_THREAD_NAME_PREFIX; /** * When true, child threads created by this appender will be daemon threads, * and therefore allow the JVM to exit gracefully without * needing to explicitly shut down the appender. * Note that in this case, it is possible for log events to not * be handled. *

* * When false, child threads created by this appender will not be daemon threads, * and therefore will prevent the JVM from shutting down * until the appender is explicitly shut down. * Set this to false if you want to ensure that every log event * prior to shutdown is handled. *

* * If you change the {@link #threadFactory}, then this * value may not be honored. */ private boolean useDaemonThread = true; /** * For every droppedWarnFrequency consecutive dropped events, log a warning. * Defaults to {@value #DEFAULT_DROPPED_WARN_FREQUENCY}. */ private int droppedWarnFrequency = DEFAULT_DROPPED_WARN_FREQUENCY; /** * The {@link ThreadFactory} used to create the handler thread. */ private ThreadFactory threadFactory = new WorkerThreadFactory(); /** * The {@link ScheduledExecutorService} used to execute the handler task. */ private ScheduledExecutorService executorService; /** * Size of the thread pool to create. */ private int threadPoolCoreSize = 1; /** * The {@link Disruptor} containing the {@link RingBuffer} onto * which to publish events. */ private Disruptor> disruptor; /** * Sets the {@link LogEvent#event} to the logback Event. * Used when publishing events to the {@link RingBuffer}. */ private EventTranslatorOneArg, Event> eventTranslator = new LogEventTranslator(); /** * Used by the handler thread to process the event. */ private EventHandler> eventHandler; /** * Defines what happens when there is an exception during * {@link RingBuffer} processing. */ private ExceptionHandler exceptionHandler = new LogEventExceptionHandler(); /** * Consecutive number of dropped events. */ private final AtomicLong consecutiveDroppedCount = new AtomicLong(); /** * The {@link EventFactory} used to create {@link LogEvent}s for the RingBuffer. */ private LogEventFactory eventFactory = new LogEventFactory(); /** * Event wrapper object used for each element of the {@link RingBuffer}. */ protected static class LogEvent { /** * The logback event. */ public volatile Event event; } /** * Factory for creating the initial {@link LogEvent}s to populate * the {@link RingBuffer}. */ private static class LogEventFactory implements EventFactory> { @Override public LogEvent newInstance() { return new LogEvent(); } } /** * The default {@link ThreadFactory} used to create the handler thread. */ private class WorkerThreadFactory implements ThreadFactory { private final AtomicInteger threadNumber = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setName(threadNamePrefix + threadNumber.getAndIncrement()); t.setDaemon(useDaemonThread); return t; } } /** * Sets the {@link LogEvent#event} to the logback Event. * Used when publishing events to the {@link RingBuffer}. */ protected static class LogEventTranslator implements EventTranslatorOneArg, Event> { @Override public void translateTo(LogEvent logEvent, long sequence, Event event) { logEvent.event = event; } } /** * Defines what happens when there is an exception during * {@link RingBuffer} processing. * * Currently, just logs to the logback context. */ private class LogEventExceptionHandler implements ExceptionHandler { @Override public void handleEventException(Throwable ex, long sequence, Object event) { addError("Unable to process event: " + ex.getMessage(), ex); } @Override public void handleOnStartException(Throwable ex) { addError("Unable start disruptor", ex); } @Override public void handleOnShutdownException(Throwable ex) { addError("Unable shutdown disruptor", ex); } } /** * Clears the event after a delegate event handler has processed the event, * so that the event can be garbage collected. */ private static class EventClearingEventHandler implements EventHandler>, LifecycleAware { private final EventHandler> delegate; public EventClearingEventHandler(EventHandler> delegate) { super(); this.delegate = delegate; } @Override public void onEvent(LogEvent event, long sequence, boolean endOfBatch) throws Exception { try { delegate.onEvent(event, sequence, endOfBatch); } finally { /* * Clear the event so that it can be garbage collected. */ event.event = null; } } @Override public void onStart() { if (delegate instanceof LifecycleAware) { ((LifecycleAware) delegate).onStart(); } } @Override public void onShutdown() { if (delegate instanceof LifecycleAware) { ((LifecycleAware) delegate).onShutdown(); } } } @SuppressWarnings("unchecked") @Override public void start() { if (this.eventHandler == null) { addError("No eventHandler was configured for appender " + name + "."); return; } this.executorService = Executors.newScheduledThreadPool( getThreadPoolCoreSize(), this.threadFactory); this.disruptor = new Disruptor>( this.eventFactory, this.ringBufferSize, this.executorService, this.producerType, this.waitStrategy); /* * Define the exceptionHandler first, so that it applies * to all future eventHandlers. */ this.disruptor.handleExceptionsWith(this.exceptionHandler); this.disruptor.handleEventsWith(new EventClearingEventHandler(this.eventHandler)); this.disruptor.start(); super.start(); } @Override public void stop() { /* * Check super.isStarted() instead of isStarted() because subclasses * might override isStarted() to perform other comparisons that we don't * want to check here. Those should be checked by subclasses * prior to calling super.stop() */ if (!super.isStarted()) { return; } /* * Don't allow any more events to be appended. */ super.stop(); try { this.disruptor.shutdown(1, TimeUnit.MINUTES); } catch (TimeoutException e) { addWarn("Some queued events have not been logged due to requested shutdown"); } this.executorService.shutdown(); try { if (!this.executorService.awaitTermination(1, TimeUnit.MINUTES)) { addWarn("Some queued events have not been logged due to requested shutdown"); } } catch (InterruptedException e) { addWarn("Some queued events have not been logged due to requested shutdown", e); } } @Override protected void append(Event event) { prepareForDeferredProcessing(event); if (!this.disruptor.getRingBuffer().tryPublishEvent(this.eventTranslator, event)) { long consecutiveDropped = this.consecutiveDroppedCount.incrementAndGet(); if ((consecutiveDropped) % this.droppedWarnFrequency == 1) { addWarn("Dropped " + consecutiveDropped + " events (and counting...) due to ring buffer at max capacity [" + this.ringBufferSize + "]"); } } else { long consecutiveDropped = this.consecutiveDroppedCount.get(); if (consecutiveDropped != 0 && this.consecutiveDroppedCount.compareAndSet(consecutiveDropped, 0L)) { addWarn("Dropped " + consecutiveDropped + " total events due to ring buffer at max capacity [" + this.ringBufferSize + "]"); } } } protected void prepareForDeferredProcessing(Event event) { event.prepareForDeferredProcessing(); } protected void setEventFactory(LogEventFactory eventFactory) { this.eventFactory = eventFactory; } protected EventTranslatorOneArg, Event> getEventTranslator() { return eventTranslator; } protected void setEventTranslator(EventTranslatorOneArg, Event> eventTranslator) { this.eventTranslator = eventTranslator; } protected ScheduledExecutorService getExecutorService() { return executorService; } protected Disruptor> getDisruptor() { return disruptor; } protected int getThreadPoolCoreSize() { return threadPoolCoreSize; } protected void setThreadPoolCoreSize(int threadPoolCoreSize) { this.threadPoolCoreSize = threadPoolCoreSize; } public String getThreadNamePrefix() { return threadNamePrefix; } public void setThreadNamePrefix(String threadNamePrefix) { this.threadNamePrefix = threadNamePrefix; } public int getRingBufferSize() { return ringBufferSize; } public void setRingBufferSize(int ringBufferSize) { this.ringBufferSize = ringBufferSize; } public ProducerType getProducerType() { return producerType; } public void setProducerType(ProducerType producerType) { this.producerType = producerType; } public WaitStrategy getWaitStrategy() { return waitStrategy; } public void setWaitStrategy(WaitStrategy waitStrategy) { this.waitStrategy = waitStrategy; } public void setWaitStrategyType(String waitStrategyType) { setWaitStrategy(WaitStrategyFactory.createWaitStrategyFromString(waitStrategyType)); } public ThreadFactory getThreadFactory() { return threadFactory; } public void setThreadFactory(ThreadFactory threadFactory) { this.threadFactory = threadFactory; } public int getDroppedWarnFrequency() { return droppedWarnFrequency; } public void setDroppedWarnFrequency(int droppedWarnFrequency) { this.droppedWarnFrequency = droppedWarnFrequency; } protected EventHandler> getEventHandler() { return eventHandler; } protected void setEventHandler(EventHandler> eventHandler) { this.eventHandler = eventHandler; } public boolean isDaemon() { return useDaemonThread; } public void setDaemon(boolean useDaemonThread) { this.useDaemonThread = useDaemonThread; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy