我在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吗?
答案 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>
中重播它们(一旦初始化了日志记录系统)也不起作用因为EnvironmentPostProcessor
和ApplicationListener
是由不同的类加载器加载和初始化的。
因此,用于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)
})
}
}