From 157979adc8ee8f3c490f2898728094adc9116e55 Mon Sep 17 00:00:00 2001 From: Rameel Rizvi Date: Fri, 17 Jul 2026 18:46:40 -0400 Subject: [PATCH] Restore default JUL root handler on Logback cleanup When the JUL-to-SLF4J bridge is installed, the default java.util.logging root ConsoleHandler is removed. When the logging system is cleaned up (for example when an ApplicationFailedEvent is published), the bridge is uninstalled but the ConsoleHandler was not restored, leaving the JUL root logger with no handlers. Anything subsequently logged through JUL, such as a startup failure reported via a JUL-backed commons-logging Log, was then silently discarded. Restore the default ConsoleHandler when uninstalling the bridge so that JUL logging continues to work after cleanup. See gh-50404 Signed-off-by: Rameel Rizvi --- .../logging/logback/LogbackLoggingSystem.java | 25 ++++++++++++++ .../logback/LogbackLoggingSystemTests.java | 33 +++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java index b772c0885e36..bca027579521 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java @@ -76,6 +76,7 @@ * @author Andy Wilkinson * @author Ben Hale * @author Dhruv Rastogi + * @author Rameel Rizvi * @since 1.0.0 */ public class LogbackLoggingSystem extends AbstractLoggingSystem implements BeanFactoryInitializationAotProcessor { @@ -114,6 +115,8 @@ public FilterReply decide(Marker marker, ch.qos.logback.classic.Logger logger, L private boolean bridgeHandlerInstalled; + private boolean rootConsoleHandlerRemoved; + public LogbackLoggingSystem(ClassLoader classLoader) { super(classLoader); } @@ -182,6 +185,23 @@ private void removeDefaultRootHandler() { Handler[] handlers = rootLogger.getHandlers(); if (handlers.length == 1 && handlers[0] instanceof ConsoleHandler) { rootLogger.removeHandler(handlers[0]); + this.rootConsoleHandlerRemoved = true; + } + } + catch (Throwable ex) { + // Ignore and continue + } + } + + private void restoreDefaultRootHandler() { + if (!this.rootConsoleHandlerRemoved) { + return; + } + this.rootConsoleHandlerRemoved = false; + try { + java.util.logging.Logger rootLogger = LogManager.getLogManager().getLogger(""); + if (rootLogger.getHandlers().length == 0) { + rootLogger.addHandler(new ConsoleHandler()); } } catch (Throwable ex) { @@ -340,6 +360,11 @@ public void cleanUp() { super.cleanUp(); if (this.bridgeHandlerInstalled) { removeJdkLoggingBridgeHandler(); + // Restore the default JUL root handler that was removed when the bridge + // was installed. Without this, anything logged through JUL after cleanup + // (for example a startup failure reported via a JUL-backed commons-logging + // Log) would be silently discarded (see gh-50404). + restoreDefaultRootHandler(); this.bridgeHandlerInstalled = false; } context.getStatusManager().clear(); diff --git a/core/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java b/core/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java index 02d9a1d39b46..b9995c720e6b 100644 --- a/core/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java +++ b/core/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.logging.ConsoleHandler; import java.util.logging.Handler; import java.util.logging.LogManager; import java.util.stream.Stream; @@ -108,6 +109,7 @@ * @author Jonatan Ivanov * @author Moritz Halbritter * @author Dhruv Rastogi + * @author Rameel Rizvi */ @ExtendWith(OutputCaptureExtension.class) @ClassPathExclusions({ "log4j-core-*.jar", "log4j-api-*.jar" }) @@ -355,6 +357,37 @@ void cleanUpLeavesBridgeHandlerInstalledByTheApplicationInPlace() { } } + @Test + void cleanUpRestoresDefaultRootHandlerToAvoidSilentlyDiscardingJulLogging(CapturedOutput output) { + // gh-50404: installing the bridge removes the default JUL root ConsoleHandler. + // If cleanUp does not restore it, anything subsequently logged through JUL + // (such as a startup failure reported via a JUL-backed commons-logging Log) is + // silently discarded. Start from the default single-ConsoleHandler state that a + // freshly started application has. + java.util.logging.Logger rootLogger = LogManager.getLogManager().getLogger(""); + Handler[] originalHandlers = rootLogger.getHandlers(); + for (Handler handler : originalHandlers) { + rootLogger.removeHandler(handler); + } + rootLogger.addHandler(new ConsoleHandler()); + try { + this.loggingSystem.beforeInitialize(); + assertThat(bridgeHandlerInstalled()).isTrue(); + this.loggingSystem.cleanUp(); + assertThat(rootLogger.getHandlers()).isNotEmpty(); + java.util.logging.Logger.getLogger(getClass().getName()).severe("JUL logging after cleanup"); + assertThat(output).contains("JUL logging after cleanup"); + } + finally { + for (Handler handler : rootLogger.getHandlers()) { + rootLogger.removeHandler(handler); + } + for (Handler handler : originalHandlers) { + rootLogger.addHandler(handler); + } + } + } + @Test void standardConfigLocations() { String[] locations = this.loggingSystem.getStandardConfigLocations();