如何在EnvironmentPostProcessor执行中记录错误

时间:2017-03-16 16:33:17

标签: java spring spring-boot

我在SpringBoot中创建了一个EnvironmentPostProcessor,用于从数据库中获取属性,并将其作为PropertySource附加到Spring的#lang racket (require rackunit) (check-equal? (add1 1) (* 2 1))

这是我的代码:

Environment

这是必须创建的@Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { Map<String, Object> propertySource = new HashMap<>(); // LOG SOMETHING HERE ******************* logger.error("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); String[] activeProfiles = environment.getActiveProfiles(); String[] defaultProfiles = environment.getDefaultProfiles(); // Do not pull db configuration when 'default' profile (used by Jenkins only) is run if (activeProfiles.length == 0 && defaultProfiles[0] == "default") { return; } // Load properties for Config schema String dataSourceUrl = environment.getProperty("service.datasource.url"); String username = environment.getProperty("service.datasource.username"); String password = environment.getProperty("service.datasource.password"); String driver = environment.getProperty("service.datasource.driverClassName"); try { // Build manually datasource to Config DataSource ds = DataSourceBuilder .create() .username(username) .password(password) .url(dataSourceUrl) .driverClassName(driver) .build(); // Fetch all properties PreparedStatement preparedStatement = ds.getConnection().prepareStatement("SELECT name, value FROM propertyConfig WHERE service = ?"); preparedStatement.setString(1, APP_NAME); ResultSet rs = preparedStatement.executeQuery(); // Populate all properties into the property source while (rs.next()) { String propName = rs.getString("name"); propertySource.put(propName, rs.getString("value")); } // Create a custom property source with the highest precedence and add it to Spring Environment environment.getPropertySources().addFirst(new MapPropertySource(PROPERTY_SOURCE_NAME, propertySource)); } catch (Exception e) { throw new Exception("Error fetching properties from ServiceConfig"); } } 文件:

main/META-INF/spring-factories

代码运行良好,它从db获取我需要的东西。但是,我想在发生错误的情况下记录有关此内容的信息,例如,如果db已关闭,我想记录错误并停止应用程序启动。我的应用程序配置为使用记录器,而不是控制台。

我已经尝试记录错误,抛出异常,也打印出一些东西,但我的日志永远不会记录这些信息。

如何在早春阶段使用记录器?无论如何都可以这样做吗?我错误地使用了EnvironmentPostProcessor吗?

4 个答案:

答案 0 :(得分:3)

这里的问题是日志系统只在初始化spring上下文后初始化。当调用日志方法时,日志系统不知道如何处理该信息,它什么都不做。

没有优雅的方法来解决这个问题。你要么摆脱Spring管理的日志系统,要么使用延迟日志机制(就像spring在内部一样)。

为了能够使用ls -la,您必须确保在上下文初始化后系统将请求重播日志。

以下是实现目标的方法之一:

DeferredLog

在此示例中,每条日志消息都缓存在@Component public class MyEnvironmentPostProcessor implements EnvironmentPostProcessor, ApplicationListener<ApplicationEvent> { private static final DeferredLog log = new DeferredLog(); @Override public void postProcessEnvironment( ConfigurableEnvironment env, SpringApplication app) { log.error("This should be printed"); } @Override public void onApplicationEvent(ApplicationEvent event) { log.replayTo(MyEnvironmentPostProcessor.class); } } 中。一旦上下文初始化,系统将调用DeferredLog。此方法将所有缓存的日志事件重播到标准记录器。

注意:我在这里使用onApplicationEvent,但您可以使用各种方便的方法。我们的想法是在初始化上下文后调用ApplicationListener,并且无论您从哪个地方调用它都无关紧要。

PS:DeferredLog.replayTo()的位置应为spring.factories,否则可能无法调用src/main/resources/META-INF

答案 1 :(得分:2)

如已接受的答案中所述,问题是运行EnvironmentPostProcessor时日志系统尚未初始化。

但是,在DeferredLog中使用诸如静态EnvironmentPostProcessor之类的机制来临时存储日志,然后在ApplicationListener<ApplicationPreparedEvent>中重播它们(一旦初始化了日志记录系统)也不起作用因为EnvironmentPostProcessorApplicationListener是由不同的类加载器加载和初始化的。

因此,用于ApplicationListener的Class实例对用作EnvironmentPostProcessor的Class实例不可见(即使它们实际上是同一类)。 / p>

一种破解方法是使用System.setProperty(...)EnvironmentPostProcessor中设置要注销的内容,并在System.getProperty(...)中使用ApplicationListener注销。这避免了Spring类加载器的问题。我绝对建议您不要使用这种方法,但是确实可以。

YMMV,但就我而言,我发现将自定义环境设置逻辑从EnvironmentPostProcessor移至ApplicationListener<ApplicationPreparedEvent>对我来说很好,包括日志记录。

Spring Application Events参考:https://docs.spring.io/spring-boot/docs/2.2.6.RELEASE/reference/html/spring-boot-features.html#boot-features-application-events-and-listeners


更新:基于shaohua-shi's answer,这是一个简单的工作解决方案,它在初始化记录系统后使用ApplicationContextInitializer重播DeferredLog

public class MyEnvPostProcessor implements EnvironmentPostProcessor {

    private DeferredLog log = new DeferredLog();

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment env, SpringApplication app) {
        app.addInitializers(ctx -> log.replayTo(MyEnvPostProcessor.class));

        log.warn("In Env Post Processor");
    }
}

日志:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.1.RELEASE)

2020-06-25 20:51:15.118  WARN 7297 --- [  restartedMain] c.e.testenvpostproc.MyEnvPostProcessor   : In Env Post Processor

答案 2 :(得分:1)

我发现了!执行postProcessEnvironment时调用addInitializers。

public class MyContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    private final Logger logger = LoggerFactory.getLogger(I3keContextInitializer.class);

    MyEnvironmentProcessor processor;

    public MyContextInitializer(MyEnvironmentProcessor processor) {
        this.processor = processor;
    }

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        logger.warn("------------> DeferredLog:");
        processor.getLogger().replayTo(MyEnvironmentProcessor.class);
    }
}
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.0.RELEASE)

06-10 15:48:59.874  WARN 38518 --- [restartedMain] c.s.e.init.MyContextInitializer       |21 : ------------> DeferredLog:
06-10 15:48:59.885 ERROR 38518 --- [restartedMain] c.s.e.e.MyEnvironmentProcessor        |231 : ---------------MyEnvironmentProcessor---------------

显示日志

<body>
<!-- Your code here... Styling also works... -->

    <iframe src="1.html" id="page1"></iframe>
    <iframe src="2.html" id="page2"></iframe

</body>

答案 3 :(得分:0)

对于使用科特林编码的人:

class SomePostProcessor : EnvironmentPostProcessor {

    var logger: DeferredLog = DeferredLog()

    override fun postProcessEnvironment(env: ConfigurableEnvironment, application: SpringApplication) {
        // do and log stuff
        logger.info("some log message")

        // HERE IS THE TRICK: defer logging to after the application starts
        application.addInitializers(ApplicationContextInitializer<ConfigurableApplicationContext> {
            logger.replayTo(ExcludeAutoConfigPostProcessor::class.java)
        })
    }
}