Uploaded image for project: 'Log4j 2'
  1. Log4j 2
  2. LOG4J2-3185

DefaultErrorHandler can not share values across threads

    XMLWordPrintableJSON

Details

    • Bug
    • Status: Resolved
    • Critical
    • Resolution: Fixed
    • 2.14.1
    • 2.15.0
    • Appenders, Core
    • None
    • Patch

    Description

      My application used a logger with AsyncAppender and set the blocking attribute to false. The example code as following.

      // Some example logger usage for catching all throwables and log it to disk file
      
          try {
              // my application code, which may throw an exception 
          } catch (Throwable t) {
              logger.error("", t);
          }
      
      

      If there was too much throwable generated, then the the blocking queue in the async appender will be full, and will never enqueue any more. Thus, it will execute DefaultErrorHandler#error(String) method.

          // org.apache.logging.log4j.core.appender.AsyncAppender
          @Override
          public void append(final LogEvent logEvent) {
              if (!isStarted()) {
                  throw new IllegalStateException("AsyncAppender " + getName() + " is not active");
              }
              final Log4jLogEvent memento = Log4jLogEvent.createMemento(logEvent, includeLocation);
              InternalAsyncUtil.makeMessageImmutable(logEvent.getMessage());
              if (!transfer(memento)) {
                  if (blocking) {
                      if (AbstractLogger.getRecursionDepth() > 1) { // LOG4J2-1518, LOG4J2-2031
                          // If queue is full AND we are in a recursive call, call appender directly to prevent deadlock
                          AsyncQueueFullMessageUtil.logWarningToStatusLogger();
                          logMessageInCurrentThread(logEvent);
                      } else {
                          // delegate to the event router (which may discard, enqueue and block, or log in current thread)
                          final EventRoute route = asyncQueueFullPolicy.getRoute(dispatcher.getId(), memento.getLevel());
                          route.logMessage(this, memento);
                      }
                  } else {
                      error("Appender " + getName() + " is unable to write primary appenders. queue is full");
                      logToErrorAppenderIfNecessary(false, memento);
                  }
              }
          }
      

      Application usually runs with multi threads, if the AsyncAppender inner queue was full, then the DefaultErrorHandler may be executed across threads. However, the exceptionCount filed and lastException fields were not volatile variables, it may cause their value were not shared across threads.

          // org.apache.logging.log4j.core.appender.DefaultErrorHandler
      
          private static final int MAX_EXCEPTIONS = 3;
      
          private static final long EXCEPTION_INTERVAL = TimeUnit.MINUTES.toNanos(5);
      
          private int exceptionCount = 0;
      
          private long lastException = System.nanoTime() - EXCEPTION_INTERVAL - 1;
      
          @Override
          public void error(final String msg) {
              final long current = System.nanoTime();
              if (current - lastException > EXCEPTION_INTERVAL || exceptionCount++ < MAX_EXCEPTIONS) {
                  LOGGER.error(msg);
              }
              lastException = current;
          }
      

      Attachments

        Activity

          People

            Unassigned Unassigned
            mzbonnt mzbonnt
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved: