java 如何记录EnvironmentPostProcessor执行中的错误

2fjabf4q  于 2023-01-24  发布在  Java
关注(0)|答案(5)|浏览(339)

我已经在SpringBoot中创建了一个EnvironmentPostProcessor来从数据库中获取属性,并将其作为PropertySource附加到Spring的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文件:

# Environment Post Processor
org.springframework.boot.env.EnvironmentPostProcessor=com.blabla.config.ReadDbPropertiesPostProcessor

代码运行良好,它从数据库中获取我需要的东西。但是,我想记录信息,以防出现错误,例如,如果数据库关闭,我想记录错误并停止应用程序启动。我的应用程序配置为使用日志记录器,而不是控制台。
我试过记录错误,抛出异常,也打印出一些东西,但我的日志从来没有记录这些信息。
在这个早春的阶段,我该如何使用日志记录器呢?无论如何都有可能做到这一点吗?我是否错误地使用了EnvironmentPostProcessor?

vxbzzdmp

vxbzzdmp1#

这里的问题是日志系统只有在spring上下文初始化之后才初始化,当log方法被调用时,日志系统不知道该如何处理这些信息,它什么也不做。
没有优雅的方法来解决这个问题,要么摆脱spring管理的日志系统,要么使用延迟日志机制(就像spring内部做的那样)。
为了能够使用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,但是你可以使用任何方便的方法。这个想法是在上下文初始化后调用DeferredLog.replayTo(),从哪里调用它并不重要。
PS:spring.factories的位置应该是src/main/resources/META-INF,否则可能不会调用postProcessEnvironment

llew8vvj

llew8vvj2#

正如接受的答案中所指出的,问题在于运行EnvironmentPostProcessor时日志记录系统尚未初始化。
然而,使用类似EnvironmentPostProcessor中的静态DeferredLog的机制来临时存储日志,然后在ApplicationListener<ApplicationPreparedEvent>中重放它们(一旦日志记录系统初始化)也不起作用,因为EnvironmentPostProcessorApplicationListener是由不同的类加载器加载和初始化的。
因此,用于ApplicationListener的Class的示例对用作EnvironmentPostProcessor的Class的示例不可见(即使它们实际上是同一个类)。
一种方法是在EnvironmentPostProcessor中使用System.setProperty(...)设置要注销的内容,在ApplicationListener中使用System.getProperty(...)设置要注销的内容。这避免了Spring类加载器的问题。我强烈建议不要使用这种方法,但它确实有效。
YMMV,但在我的例子中,我发现将自定义环境设置逻辑从EnvironmentPostProcessor移动到ApplicationListener<ApplicationPreparedEvent>对我来说工作得很好,包括日志记录。
Spring应用程序事件参考: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
bqf10yzr

bqf10yzr3#

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

public class MyEnvironmentProcessor implements EnvironmentPostProcessor, Ordered {

    public static final int ORDER = Ordered.LOWEST_PRECEDENCE - 6;

    private DeferredLog logger = new DeferredLog();

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment env, SpringApplication app) {
        app.addInitializers(new MyContextInitializer(this));
        logger.error("---------------MyEnvironmentProcessor---------------");
    }
    public DeferredLog getLogger() {
        return logger;
    }
}
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---------------
c9qzyr3d

c9qzyr3d4#

对于Kotlin中的编码:

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)
        })
    }
}
dced5bon

dced5bon5#

我知道这个问题最初是在几年前 * 在 * Spring 2.4之前问到的,但是对于现在看到这个帖子的人来说,我希望这能有所帮助。
在环境后处理器的JavaDocs中,您可以看到:
“从Sping Boot 2.4开始,EnvironmentPostProcessor实现可以选择采用以下构造函数参数:

  • DeferredLogFactory -可用于创建记录器的工厂,输出延迟到应用程序完全准备好之后(允许环境本身配置日志记录级别)。”
import org.apache.commons.logging.Log;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.env.ConfigurableEnvironment;

public class MyEnvironmentPostProcessor implements EnvironmentPostProcessor {
    private final Log log;

    public MyEnvironmentPostProcessor(DeferredLogFactory logFactory) {
        log = logFactory.getLog(MyEnvironmentPostProcessor.class);
    }
    
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        log.info("My log statement.");
    }
}

相关问题