Почему приложение Log4j2 RollingFile предотвращает завершение автономного приложения в течение 60 секунд?

Этот код воспроизводит то, что я считаю ошибкой Log4j2. Это простой цикл, который регистрирует 2000 сообщений с двумя приложениями: консольным приложением и приложением с прокруткой файла, которое прокручивает файл каждые 5 КБ. Это ограничение намеренно занижено, чтобы воспроизвести то, что я считаю ошибкой.

Вот код.

package bug;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Example {

    private static final Logger logger = LogManager.getLogger(Example.class);

    public static void main(String[] args) throws InterruptedException {
        for(int i = 0; i<2000; i++){
            logger.info("This is log message #{}.", i);
            Thread.sleep(0);
        }
    }

}

Вот файл конфигурации log4j2.xml.

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="TRACE">
    <Appenders>
        <Console name="stdout" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %p %m%n"/>
        </Console>
        <RollingFile name="roll-by-size"
                     fileName="target/log4j2/roll-by-size/app.log"
                     filePattern="target/log4j2/roll-by-size/app.%i.log.gz"
                     ignoreExceptions="false"
                     append="false">
            <PatternLayout>
                <Pattern>%d{yyyy-MM-dd HH:mm:ss} %p %m%n</Pattern>
            </PatternLayout>
            <Policies>
                <OnStartupTriggeringPolicy/>
                <SizeBasedTriggeringPolicy
                        size="5 KB"/>
            </Policies>
        </RollingFile>
    </Appenders>
    <Loggers>
        <Logger name="bug" level="TRACE">
            <AppenderRef ref="roll-by-size"/>
        </Logger>
        <Root level="DEBUG">
            <AppenderRef ref="stdout"/>
        </Root>
    </Loggers>
</Configuration>

Странно то, что при запуске приложения вы увидите этот лог в консоли.

2016-12-22 22:12:36 INFO This is log message #1993.
2016-12-22 22:12:36 INFO This is log message #1994.
2016-12-22 22:12:36 INFO This is log message #1995.
2016-12-22 22:12:36 INFO This is log message #1996.
2016-12-22 22:12:36 INFO This is log message #1997.
2016-12-22 22:12:36 INFO This is log message #1998.
2016-12-22 22:12:36 INFO This is log message #1999.
2016-12-22 22:13:36,380 pool-1-thread-1 DEBUG Stopping LoggerContext[name=60199c81, org.apache.logging.log4j.core.LoggerContext@4597ec68]
2016-12-22 22:13:36,380 pool-1-thread-1 DEBUG Stopping LoggerContext[name=60199c81, org.apache.logging.log4j.core.LoggerContext@4597ec68]...
2016-12-22 22:13:36,381 pool-1-thread-1 TRACE Unregistering 1 MBeans: [org.apache.logging.log4j2:type=60199c81]
2016-12-22 22:13:36,381 pool-1-thread-1 TRACE Unregistering 1 MBeans: [org.apache.logging.log4j2:type=60199c81,component=StatusLogger]
2016-12-22 22:13:36,381 pool-1-thread-1 TRACE Unregistering 1 MBeans: [org.apache.logging.log4j2:type=60199c81,component=ContextSelector]
2016-12-22 22:13:36,381 pool-1-thread-1 TRACE Unregistering 2 MBeans: [org.apache.logging.log4j2:type=60199c81,component=Loggers,name=bug, org.apache.logging.log4j2:type=60199c81,component=Lo
ggers,name=]
2016-12-22 22:13:36,381 pool-1-thread-1 TRACE Unregistering 2 MBeans: [org.apache.logging.log4j2:type=60199c81,component=Appenders,name=roll-by-size, org.apache.logging.log4j2:type=60199c81,c
omponent=Appenders,name=stdout]
2016-12-22 22:13:36,382 pool-1-thread-1 TRACE Unregistering but no MBeans found matching 'org.apache.logging.log4j2:type=60199c81,component=AsyncAppenders,name=*'
2016-12-22 22:13:36,382 pool-1-thread-1 TRACE Unregistering but no MBeans found matching 'org.apache.logging.log4j2:type=60199c81,component=AsyncLoggerRingBuffer'
2016-12-22 22:13:36,382 pool-1-thread-1 TRACE Unregistering but no MBeans found matching 'org.apache.logging.log4j2:type=60199c81,component=Loggers,name=*,subtype=RingBuffer'
2016-12-22 22:13:36,382 pool-1-thread-1 TRACE Stopping XmlConfiguration[location=C:\Users\danidemi\workspace\bug-log4j2-hanging-up-before-shutdown\target\classes\log4j2.xml]...
2016-12-22 22:13:36,382 pool-1-thread-1 TRACE XmlConfiguration notified 3 ReliabilityStrategies that config will be stopped.
2016-12-22 22:13:36,382 pool-1-thread-1 TRACE XmlConfiguration stopping 2 LoggerConfigs.
2016-12-22 22:13:36,382 pool-1-thread-1 TRACE XmlConfiguration stopping root LoggerConfig.
2016-12-22 22:13:36,382 pool-1-thread-1 TRACE XmlConfiguration notifying ReliabilityStrategies that appenders will be stopped.
2016-12-22 22:13:36,382 pool-1-thread-1 TRACE XmlConfiguration stopping remaining Appenders.
2016-12-22 22:13:36,383 pool-1-thread-1 DEBUG Shutting down RollingFileManager target/log4j2/roll-by-size/app.log
2016-12-22 22:13:36,383 pool-1-thread-1 DEBUG Shut down RollingFileManager target/log4j2/roll-by-size/app.log, all resources released: true
2016-12-22 22:13:36,383 pool-1-thread-1 DEBUG Shutting down OutputStreamManager SYSTEM_OUT.false.false
2016-12-22 22:13:36,383 pool-1-thread-1 DEBUG Shut down OutputStreamManager SYSTEM_OUT.false.false, all resources released: true
2016-12-22 22:13:36,384 pool-1-thread-1 TRACE XmlConfiguration stopped 2 remaining Appenders.
2016-12-22 22:13:36,384 pool-1-thread-1 TRACE XmlConfiguration cleaning Appenders from 3 LoggerConfigs.
2016-12-22 22:13:36,384 pool-1-thread-1 DEBUG Stopped XmlConfiguration[location=C:\Users\danidemi\workspace\bug-log4j2-hanging-up-before-shutdown\target\classes\log4j2.xml] OK
2016-12-22 22:13:36,385 pool-1-thread-1 DEBUG Stopped LoggerContext[name=60199c81, org.apache.logging.log4j.core.LoggerContext@4597ec68]...

