package org.eluder.logback.ext.lmax.appender;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import ch.qos.logback.core.spi.ContextAware;
import ch.qos.logback.core.spi.DeferredProcessingAware;
import com.lmax.disruptor.EventFactory;
import com.lmax.disruptor.EventTranslatorOneArg;
import com.lmax.disruptor.ExceptionHandler;
import com.lmax.disruptor.LifecycleAware;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.TimeoutException;
import com.lmax.disruptor.WaitStrategy;
import com.lmax.disruptor.WorkHandler;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;
import org.eluder.logback.ext.core.AppenderExecutors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import static java.lang.String.format;
public class DisruptorAppender<E extends DeferredProcessingAware> extends UnsynchronizedAppenderBase<E> {
protected final ReentrantLock lock = new ReentrantLock(true);
private static final int SLEEP_ON_DRAIN = 50;
private static final int DEFAULT_THREAD_POOL_SIZE = 1;
private static final int DEFAULT_BUFFER_SIZE = 8192;
private EventFactory<LogEvent<E>> eventFactory = new LogEventFactory<>();
private EventTranslatorOneArg<LogEvent<E>, E> eventTranslator = new LogEventTranslator<>();
private ExceptionHandler<LogEvent<E>> exceptionHandler = new LogExceptionHandler<>(this);
private WorkHandler<LogEvent<E>> workHandler;
private int threadPoolSize = DEFAULT_THREAD_POOL_SIZE;
private int bufferSize = DEFAULT_BUFFER_SIZE;
private int maxFlushTime = AppenderExecutors.DEFAULT_MAX_FLUSH_TIME;
private ProducerType producerType = ProducerType.MULTI;
private WaitStrategy waitStrategy = WaitStrategyFactory.DEFAULT_WAIT_STRATEGY;
private Disruptor<LogEvent<E>> disruptor;
private ExecutorService executor;
public final void setEventFactory(EventFactory<LogEvent<E>> eventFactory) {
this.eventFactory = eventFactory;
}
public final void setEventTranslator(EventTranslatorOneArg<LogEvent<E>, E> eventTranslator) {
this.eventTranslator = eventTranslator;
}
public final void setExceptionHandler(ExceptionHandler<LogEvent<E>> exceptionHandler) {
this.exceptionHandler = exceptionHandler;
}
public final void setWorkHandler(WorkHandler<LogEvent<E>> workHandler) {
this.workHandler = workHandler;
}
public final void setThreadPoolSize(int threadPoolSize) {
this.threadPoolSize = threadPoolSize;
}
public final void setBufferSize(int bufferSize) {
this.bufferSize = bufferSize;
}
public final void setMaxFlushTime(int maxFlushTime) {
this.maxFlushTime = maxFlushTime;
}
public final void setProducerType(ProducerType producerType) {
this.producerType = producerType;
}
public final void setWaitStrategy(WaitStrategy waitStrategy) {
this.waitStrategy = waitStrategy;
}
public final void setWaitStrategyType(String waitStrategyType) {
setWaitStrategy(WaitStrategyFactory.createFromType(waitStrategyType));
}
@Override
@SuppressWarnings("unchecked")
public void start() {
if (workHandler == null) {
addError(format("Event handler not set for appender '%s'", getName()));
return;
}
lock.lock();
try {
if (isStarted()) {
return;
}
executor = AppenderExecutors.newExecutor(this, threadPoolSize);
disruptor = new Disruptor<>(
eventFactory,
bufferSize,
executor,
producerType,
waitStrategy
);
disruptor.handleExceptionsWith(exceptionHandler);
disruptor.handleEventsWithWorkerPool(createWorkers());
disruptor.start();
super.start();
} finally {
lock.unlock();
}
}
@Override
public void stop() {
lock.lock();
try {
if (!isStarted()) {
return;
}
super.stop();
shutdownDisruptor();
executor.shutdownNow();
} finally {
lock.unlock();
}
}
@Override
protected void append(E eventObject) {
prepareForDeferredProcessing(eventObject);
disruptor.publishEvent(eventTranslator, eventObject);
}
protected void prepareForDeferredProcessing(E event) {
event.prepareForDeferredProcessing();
}
@SuppressWarnings("unchecked")
private WorkHandler<LogEvent<E>>[] createWorkers() {
WorkHandler<LogEvent<E>> handler = new ClearingWorkHandler<>(workHandler);
WorkHandler<LogEvent<E>>[] workers = new WorkHandler[threadPoolSize];
for (int i = 0; i < threadPoolSize; i++) {
workers[i] = handler;
}
return workers;
}
private void shutdownDisruptor() {
// disruptor busy waits while shutting down so this is a workaround
// for not to hog all the CPU on shutdown
long until = System.currentTimeMillis() + maxFlushTime;
while (System.currentTimeMillis() < until && hashBackLog()) {
try {
Thread.sleep(SLEEP_ON_DRAIN);
} catch (InterruptedException ex) {
// noop
}
}
try {
disruptor.shutdown(0, TimeUnit.MILLISECONDS);
} catch (TimeoutException ex) {
addWarn(format("Disruptor did not shut down in %d milliseconds, " +
"logging events might have been discarded",
maxFlushTime));
}
}
private boolean hashBackLog() {
RingBuffer<LogEvent<E>> buffer = disruptor.getRingBuffer();
return !buffer.hasAvailableCapacity(buffer.getBufferSize());
}
protected static class LogEvent<E> {
public volatile E event;
}
protected static class LogEventFactory<E> implements EventFactory<LogEvent<E>> {
@Override
public LogEvent<E> newInstance() {
return new LogEvent<>();
}
}
protected static class LogEventTranslator<E> implements EventTranslatorOneArg<LogEvent<E>, E> {
@Override
public void translateTo(LogEvent<E> event, long sequence, E arg0) {
event.event = arg0;
}
}
protected static class LogExceptionHandler<E> implements ExceptionHandler<LogEvent<E>> {
private final ContextAware context;
public LogExceptionHandler(ContextAware context) {
this.context = context;
}
@Override
public void handleEventException(Throwable ex, long sequence, LogEvent<E> event) {
if (ex instanceof InterruptedException) {
context.addWarn("Disruptor was interrupted while processing event");
} else {
context.addError("Failed to process event", ex);
}
}
@Override
public void handleOnStartException(Throwable ex) {
context.addError("Failed to start disruptor", ex);
}
@Override
public void handleOnShutdownException(Throwable ex) {
context.addError("Failed to shutdown disruptor", ex);
}
}
/**
* Clears logback event objects from distruptor event context to allow proper garbage collecting.
*/
private static class ClearingWorkHandler<E> implements WorkHandler<LogEvent<E>>, LifecycleAware {
private final WorkHandler<LogEvent<E>> delegate;
private boolean started = false;
public ClearingWorkHandler(WorkHandler<LogEvent<E>> delegate) {
this.delegate = delegate;
}
@Override
public void onEvent(LogEvent<E> event) throws Exception {
try {
delegate.onEvent(event);
} finally {
event.event = null;
}
}
@Override
public synchronized void onStart() {
if (!started) {
if (delegate instanceof LifecycleAware) {
((LifecycleAware) delegate).onStart();
}
started = true;
}
}
@Override
public synchronized void onShutdown() {
if (started) {
started = false;
if (delegate instanceof LifecycleAware) {
((LifecycleAware) delegate).onShutdown();
}
}
}
}
}