diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java index 7fcc812cd85a..d0a46d5b025d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java @@ -221,6 +221,7 @@ private boolean initializeFromAotGeneratedArtifactsIfPossible(LoggingInitializat boolean configuredUsingAotGeneratedArtifacts = configurator.configureUsingAotGeneratedArtifacts(); if (configuredUsingAotGeneratedArtifacts) { reportConfigurationErrorsIfNecessary(loggerContext); + addOnErrorConsoleStatusListener(loggerContext); } return configuredUsingAotGeneratedArtifacts; } @@ -235,9 +236,7 @@ protected void loadDefaults(LoggingInitializationContext initializationContext, if (debug) { StatusListenerConfigHelper.addOnConsoleListenerInstance(loggerContext, new OnConsoleStatusListener()); } - else { - addOnErrorConsoleStatusListener(loggerContext); - } + addOnErrorConsoleStatusListener(loggerContext); Environment environment = initializationContext.getEnvironment(); // Apply system properties directly in case the same JVM runs multiple apps new LogbackLoggingSystemProperties(environment, getDefaultValueResolver(environment), @@ -271,6 +270,7 @@ protected void loadConfiguration(LoggingInitializationContext initializationCont loggerContext.start(); }); reportConfigurationErrorsIfNecessary(loggerContext); + addOnErrorConsoleStatusListener(loggerContext); } private void reportConfigurationErrorsIfNecessary(LoggerContext loggerContext) { @@ -492,6 +492,9 @@ private void withLoggingSuppressed(Runnable action) { } private void addOnErrorConsoleStatusListener(LoggerContext context) { + if (StatusUtil.contextHasStatusListener(context)) { + return; + } FilteringStatusListener listener = new FilteringStatusListener(new OnErrorConsoleStatusListener(), Status.ERROR); listener.setContext(context); 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 966b0f593b95..e9abc0c921ea 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 @@ -43,7 +43,6 @@ import ch.qos.logback.core.status.OnConsoleStatusListener; import ch.qos.logback.core.status.OnErrorConsoleStatusListener; import ch.qos.logback.core.status.Status; -import ch.qos.logback.core.status.StatusListener; import ch.qos.logback.core.util.DynamicClassLoadingException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -656,10 +655,8 @@ void logbackDebugPropertyIsHonored(CapturedOutput output) { .contains("SizeAndTimeBasedFileNamingAndTriggeringPolicy") .contains("DebugLogbackConfigurator"); LoggerContext loggerContext = this.logger.getLoggerContext(); - List statusListeners = loggerContext.getStatusManager().getCopyOfStatusListenerList(); - assertThat(statusListeners).hasSize(1); - StatusListener statusListener = statusListeners.get(0); - assertThat(statusListener).isInstanceOf(OnConsoleStatusListener.class); + assertThat(loggerContext.getStatusManager().getCopyOfStatusListenerList()) + .allSatisfy((listener) -> assertThat(listener).isInstanceOf(OnConsoleStatusListener.class)); } finally { System.clearProperty("logback.debug"); @@ -671,25 +668,46 @@ void logbackErrorStatusListenerShouldBeRegistered(CapturedOutput output) { this.loggingSystem.beforeInitialize(); initialize(this.initializationContext, null, getLogFile(tmpDir() + "/tmp.log", null)); LoggerContext loggerContext = this.logger.getLoggerContext(); - List statusListeners = loggerContext.getStatusManager().getCopyOfStatusListenerList(); - assertThat(statusListeners).hasSize(1); - StatusListener statusListener = statusListeners.get(0); - assertThat(statusListener).isInstanceOf(FilteringStatusListener.class); - assertThat(statusListener).hasFieldOrPropertyWithValue("levelThreshold", Status.ERROR); - assertThat(statusListener).extracting("delegate").isInstanceOf(OnErrorConsoleStatusListener.class); - AppenderBase appender = new AppenderBase<>() { - - @Override - protected void append(ILoggingEvent eventObject) { - throw new IllegalStateException("Fail to append"); - } - - }; + assertThat(loggerContext.getStatusManager().getCopyOfStatusListenerList()).allSatisfy((listener) -> { + assertThat(listener).isInstanceOf(FilteringStatusListener.class); + assertThat(listener).hasFieldOrPropertyWithValue("levelThreshold", Status.ERROR); + assertThat(listener).extracting("delegate").isInstanceOf(OnErrorConsoleStatusListener.class); + }); + AlwaysFailAppender appender = new AlwaysFailAppender(); + appender.setContext(loggerContext); + appender.start(); this.logger.addAppender(appender); + this.logger.info("Hello world"); + assertThat(output).contains("Always Fail Appender").contains("Hello world"); + } + + @Test + void logbackErrorStatusListenerShouldBeRegisteredWhenUsingCustomLogbackXml(CapturedOutput output) { + this.loggingSystem.beforeInitialize(); + initialize(this.initializationContext, "classpath:logback-include-defaults.xml", null); + LoggerContext loggerContext = this.logger.getLoggerContext(); + assertThat(loggerContext.getStatusManager().getCopyOfStatusListenerList()).allSatisfy((listener) -> { + assertThat(listener).isInstanceOf(FilteringStatusListener.class); + assertThat(listener).hasFieldOrPropertyWithValue("levelThreshold", Status.ERROR); + assertThat(listener).extracting("delegate").isInstanceOf(OnErrorConsoleStatusListener.class); + }); + AlwaysFailAppender appender = new AlwaysFailAppender(); appender.setContext(loggerContext); appender.start(); + this.logger.addAppender(appender); this.logger.info("Hello world"); - assertThat(output).contains("Fail to append").contains("Hello world"); + assertThat(output).contains("Always Fail Appender").contains("Hello world"); + } + + @Test + void logbackErrorStatusListenerShouldNotBeRegisteredWhenCustomLogbackXmlHasStatusListener(CapturedOutput output) { + this.loggingSystem.beforeInitialize(); + initialize(this.initializationContext, "classpath:logback-include-status-listener.xml", null); + LoggerContext loggerContext = this.logger.getLoggerContext(); + assertThat(loggerContext.getStatusManager().getCopyOfStatusListenerList()) + .allSatisfy((listener) -> assertThat(listener).isInstanceOf(OnConsoleStatusListener.class)); + this.logger.info("Hello world"); + assertThat(output).contains("Hello world"); } @Test @@ -1042,4 +1060,13 @@ private static SizeAndTimeBasedRollingPolicy getRollingPolicy() { return (SizeAndTimeBasedRollingPolicy) getFileAppender().getRollingPolicy(); } + private static final class AlwaysFailAppender extends AppenderBase { + + @Override + protected void append(ILoggingEvent eventObject) { + throw new RuntimeException("Always Fail Appender"); + } + + } + } diff --git a/spring-boot-project/spring-boot/src/test/resources/logback-include-status-listener.xml b/spring-boot-project/spring-boot/src/test/resources/logback-include-status-listener.xml new file mode 100644 index 000000000000..6be18d7a3786 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/logback-include-status-listener.xml @@ -0,0 +1,13 @@ + + + + + + + [%p] - %m%n + + + + + + diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-structured-logging/src/main/resources/application.properties b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-structured-logging/src/main/resources/application.properties index 000769604f46..6a26dd0049b9 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-structured-logging/src/main/resources/application.properties +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-structured-logging/src/main/resources/application.properties @@ -10,3 +10,7 @@ logging.structured.format.console=smoketest.structuredlogging.CustomStructuredLo #--- spring.config.activate.on-profile=on-error logging.structured.json.customizer=smoketest.structuredlogging.DuplicateJsonMembersCustomizer +#--- +logging.config=classpath:custom-logback.xml +spring.config.activate.on-profile=on-error-custom-logback-file +logging.structured.json.customizer=smoketest.structuredlogging.DuplicateJsonMembersCustomizer diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-structured-logging/src/main/resources/custom-logback.xml b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-structured-logging/src/main/resources/custom-logback.xml new file mode 100644 index 000000000000..fd274d8cbaa3 --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-structured-logging/src/main/resources/custom-logback.xml @@ -0,0 +1,11 @@ + + + + ecs + UTF-8 + + + + + + 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 2d60bb8c8f1b..a6f8b8fbd118 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 @@ -71,4 +71,11 @@ void shouldCaptureCustomizerError(CapturedOutput output) { assertThat(output).contains("The name 'test' has already been written"); } + @Test + void shouldCaptureCustomizerErrorWhenUsingCustomLogbackFile(CapturedOutput output) { + SampleStructuredLoggingApplication + .main(new String[] { "--spring.profiles.active=on-error-custom-logback-file" }); + assertThat(output).contains("The name 'test' has already been written"); + } + }