The problem here is that logging system initialized only after spring context is initialized. When the log method is invoked the log system does not know what to do with the information and it does nothing.
There is no elegant way to solve this issue. You either get rid of spring-managed log system or use deferred log mechanisms (just like spring does internally).
To be able to use DeferredLog you have to make sure that after context initialization the system will request to replay logs.
Here is one of the ways how it could be achieved:
@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);
}
}
In this example every log message is cached in the DeferredLog. And once the context initialized the system will call onApplicationEvent. This method will replay all the cached log-events to the standard logger.
NOTE: I used ApplicationListener here but you can use every convenient way. The idea is to call DeferredLog.replayTo() once context initialized and it does not matter from which place you call it.
PS: The location of spring.factories should be src/main/resources/META-INF otherwise postProcessEnvironment might not be invoked.
As noted in the accepted answer, the problem is the logging system isn't initialized yet when EnvironmentPostProcessors are run.
However, using a mechanism like a static DeferredLog in EnvironmentPostProcessor to store the logs temporarily, then replay them in a ApplicationListener<ApplicationPreparedEvent> (once the logging system is initialized) does not work either because the EnvironmentPostProcessor and ApplicationListener are loaded and initialized by different class loaders.
Because of that, the instance of the Class used for the ApplicationListener has no visibility into the instance of the Class used as the EnvironmentPostProcessor (even if they are in fact the same class).
One hack would be to use System.setProperty(...) to set what you want to log out in the EnvironmentPostProcessor, and System.getProperty(...) in the ApplicationListener to log it out. This avoids the issue with Spring's class loaders. I definitely recommend against using this approach, but it does work.
YMMV, but in my case I found that moving the custom environment setup logic from an EnvironmentPostProcessor to an ApplicationListener<ApplicationPreparedEvent> worked just fine for me, logging included.
Spring Application Events reference: https://docs.spring.io/spring-boot/docs/2.2.6.RELEASE/reference/html/spring-boot-features.html#boot-features-application-events-and-listeners
UPDATE: Based on shaohua-shi's answer, here is a simple working solution that uses an ApplicationContextInitializer to replay a DeferredLog after the logging system has been initialized:
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");
}
}
Log:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.1.RELEASE)
2020-06-25 20:51:15.118 WARN 7297 --- [ restartedMain] c.e.testenvpostproc.MyEnvPostProcessor : In Env Post Processor