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 a033d72ac7db..ae6fac546695 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 @@ -38,7 +38,7 @@ */ final class SystemStatusListener extends OnConsoleStatusListener { - static final long RETROSPECTIVE_THRESHOLD = 300; + private static final long RETROSPECTIVE_THRESHOLD = 300; private final boolean debug; 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 5bd3ff185b8d..474fe2b37fa9 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 @@ -42,6 +42,7 @@ 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.OnConsoleStatusListener; import ch.qos.logback.core.status.StatusManager; import ch.qos.logback.core.status.WarnStatus; import ch.qos.logback.core.util.DynamicClassLoadingException; @@ -673,6 +674,18 @@ void logbackDebugPropertyIsHonored(CapturedOutput output) { } } + @Test + void logbackSystemStatusListenerShouldBeRegisteredWhenCustomLogbackXmlHasStatusListener(CapturedOutput output) { + this.loggingSystem.beforeInitialize(); + initialize(this.initializationContext, "classpath:logback-include-status-listener.xml", null); + LoggerContext loggerContext = this.logger.getLoggerContext(); + assertThat(loggerContext.getStatusManager().getCopyOfStatusListenerList()).hasSize(2) + .allSatisfy((listener) -> assertThat(listener).isInstanceOf(OnConsoleStatusListener.class)) + .anySatisfy((listener) -> assertThat(listener).isInstanceOf(SystemStatusListener.class)); + this.logger.info("Hello world"); + assertThat(output).contains("Hello world"); + } + @Test void logbackSystemStatusListenerShouldBeRegistered(CapturedOutput output) { this.loggingSystem.beforeInitialize(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/SystemStatusListenerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/SystemStatusListenerTests.java index a3ef3bf65937..0d98cc61fc96 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/SystemStatusListenerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/SystemStatusListenerTests.java @@ -16,6 +16,7 @@ package org.springframework.boot.logging.logback; +import java.util.List; import java.util.function.Supplier; import ch.qos.logback.classic.LoggerContext; @@ -31,6 +32,7 @@ import org.springframework.boot.testsupport.system.CapturedOutput; import org.springframework.boot.testsupport.system.OutputCaptureExtension; +import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -49,6 +51,15 @@ class SystemStatusListenerTests { private static final String TEST_MESSAGE = "testtesttest"; + private final StatusManager statusManager = mock(StatusManager.class); + + private final LoggerContext loggerContext = mock(LoggerContext.class); + + SystemStatusListenerTests() { + given(this.loggerContext.getStatusManager()).willReturn(this.statusManager); + given(this.statusManager.add(any(StatusListener.class))).willReturn(true); + } + @Test void addStatusWithInfoLevelWhenNoDebugDoesNotPrint(CapturedOutput output) { addStatus(false, () -> new InfoStatus(TEST_MESSAGE, null)); @@ -91,15 +102,43 @@ void addStatusWithErrorLevelWhenDebugPrintsToSystemOut(CapturedOutput output) { assertThat(output.getErr()).doesNotContain(TEST_MESSAGE); } + @Test + void shouldRetrospectivePrintStatusOnStartAndDebugIsDisabled(CapturedOutput output) { + given(this.statusManager.getCopyOfStatusList()).willReturn(List.of(new ErrorStatus(TEST_MESSAGE, null), + new WarnStatus(TEST_MESSAGE, null), new InfoStatus(TEST_MESSAGE, null))); + addStatus(false, () -> new InfoStatus(TEST_MESSAGE, null)); + assertThat(output.getErr()).contains("WARN " + TEST_MESSAGE); + assertThat(output.getErr()).contains("ERROR " + TEST_MESSAGE); + assertThat(output.getErr()).doesNotContain("INFO"); + assertThat(output.getOut()).isEmpty(); + } + + @Test + void shouldRetrospectivePrintStatusOnStartAndDebugIsEnabled(CapturedOutput output) { + given(this.statusManager.getCopyOfStatusList()).willReturn(List.of(new ErrorStatus(TEST_MESSAGE, null), + new WarnStatus(TEST_MESSAGE, null), new InfoStatus(TEST_MESSAGE, null))); + addStatus(true, () -> new InfoStatus(TEST_MESSAGE, null)); + assertThat(output.getErr()).isEmpty(); + assertThat(output.getOut()).contains("WARN " + TEST_MESSAGE); + assertThat(output.getOut()).contains("ERROR " + TEST_MESSAGE); + assertThat(output.getOut()).contains("INFO " + TEST_MESSAGE); + } + + @Test + void shouldNotRetrospectivePrintWhenStatusIsOutdated(CapturedOutput output) { + ErrorStatus outdatedStatus = new ErrorStatus(TEST_MESSAGE, null); + ReflectionTestUtils.setField(outdatedStatus, "timestamp", System.currentTimeMillis() - 300); + given(this.statusManager.getCopyOfStatusList()).willReturn(List.of(outdatedStatus)); + addStatus(false, () -> new InfoStatus(TEST_MESSAGE, null)); + assertThat(output.getOut()).isEmpty(); + assertThat(output.getErr()).isEmpty(); + } + private void addStatus(boolean debug, Supplier statusFactory) { - StatusManager statusManager = mock(StatusManager.class); - given(statusManager.add(any(StatusListener.class))).willReturn(true); - LoggerContext loggerContext = mock(LoggerContext.class); - given(loggerContext.getStatusManager()).willReturn(statusManager); - SystemStatusListener.addTo(loggerContext, debug); + SystemStatusListener.addTo(this.loggerContext, debug); ArgumentCaptor listener = ArgumentCaptor.forClass(StatusListener.class); - then(statusManager).should().add(listener.capture()); - assertThat(listener.getValue()).extracting("context").isSameAs(loggerContext); + then(this.statusManager).should().add(listener.capture()); + assertThat(listener.getValue()).extracting("context").isSameAs(this.loggerContext); listener.getValue().addStatusEvent(statusFactory.get()); }