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();