package org.apache.logging.log4j.core.async;

import com.lmax.disruptor.BlockingWaitStrategy;
import com.lmax.disruptor.EventHandler;
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.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.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 AsyncLogger.ThreadNameStrategy THREAD_NAME_STRATEGY = AsyncLogger.ThreadNameStrategy.create();
   private static final ThreadLocal<AsyncLogger.Info> threadlocalInfo = new ThreadLocal();
   private static volatile Disruptor<RingBufferLogEvent> disruptor;
   private static final Clock clock = ClockFactory.getClock();
   private static final ExecutorService executor = Executors.newSingleThreadExecutor(new DaemonThreadFactory("AsyncLogger-"));

   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 {}.", new Object[]{userPreferredRBSize, 128});
         }

         ringBufferSize = size;
      } catch (Exception var3) {
         LOGGER.warn("Invalid RingBufferSize {}, using default size {}.", new Object[]{userPreferredRBSize, ringBufferSize});
      }

      return Util.ceilingNextPowerOfTwo(ringBufferSize);
   }

   private static void initInfoForExecutorThread() {
      executor.submit(new Runnable() {
         public void run() {
            boolean isAppenderThread = true;
            AsyncLogger.Info info = new AsyncLogger.Info(new RingBufferLogEventTranslator(), Thread.currentThread().getName(), true);
            AsyncLogger.threadlocalInfo.set(info);
         }
      });
   }

   private static WaitStrategy createWaitStrategy() {
      String strategy = System.getProperty("AsyncLogger.WaitStrategy");
      LOGGER.debug("property AsyncLogger.WaitStrategy={}", new Object[]{strategy});
      if ("Sleep".equals(strategy)) {
         return new SleepingWaitStrategy();
      } else if ("Yield".equals(strategy)) {
         return new YieldingWaitStrategy();
      } else if ("Block".equals(strategy)) {
         return new BlockingWaitStrategy();
      } else {
         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;
      } else {
         try {
            ExceptionHandler result = (ExceptionHandler)Loader.newCheckedInstanceOf(cls, ExceptionHandler.class);
            LOGGER.debug("AsyncLogger.ExceptionHandler={}", new Object[]{result});
            return result;
         } catch (Exception var2) {
            LOGGER.debug("AsyncLogger.ExceptionHandler not set: error creating " + cls + ": ", var2);
            return null;
         }
      }
   }

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

   public void logMessage(String fqcn, Level level, Marker marker, Message message, Throwable thrown) {
      AsyncLogger.Info info = (AsyncLogger.Info)threadlocalInfo.get();
      if (info == null) {
         info = new AsyncLogger.Info(new RingBufferLogEventTranslator(), Thread.currentThread().getName(), false);
         threadlocalInfo.set(info);
      }

      Disruptor<RingBufferLogEvent> temp = disruptor;
      if (temp == null) {
         LOGGER.fatal("Ignoring log event after log4j was shut down");
      } else if (info.isAppenderThread && temp.getRingBuffer().remainingCapacity() == 0L) {
         this.config.loggerConfig.log(this.getName(), fqcn, marker, level, message, thrown);
      } else {
         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 ? location(fqcn) : null, message instanceof TimestampMessage ? ((TimestampMessage)message).getTimestamp() : clock.currentTimeMillis());

         try {
            disruptor.publishEvent(info.translator);
         } catch (NullPointerException var10) {
            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) {
         for(int i = 0; hasBacklog(temp) && i < 200; ++i) {
            try {
               Thread.sleep(50L);
            } catch (InterruptedException var3) {
            }
         }

         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 {
      initInfoForExecutorThread();
      LOGGER.debug("AsyncLogger.ThreadNameStrategy={}", new Object[]{THREAD_NAME_STRATEGY});
      int ringBufferSize = calculateRingBufferSize();
      WaitStrategy waitStrategy = createWaitStrategy();
      disruptor = new Disruptor(RingBufferLogEvent.FACTORY, ringBufferSize, executor, ProducerType.MULTI, waitStrategy);
      disruptor.handleExceptionsWith(getExceptionHandler());
      disruptor.handleEventsWith(new EventHandler[]{new RingBufferLogEventHandler()});
      LOGGER.debug("Starting AsyncLogger disruptor with ringbuffer size {}...", new Object[]{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 {
         public String getThreadName(AsyncLogger.Info info) {
            return info.cachedThreadName;
         }
      },
      UNCACHED {
         public String getThreadName(AsyncLogger.Info info) {
            return Thread.currentThread().getName();
         }
      };

      private ThreadNameStrategy() {
      }

      abstract String getThreadName(AsyncLogger.Info var1);

      static AsyncLogger.ThreadNameStrategy create() {
         String name = System.getProperty("AsyncLogger.ThreadNameStrategy", CACHED.name());

         try {
            return valueOf(name);
         } catch (Exception var2) {
            AsyncLogger.LOGGER.debug("Using AsyncLogger.ThreadNameStrategy.CACHED: '{}' not valid: {}", new Object[]{name, var2.toString()});
            return CACHED;
         }
      }

      // $FF: synthetic method
      ThreadNameStrategy(Object x2) {
         this();
      }
   }
}
