You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Slf4JBridgeHandler.uninstall called too early in context close, swallowing JUL log output.Β #33697
therefore JUL logging in @PreDestroy is swallowed, not logged.
This is because the uninstall was registered as a SmartLifeCycle, in LoggingApplicationListener.onApplicationPreparedEvent.
And SmartLifeCycle.stop() is called early in the AbstractApplicationContext.doClose(), before the destroyBeans().
Versions
spring-boot-2.6.13.jar / spring-boot-2.7.7.jar
jul-to-slf4j-1.7.36.jar
Context
WAR deployment
Apache Tomcat Version 9.0.53
Misc
There are multiple call paths reaching LoggingApplicationListener.cleanupLoggingSystem(). Also one for onContextClosedEvent(). but this contains shortcut code for exactly this scenario. So it looks like the SmartLifeCycle shutdown route is intentional, but unclear to me why the shutdown of Slf4JBridgeHandler should happen so early in the shutdown.
This behaviour was improved in 45ad155 but it sounds like it didn't go far enough for your needs. I'm not sure if we'll be able to improve things further, but we can take a look. To help us to do so, please provide a complete yet minimal sample that reproduces the problem. You can share such a sample with us by pushing it to a separate repository on GitHub or by zipping it up and attaching it to this issue.
When it turns out that it might not be easy to fix this in AbstractApplicationContext.doClose(), we could discuss the need of calling the Slf4JLoggingSystem.removeJdkLoggingBridgeHandler() in Slf4JLoggingSystem.cleanup(). All it does is removing a reference from the JUL logging handlers lists, no listener calling or cleaning resources (so it looks).
It would be easily fixed by adding a property which shortcuts this behaviour, but i'm not familiar with how easy this is perceived in the whole spring project.
I will try to bump the version to a higher level, this will take some time. If this doesnt fix things, i will build minimal sample.
what stands out here, that although Slf4JLoggingSystem decides not to install SLF4JBridgeHandler in configureJdkLoggingBridgeHandler() it still decides to uninstall it. That to me seems asymmetric.
So any change of handling SLF4JBridgeHandler in the class derived from SpringBootServletInitializer is impossible.
when deployed with tomcat the Slf4JLoggingSystem.isJulUsingASingleConsoleHandlerAtMost() returns false dependent on the logging configuration used in the tomcat server.
I would like to try a fix for this and I have read through the current code paths (LogbackLoggingSystem.configureJdkLoggingBridgeHandler / cleanUp, LoggingApplicationListener.Lifecycle after commit 45ad155). Before writing a PR I wanted to pitch the direction since the behavioral change is user-visible.
As I read it, the remaining problem has two distinct parts:
Install/uninstall asymmetry.configureJdkLoggingBridgeHandler() only installs the bridge when isBridgeJulIntoSlf4j() is true (bridge class available AND JUL has at most a single default ConsoleHandler). cleanUp() calls removeJdkLoggingBridgeHandler() whenever isBridgeHandlerAvailable() is true, i.e. whenever the class is on the classpath. If an application sets up its own JUL handlers before Spring Boot starts, we do not install, but on shutdown we still call SLF4JBridgeHandler.uninstall() and removeDefaultRootHandler(), which can clobber the user's handlers.
Timing of the bridge uninstall. Even when we did install it, the uninstall currently runs from LoggingApplicationListener.Lifecycle.stop(), which fires during stopBeans() in AbstractApplicationContext.doClose(). That is before destroyBeans() runs, so a bean's @PreDestroy-emitted JUL output has no bridge to Slf4j and is swallowed. (This is the scenario in the issue β the reproducer under logbugdemo.zip shows exactly that stack order.)
Possible fixes, in increasing order of scope:
(a) Symmetry only. Track whether we called SLF4JBridgeHandler.install() in configureJdkLoggingBridgeHandler() and guard the removeJdkLoggingBridgeHandler() call in cleanUp() on that flag. Small, clearly correct, and covers the asymmetry case. Does not address @PreDestroy timing.
(b) Timing only. Move the bridge-handler uninstall out of Lifecycle.stop() into a DisposableBean (or a @PreDestroy with an explicitly low ordering) so that it runs after user @PreDestroy methods. The existing Lifecycle still handles the rest of logging system cleanup so the web-server-shutdown improvement from JUL-based logging performed during close of application context is lostΒ #9457 / 45ad155 is preserved.
(c) Both. Apply (a) and (b) together.
My instinct is (c) with the bridge-uninstall moved to a DisposableBean registered after the LOGGING_LIFECYCLE_BEAN_NAME registration in registerLoggerGroupsIfNecessary, guarded by the install flag from (a). That keeps the behavioral change narrow (only affects the bridge handler; other logging cleanup timing is unchanged) and is symmetric.
Would any of these directions be acceptable as a PR, or do you see a different approach you'd prefer? Happy to scope down to (a) if (b)/(c) touches too much of the lifecycle model.
Thanks for taking a look at this, @daguimu. Beans are destroyed in reverse registration order (see DefaultSingletonBeanRegistry.destroySingletons()) and, therefore, @Order or Ordered have no effect. This makes guaranteeing the order and being the last bean to be destroyed rather tricky. It might be possible to register a bean really early so it's destroyed really late, but I'm not yet sure how we could do so in a robust and predictable manner. Your comment is food for thought though, so thank you for that.
Activity
wilkinsona commentedon Jan 5, 2023
This behaviour was improved in 45ad155 but it sounds like it didn't go far enough for your needs. I'm not sure if we'll be able to improve things further, but we can take a look. To help us to do so, please provide a complete yet minimal sample that reproduces the problem. You can share such a sample with us by pushing it to a separate repository on GitHub or by zipping it up and attaching it to this issue.
Please note that Spring Boot 2.6.x is no longer eligible for open source support. Any fix for this issue will only be made in 2.7.x at the earliest.
sijskes commentedon Jan 5, 2023
When it turns out that it might not be easy to fix this in AbstractApplicationContext.doClose(), we could discuss the need of calling the Slf4JLoggingSystem.removeJdkLoggingBridgeHandler() in Slf4JLoggingSystem.cleanup(). All it does is removing a reference from the JUL logging handlers lists, no listener calling or cleaning resources (so it looks).
It would be easily fixed by adding a property which shortcuts this behaviour, but i'm not familiar with how easy this is perceived in the whole spring project.
I will try to bump the version to a higher level, this will take some time. If this doesnt fix things, i will build minimal sample.
sijskes commentedon Jan 5, 2023
upgraded tot spring boot 2.7.6.
what stands out here, that although Slf4JLoggingSystem decides not to install SLF4JBridgeHandler in configureJdkLoggingBridgeHandler() it still decides to uninstall it. That to me seems asymmetric.
So any change of handling SLF4JBridgeHandler in the class derived from SpringBootServletInitializer is impossible.
sijskes commentedon Jan 6, 2023
logbugdemo.zip
minimal example demonstrating this bug
sijskes commentedon Jan 6, 2023
stackdump of preDestroy();
stackdump of cleanup();
proposed changes:
The first one is the most fundamental one, if applied it allows you to control the logging bridge at application level.
sijskes commentedon Jan 6, 2023
when deployed with tomcat the
Slf4JLoggingSystem.isJulUsingASingleConsoleHandlerAtMost()returnsfalsedependent on the logging configuration used in the tomcat server.1 remaining item
daguimu commentedon Apr 23, 2026
I would like to try a fix for this and I have read through the current code paths (
LogbackLoggingSystem.configureJdkLoggingBridgeHandler/cleanUp,LoggingApplicationListener.Lifecycleafter commit 45ad155). Before writing a PR I wanted to pitch the direction since the behavioral change is user-visible.As I read it, the remaining problem has two distinct parts:
Install/uninstall asymmetry.
configureJdkLoggingBridgeHandler()only installs the bridge whenisBridgeJulIntoSlf4j()is true (bridge class available AND JUL has at most a single defaultConsoleHandler).cleanUp()callsremoveJdkLoggingBridgeHandler()wheneverisBridgeHandlerAvailable()is true, i.e. whenever the class is on the classpath. If an application sets up its own JUL handlers before Spring Boot starts, we do not install, but on shutdown we still callSLF4JBridgeHandler.uninstall()andremoveDefaultRootHandler(), which can clobber the user's handlers.Timing of the bridge uninstall. Even when we did install it, the uninstall currently runs from
LoggingApplicationListener.Lifecycle.stop(), which fires duringstopBeans()inAbstractApplicationContext.doClose(). That is beforedestroyBeans()runs, so a bean's@PreDestroy-emitted JUL output has no bridge to Slf4j and is swallowed. (This is the scenario in the issue β the reproducer underlogbugdemo.zipshows exactly that stack order.)Possible fixes, in increasing order of scope:
SLF4JBridgeHandler.install()inconfigureJdkLoggingBridgeHandler()and guard theremoveJdkLoggingBridgeHandler()call incleanUp()on that flag. Small, clearly correct, and covers the asymmetry case. Does not address@PreDestroytiming.Lifecycle.stop()into aDisposableBean(or a@PreDestroywith an explicitly low ordering) so that it runs after user@PreDestroymethods. The existingLifecyclestill handles the rest of logging system cleanup so the web-server-shutdown improvement from JUL-based logging performed during close of application context is lostΒ #9457 / 45ad155 is preserved.My instinct is (c) with the bridge-uninstall moved to a
DisposableBeanregistered after theLOGGING_LIFECYCLE_BEAN_NAMEregistration inregisterLoggerGroupsIfNecessary, guarded by the install flag from (a). That keeps the behavioral change narrow (only affects the bridge handler; other logging cleanup timing is unchanged) and is symmetric.Would any of these directions be acceptable as a PR, or do you see a different approach you'd prefer? Happy to scope down to (a) if (b)/(c) touches too much of the lifecycle model.
wilkinsona commentedon Apr 30, 2026
Thanks for taking a look at this, @daguimu. Beans are destroyed in reverse registration order (see
DefaultSingletonBeanRegistry.destroySingletons()) and, therefore,@OrderorOrderedhave no effect. This makes guaranteeing the order and being the last bean to be destroyed rather tricky. It might be possible to register a bean really early so it's destroyed really late, but I'm not yet sure how we could do so in a robust and predictable manner. Your comment is food for thought though, so thank you for that.Avoid removing application-managed JUL bridge handler
Remove redundant comments from bridge handler tests
Clean up logging system after beans have been destroyed