【问题标题】:Spring is resetting my logging configuration - how do I work around this?Spring 正在重置我的日志记录配置 - 我该如何解决这个问题?
【发布时间】:2015-02-09 20:46:58
【问题描述】:

我有一个 Spring Batch 作业,负责处理传入的客户文件。其中一项要求是记录日志以在每个作业运行时(由客户)分隔日志文件。

在我的应用程序的主程序中,我处理命令行参数,然后从那里动态创建我的 FileAppender。

我的 logback.xml:

<configuration>
    <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="Console" />
    </root>
</configuration>

我的代码添加了 appender:

    private static void setupFileAppender() {
        String logDir = fetchLogDir();
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        String datePortion = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));

        FileAppender<ILoggingEvent> fileAppender = new FileAppender<>();
        fileAppender.setContext(loggerContext);
        fileAppender.setName("File");
        fileAppender.setFile(logDir + baseFileName + "-" + datePortion + ".log");
        fileAppender.setAppend(true);

        PatternLayoutEncoder encoder = new PatternLayoutEncoder();
        encoder.setContext(loggerContext);
        encoder.setPattern("%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg%n");
        encoder.start();

        fileAppender.setEncoder(encoder);
        fileAppender.start();

        Logger rootLogger = loggerContext.getLogger("root");
        rootLogger.addAppender(fileAppender);

        log.info("Logging configured.");
    }

从我的主程序(或从中调用)执行的任何日志语句都按预期记录到文件中。我可以在调试模式下深入研究,发现我在根记录器上有两个附加程序 - 来自两个配置的“控制台”和“文件”附加程序。但是,一旦我运行SpringApplication.run 命令,FileAppender 就会消失。

我逐步完成了SpringApplicaton.run(...) 方法,我发现 Spring 正在重置我的日志配置并从 logback.xml 重新加载它。

来自 SpringApplication:

try {
    // Create and configure the environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    configureEnvironment(environment, args);
    for (SpringApplicationRunListener runListener : runListeners) {
        runListener.environmentPrepared(environment);
    }

    ...

来自EventPublishingRunListener

@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
    publishEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args,
        environment));
}

private void publishEvent(SpringApplicationEvent event) {
    this.multicaster.multicastEvent(event);
}

之后打了几个电话,然后LoggingApplicationListener

@Override
public void onApplicationEvent(ApplicationEvent event) {
    if (event instanceof ApplicationStartedEvent) {
        onApplicationStartedEvent((ApplicationStartedEvent) event);
    }
    else if (event instanceof ApplicationEnvironmentPreparedEvent) {
        onApplicationPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
    }
}

private void onApplicationPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
    if (this.loggingSystem == null) {
        this.loggingSystem = LoggingSystem.get(event.getSpringApplication()
            .getClassLoader());
    }
    initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}

protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {
    if (System.getProperty(PID_KEY) == null) {
        System.setProperty(PID_KEY, new ApplicationPid().toString());
    }
    initializeEarlyLoggingLevel(environment);
    initializeSystem(environment, this.loggingSystem);
    initializeFinalLoggingLevels(environment, this.loggingSystem);
}

private void initializeSystem(ConfigurableEnvironment environment,
        LoggingSystem system) {
    LogFile logFile = LogFile.get(environment);
    String logConfig = environment.getProperty(CONFIG_PROPERTY);
    if (StringUtils.hasLength(logConfig)) {
        try {
            ResourceUtils.getURL(logConfig).openStream().close();
            system.initialize(logConfig, logFile);
        }
        catch (Exception ex) {
            this.logger.warn("Logging environment value '" + logConfig
                + "' cannot be opened and will be ignored "
                + "(using default location instead)");
            system.initialize(null, logFile);
        }
    }
    else {
        system.initialize(null, logFile);
    }
}

LogbackLoggingSystem(和AbstractLoggingSystem):