Что странно, так это то, что последний лог выдается в определенное время...

2016-12-22 22:12:36 INFO This is log message #1999.

но отключение log4j2 начинается ровно через одну минуту после последнего "делового" сообщения журнала.

2016-12-22 22:13:36,380 pool-1-thread-1 DEBUG Stopping LoggerContext[name=60199c81, org.apache.logging.log4j.core.LoggerContext@4597ec68]

Вот в чем проблема! Бизнес-логика завершается, но log4j2 ждет одну минуту, прежде чем позволить приложению остановиться! Почему? Я бы предпочел, чтобы приложение остановилось немедленно, как и следовало ожидать.

Я немного исследовал... эта 60-секундная задержка кажется более или менее независимой от количества регистрируемых сообщений.

Однако, если вы измените log4j2.xml, увеличив размер с 5 КБ...

<Policies>
    <OnStartupTriggeringPolicy/>
    <SizeBasedTriggeringPolicy size="5 KB"/>
</Policies>

to 5Mb...

<Policies>
    <OnStartupTriggeringPolicy/>
    <SizeBasedTriggeringPolicy size="5 MB"/>
</Policies>

... это заставляет приложение останавливаться сразу после последнего сообщения журнала. 5 Мб — это предел, достаточно большой, чтобы не требовать фактической прокрутки. Поэтому я думаю, что сама прокатка каким-то образом вызывает эту задержку. Я думаю, что это ошибка, но... что вы думаете?

Я создал небольшой проект Maven на GitHub. это демонстрирует то, что я пытался здесь объяснить.


person danidemi    schedule 22.12.2016    source источник


Ответы (2)


Вы можете попробовать использовать LogManager.shutdown() как самую последнюю команду вашей программы.

person sazzad    schedule 23.12.2016
comment
Привет @sazzad, я принимаю ответ, потому что он решает проблему, но мне было интересно, не могли бы вы немного расширить тему. Вы знаете, я всегда использовал log4j и logback, и мне никогда не приходилось явно отключать систему логирования, поэтому я немного удивлен этой функцией. Спасибо заранее. - person danidemi; 24.12.2016
comment
Вы можете изучить этот ответ и решить проблемы jira log4j2. stackoverflow.com/a/28835409/4021802 - person sazzad; 24.12.2016

Благодарим вас за поднятие этой проблемы перед сообществом Log4j2.

Основная причина заключается в том, что Log4j создает два ThreadPoolExecutors, один демон и один не демон. Эти исполнители создаются с настройками по умолчанию, которые задают для потока keepAliveTime одну минуту.

В этом примере приложение закрывается сразу после срабатывания ролловера. Опрокидывание сжимает старый файл в фоновом потоке в исполнителе, не являющемся демоном. Поскольку исполнитель поддерживает этот поток в течение одной минуты, а это не демонический поток, приложение в целом остается в рабочем состоянии в течение одной минуты.

Это повлияет только на приложения, которые закрываются сразу после ролловера.

Обновление 2016-12-26: исправлено. Начиная с версии 2.8, потоки, не являющиеся демонами, по умолчанию будут иметь более короткое (одна секунда) значение keepAliveTime.

person Remko Popma    schedule 25.12.2016
comment
Я пытался понять, почему мой ответ был отклонен. - person Remko Popma; 29.12.2016