diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SystemStatusListener.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SystemStatusListener.java index 89a257e88e3..ef90b243041 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SystemStatusListener.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SystemStatusListener.java @@ -22,6 +22,7 @@ import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.core.status.OnPrintStreamStatusListenerBase; import ch.qos.logback.core.status.Status; import ch.qos.logback.core.status.StatusListener; +import ch.qos.logback.core.status.StatusManager; /** * {@link StatusListener} used to print appropriate status messages to {@link System#out} @@ -36,6 +37,10 @@ final class SystemStatusListener extends OnPrintStreamStatusListenerBase { private SystemStatusListener(boolean debug) { this.debug = debug; + setResetResistant(false); + if (!this.debug) { + setRetrospective(0); + } } @Override @@ -57,9 +62,20 @@ final class SystemStatusListener extends OnPrintStreamStatusListenerBase { static void addTo(LoggerContext loggerContext, boolean debug) { SystemStatusListener listener = new SystemStatusListener(debug); listener.setContext(loggerContext); - if (loggerContext.getStatusManager().add(listener)) { + StatusManager sm = loggerContext.getStatusManager(); + if (!sm.getCopyOfStatusListenerList().contains(listener) && sm.add(listener)) { listener.start(); } } + @Override + public boolean equals(Object obj) { + return (obj != null) && (obj.getClass() == getClass()); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java index db6f34ce9f7..841c82d4707 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java @@ -40,6 +40,10 @@ import ch.qos.logback.core.encoder.LayoutWrappingEncoder; import ch.qos.logback.core.joran.spi.JoranException; import ch.qos.logback.core.rolling.RollingFileAppender; import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy; +import ch.qos.logback.core.status.ErrorStatus; +import ch.qos.logback.core.status.InfoStatus; +import ch.qos.logback.core.status.StatusManager; +import ch.qos.logback.core.status.WarnStatus; import ch.qos.logback.core.util.DynamicClassLoadingException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -645,13 +649,20 @@ class LogbackLoggingSystemTests extends AbstractLoggingSystemTests { System.setProperty("logback.debug", "true"); try { this.loggingSystem.beforeInitialize(); + LoggerContext loggerContext = this.logger.getLoggerContext(); + StatusManager statusManager = loggerContext.getStatusManager(); + statusManager.add(new InfoStatus("INFO STATUS MESSAGE", getClass())); + statusManager.add(new WarnStatus("WARN STATUS MESSAGE", getClass())); + statusManager.add(new ErrorStatus("ERROR STATUS MESSAGE", getClass())); File file = new File(tmpDir(), "logback-test.log"); LogFile logFile = getLogFile(file.getPath(), null); initialize(this.initializationContext, null, logFile); assertThat(output).contains("LevelChangePropagator") .contains("SizeAndTimeBasedFileNamingAndTriggeringPolicy") - .contains("DebugLogbackConfigurator"); - LoggerContext loggerContext = this.logger.getLoggerContext(); + .contains("DebugLogbackConfigurator") + .contains("INFO STATUS MESSAGE") + .contains("WARN STATUS MESSAGE") + .contains("ERROR STATUS MESSAGE"); assertThat(loggerContext.getStatusManager().getCopyOfStatusListenerList()).allSatisfy((listener) -> { assertThat(listener).isInstanceOf(SystemStatusListener.class); assertThat(listener).hasFieldOrPropertyWithValue("debug", true); @@ -663,7 +674,7 @@ class LogbackLoggingSystemTests extends AbstractLoggingSystemTests { } @Test - void logbackErrorStatusListenerShouldBeRegistered(CapturedOutput output) { + void logbackSystemStatusListenerShouldBeRegistered(CapturedOutput output) { this.loggingSystem.beforeInitialize(); initialize(this.initializationContext, null, getLogFile(tmpDir() + "/tmp.log", null)); LoggerContext loggerContext = this.logger.getLoggerContext(); @@ -680,7 +691,41 @@ class LogbackLoggingSystemTests extends AbstractLoggingSystemTests { } @Test - void logbackErrorStatusListenerShouldBeRegisteredWhenUsingCustomLogbackXml(CapturedOutput output) { + void logbackSystemStatusListenerShouldBeRegisteredOnlyOnce() { + this.loggingSystem.beforeInitialize(); + initialize(this.initializationContext, null, getLogFile(tmpDir() + "/tmp.log", null)); + LoggerContext loggerContext = this.logger.getLoggerContext(); + SystemStatusListener.addTo(loggerContext); + SystemStatusListener.addTo(loggerContext, true); + assertThat(loggerContext.getStatusManager().getCopyOfStatusListenerList()).satisfiesOnlyOnce((listener) -> { + assertThat(listener).isInstanceOf(SystemStatusListener.class); + assertThat(listener).hasFieldOrPropertyWithValue("debug", false); + }); + } + + @Test + void logbackSystemStatusListenerShouldBeRegisteredAndIgnoreAnyRetrospectiveStatusesIfDebugDisabled( + CapturedOutput output) { + this.loggingSystem.beforeInitialize(); + LoggerContext loggerContext = this.logger.getLoggerContext(); + StatusManager statusManager = loggerContext.getStatusManager(); + statusManager.add(new InfoStatus("INFO STATUS MESSAGE", getClass())); + statusManager.add(new WarnStatus("WARN STATUS MESSAGE", getClass())); + statusManager.add(new ErrorStatus("ERROR STATUS MESSAGE", getClass())); + initialize(this.initializationContext, null, getLogFile(tmpDir() + "/tmp.log", null)); + assertThat(statusManager.getCopyOfStatusListenerList()).allSatisfy((listener) -> { + assertThat(listener).isInstanceOf(SystemStatusListener.class); + assertThat(listener).hasFieldOrPropertyWithValue("debug", false); + }); + this.logger.info("Hello world"); + assertThat(output).doesNotContain("INFO STATUS MESSAGE") + .doesNotContain("WARN STATUS MESSAGE") + .doesNotContain("ERROR STATUS MESSAGE") + .contains("Hello world"); + } + + @Test + void logbackSystemStatusListenerShouldBeRegisteredWhenUsingCustomLogbackXml(CapturedOutput output) { this.loggingSystem.beforeInitialize(); initialize(this.initializationContext, "classpath:logback-include-defaults.xml", null); LoggerContext loggerContext = this.logger.getLoggerContext(); diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-structured-logging/src/test/java/smoketest/structuredlogging/SampleStructuredLoggingApplicationTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-structured-logging/src/test/java/smoketest/structuredlogging/SampleStructuredLoggingApplicationTests.java index a6f8b8fbd11..2e0159d2dab 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-structured-logging/src/test/java/smoketest/structuredlogging/SampleStructuredLoggingApplicationTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-structured-logging/src/test/java/smoketest/structuredlogging/SampleStructuredLoggingApplicationTests.java @@ -36,11 +36,12 @@ import static org.assertj.core.api.Assertions.assertThat; class SampleStructuredLoggingApplicationTests { @AfterEach - void reset() { + void reset(CapturedOutput output) { LoggingSystem.get(getClass().getClassLoader()).cleanUp(); for (LoggingSystemProperty property : LoggingSystemProperty.values()) { System.getProperties().remove(property.getEnvironmentVariableName()); } + assertThat(output).doesNotContain("-INFO in ch.qos.logback.classic.LoggerContext"); } @Test