/*
 * Decompiled with CFR 0.152.
 * 
 * Could not load the following classes:
 *  com.lmax.disruptor.BlockingWaitStrategy
 *  com.lmax.disruptor.EventFactory
 *  com.lmax.disruptor.EventHandler
 *  com.lmax.disruptor.EventTranslator
 *  com.lmax.disruptor.ExceptionHandler
 *  com.lmax.disruptor.RingBuffer
 *  com.lmax.disruptor.SleepingWaitStrategy
 *  com.lmax.disruptor.WaitStrategy
 *  com.lmax.disruptor.YieldingWaitStrategy
 *  com.lmax.disruptor.dsl.Disruptor
 *  com.lmax.disruptor.dsl.ProducerType
 *  com.lmax.disruptor.util.Util
 */
package org.apache.logging.log4j.core.async;

import com.lmax.disruptor.BlockingWaitStrategy;
import com.lmax.disruptor.EventFactory;
import com.lmax.disruptor.EventHandler;
import com.lmax.disruptor.EventTranslator;
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;
import com.lmax.disruptor.util.Util;
import java.util.Map;
import java.util.concurrent.Executor;
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.async.DaemonThreadFactory;
import org.apache.logging.log4j.core.async.RingBufferLogEvent;
import org.apache.logging.log4j.core.async.RingBufferLogEventHandler;
import org.apache.logging.log4j.core.async.RingBufferLogEventTranslator;
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.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;

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 = 262144;
    private static final StatusLogger LOGGER = StatusLogger.getLogger();
    private static final ThreadNameStrategy THREAD_NAME_STRATEGY = ThreadNameStrategy.create();
    private static final ThreadLocal<Info> threadlocalInfo = new ThreadLocal();
    private static volatile Disruptor<RingBufferLogEvent> disruptor;
    private static final Clock clock;
    private static final ExecutorService executor;

    private static int calculateRingBufferSize() {
        int ringBufferSize = 262144;
        String userPreferredRBSize = System.getProperty("AsyncLogger.RingBufferSize", String.valueOf(ringBufferSize));
        try {
            int size = Integer.parseInt(userPreferredRBSize);
            if (size < 128) {
                size = 128;
                LOGGER.warn("Invalid RingBufferSize {}, using minimum size {}.", userPreferredRBSize, 128);
            }
            ringBufferSize = size;
        }
        catch (Exception ex) {
            LOGGER.warn("Invalid RingBufferSize {}, using default size {}.", userPreferredRBSize, ringBufferSize);
        }
        return Util.ceilingNextPowerOfTwo((int)ringBufferSize);
    }

    private static void initInfoForExecutorThread() {
        executor.submit(new Runnable(){

            @Override
            public void run() {
                boolean isAppenderThread = true;
                Info info = new Info(new RingBufferLogEventTranslator(), Thread.currentThread().getName(), true);
                threadlocalInfo.set(info);
            }
        });
    }

    private static WaitStrategy createWaitStrategy() {
        String strategy = System.getProperty("AsyncLogger.WaitStrategy");
        LOGGER.debug("property AsyncLogger.WaitStrategy={}", strategy);
        if ("Sleep".equals(strategy)) {
            return new SleepingWaitStrategy();
        }
        if ("Yield".equals(strategy)) {
            return new YieldingWaitStrategy();
        }
        if ("Block".equals(strategy)) {
            return new BlockingWaitStrategy();
        }
        LOGGER.debug("disruptor event handler uses BlockingWaitStrategy");
        return new BlockingWaitStrategy();
    }

    private static ExceptionHandler getExceptionHandler() {
        String cls = System.getProperty("AsyncLogger.ExceptionHandler");
        if (cls == null) {
            LOGGER.debug("No AsyncLogger.ExceptionHandler specified");
            return null;
        }
        try {
            ExceptionHandler result = Loader.newCheckedInstanceOf(cls, ExceptionHandler.class);
            LOGGER.debug("AsyncLogger.ExceptionHandler={}", result);
            return result;
        }
        catch (Exception ignored) {
            LOGGER.debug("AsyncLogger.ExceptionHandler not set: error creating " + cls + ": ", (Throwable)ignored);
            return null;
        }
    }

    public AsyncLogger(LoggerContext context, String name, MessageFactory messageFactory) {
        super(context, name, messageFactory);
    }

    @Override
    public void logMessage(String fqcn, Level level, Marker marker, Message message, Throwable thrown) {
        Disruptor<RingBufferLogEvent> temp;
        Info info = threadlocalInfo.get();
        if (info == null) {
            info = new Info(new RingBufferLogEventTranslator(), Thread.currentThread().getName(), false);
            threadlocalInfo.set(info);
        }
        if ((temp = disruptor) == null) {
            LOGGER.fatal("Ignoring log event after log4j was shut down");
            return;
        }
        if (info.isAppenderThread && temp.getRingBuffer().remainingCapacity() == 0L) {
            this.config.loggerConfig.log(this.getName(), fqcn, marker, level, message, thrown);
            return;
        }
        boolean includeLocation = this.config.loggerConfig.isIncludeLocation();
        info.translator.setValues(this, this.getName(), marker, fqcn, level, message, thrown, ThreadContext.getImmutableContext(), ThreadContext.getImmutableStack(), THREAD_NAME_STRATEGY.getThreadName(info), includeLocation ? AsyncLogger.location(fqcn) : null, message instanceof TimestampMessage ? ((TimestampMessage)((Object)message)).getTimestamp() : clock.currentTimeMillis());
        try {
            disruptor.publishEvent((EventTranslator)info.translator);
        }
        catch (NullPointerException npe) {
            LOGGER.fatal("Ignoring log event after log4j was shut down.");
        }
    }

    private static StackTraceElement location(String fqcnOfLogger) {
        return Log4jLogEvent.calcLocation(fqcnOfLogger);
    }

    public void actualAsyncLog(RingBufferLogEvent event) {
        Map<Property, Boolean> properties = this.config.loggerConfig.getProperties();
        event.mergePropertiesIntoContextMap(properties, this.config.config.getStrSubstitutor());
        this.config.logEvent(event);
    }

    public static void stop() {
        Disruptor<RingBufferLogEvent> temp = disruptor;
        disruptor = null;
        if (temp == null) {
            return;
        }
        for (int i = 0; AsyncLogger.hasBacklog(temp) && i < 200; ++i) {
            try {
                Thread.sleep(50L);
                continue;
            }
            catch (InterruptedException interruptedException) {
                // empty catch block
            }
        }
        temp.shutdown();
        executor.shutdown();
        threadlocalInfo.remove();
    }

    private static boolean hasBacklog(Disruptor<?> disruptor) {
        RingBuffer ringBuffer = disruptor.getRingBuffer();
        return !ringBuffer.hasAvailableCapacity(ringBuffer.getBufferSize());
    }

    public static RingBufferAdmin createRingBufferAdmin(String contextName) {
        return RingBufferAdmin.forAsyncLogger(disruptor.getRingBuffer(), contextName);
    }

    static {
        clock = ClockFactory.getClock();
        executor = Executors.newSingleThreadExecutor(new DaemonThreadFactory("AsyncLogger-"));
        AsyncLogger.initInfoForExecutorThread();
        LOGGER.debug("AsyncLogger.ThreadNameStrategy={}", new Object[]{THREAD_NAME_STRATEGY});
        int ringBufferSize = AsyncLogger.calculateRingBufferSize();
        WaitStrategy waitStrategy = AsyncLogger.createWaitStrategy();
        disruptor = new Disruptor((EventFactory)RingBufferLogEvent.FACTORY, ringBufferSize, (Executor)executor, ProducerType.MULTI, waitStrategy);
        disruptor.handleExceptionsWith(AsyncLogger.getExceptionHandler());
        disruptor.handleEventsWith(new EventHandler[]{new RingBufferLogEventHandler()});
        LOGGER.debug("Starting AsyncLogger disruptor with ringbuffer size {}...", disruptor.getRingBuffer().getBufferSize());
        disruptor.start();
    }

    static class Info {
        private final RingBufferLogEventTranslator translator;
        private final String cachedThreadName;
        private final boolean isAppenderThread;

        public Info(RingBufferLogEventTranslator translator, String threadName, boolean appenderThread) {
            this.translator = translator;
            this.cachedThreadName = threadName;
            this.isAppenderThread = appenderThread;
        }
    }

    static enum ThreadNameStrategy {
        CACHED{

            @Override
            public String getThreadName(Info info) {
                return info.cachedThreadName;
            }
        }
        ,
        UNCACHED{

            @Override
            public String getThreadName(Info info) {
                return Thread.currentThread().getName();
            }
        };


        abstract String getThreadName(Info var1);

        static ThreadNameStrategy create() {
            String name = System.getProperty("AsyncLogger.ThreadNameStrategy", CACHED.name());
            try {
                return ThreadNameStrategy.valueOf(name);
            }
            catch (Exception ex) {
                LOGGER.debug("Using AsyncLogger.ThreadNameStrategy.CACHED: '{}' not valid: {}", name, ex.toString());
                return CACHED;
            }
        }
    }
}