@Override
public void initialize(String configLocation, LogFile logFile) {
    getLogger(null).getLoggerContext().getTurboFilterList().remove(FILTER);
    super.initialize(configLocation, logFile);
}

@Override
public void initialize(String configLocation, LogFile logFile) {
    if (StringUtils.hasLength(configLocation)) {
        // Load a specific configuration
        configLocation = SystemPropertyUtils.resolvePlaceholders(configLocation);
        loadConfiguration(configLocation, logFile);
    }
    else {
        String selfInitializationConfig = getSelfInitializationConfig();
        if (selfInitializationConfig == null) {
            // No self initialization has occurred, use defaults
            loadDefaults(logFile);
        }
        else if (logFile != null) {
            // Self initialization has occurred but the file has changed, reload
            loadConfiguration(selfInitializationConfig, logFile);
        }
        else {
            reinitialize();
        }
    }
}

上面最后一个else被打了,reinitialize()被调用:

@Override
protected void reinitialize() {
    getLoggerContext().reset();
    loadConfiguration(getSelfInitializationConfig(), null);
}

对上下文的重置调用是重置所有内容的原因。问题是,深入研究loadConfiguration 方法也会调用 reset 方法作为日志记录上下文。

关于如何绕过 Spring 重置我的日志记录配置的任何想法?

仅供参考,我使用的是 Spring 的 4.1.4.RELEASE 版本。

【问题讨论】:

  • 这个问题与 Spring 或 Spring Batch 无关,都与 Spring Boot 有关。

标签: spring-boot logback


【解决方案1】:

听起来好像将日志配置的自定义推迟到 LoggingApplicationListener 运行之后应该可以工作。

LoggingApplicationListener 执行其初始化以响应ApplicationEnvironmentPreparedEvent,并具有Ordered.HIGHEST_PRECEDENCE + 11 的顺序。为了防止您的自定义配置被覆盖,您可以将自定义逻辑封装在 SmartApplicationListener 中,它响应相同的事件但顺序较低,因此它在 LoggingApplicationListener 之后运行:

public class CustomLoggingConfigurationApplicationListener implements
    SmartApplicationListener {

    @Override
    public void onApplicationEvent(ApplicationEvent event) {    
        // Customise the logging configuration
    }   

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE + 12;
    }

    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
        return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType);
    }

    @Override
    public boolean supportsSourceType(Class<?> sourceType) {
        return true;
    }

}

您可以创建侦听器并将其注册到应用程序的 main 方法中:

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        new SpringApplicationBuilder(Application.class)
                .listeners(new CustomLoggingConfigurationApplicationListener())
                .run(args);
    }
}

【讨论】:

  • 100% 正确回答。谢谢安迪。
  • 我遇到了完全相同的问题,但这个解决方案对我不起作用。我自己已经得到了同样的“潜在解决方案”,但由于某种原因,我的听众永远没有机会听到 ApplicationEnvironmentPreparedEvent 等。例如,如果我向supportsEventType 添加一个断点,它所调用的所有内容是:ContextRefreshedEvent、EmbeddedServletContainerInitializedEvent、ApplicationReadyEvent 和 ContextClosedEvent。 ApplicationEnvironmentPreparedEvent 应该早于所有这些其他事件,它永远不会触发我的侦听器。有人知道为什么吗?
  • 刚刚发现为什么在我的情况下它不起作用。我在 Application 类中使用 @Import 注册了我的侦听器,而不是直接在 SpringApplicationBuilder 中调用 listeners 方法。因此,我的侦听器没有按时注册许多事件,包括 ApplicationEnvironmentPreparedEvent。我想知道这种行为是预期的(并记录在案)还是一个错误。
  • 是的,这是意料之中的。 @Import 在刷新应用程序上下文之前不会被处理。
  • 是的。它实际上记录在here
猜你喜欢
  • 1970-01-01
  • 2014-05-10
  • 2020-03-25
  • 2020-02-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多