Register Logback StatusListener when using custom Logback file

Update `LogbackLoggingSystem` so that the `OnErrorConsoleStatusListener`
is also registered when loading a custom Logback configuration file.

See gh-43931

Signed-off-by: Dmytro Nosan <dimanosan@gmail.com>
This commit is contained in:
Dmytro Nosan 2025-01-22 16:26:53 +02:00 committed by Phillip Webb
parent b9f3d522cf
commit 258eb29978
6 changed files with 75 additions and 22 deletions

View File

@ -216,6 +216,7 @@ public class LogbackLoggingSystem extends AbstractLoggingSystem implements BeanF
LoggerContext loggerContext = getLoggerContext();
stopAndReset(loggerContext);
withLoggingSuppressed(() -> putInitializationContextObjects(loggerContext, initializationContext));
addOnErrorConsoleStatusListener(loggerContext);
SpringBootJoranConfigurator configurator = new SpringBootJoranConfigurator(initializationContext);
configurator.setContext(loggerContext);
boolean configuredUsingAotGeneratedArtifacts = configurator.configureUsingAotGeneratedArtifacts();
@ -261,6 +262,7 @@ public class LogbackLoggingSystem extends AbstractLoggingSystem implements BeanF
if (initializationContext != null) {
applySystemProperties(initializationContext.getEnvironment(), logFile);
}
addOnErrorConsoleStatusListener(loggerContext);
try {
Resource resource = ApplicationResourceLoader.get().getResource(location);
configureByResourceUrl(initializationContext, loggerContext, resource.getURL());
@ -493,7 +495,7 @@ public class LogbackLoggingSystem extends AbstractLoggingSystem implements BeanF
private void addOnErrorConsoleStatusListener(LoggerContext context) {
FilteringStatusListener listener = new FilteringStatusListener(new OnErrorConsoleStatusListener(),
Status.ERROR);
Status.WARN);
listener.setContext(context);
if (context.getStatusManager().add(listener)) {
listener.start();

View File

@ -43,7 +43,6 @@ import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy;
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 @@ class LogbackLoggingSystemTests extends AbstractLoggingSystemTests {
.contains("SizeAndTimeBasedFileNamingAndTriggeringPolicy")
.contains("DebugLogbackConfigurator");
LoggerContext loggerContext = this.logger.getLoggerContext();
List<StatusListener> 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,35 @@ class LogbackLoggingSystemTests extends AbstractLoggingSystemTests {
this.loggingSystem.beforeInitialize();
initialize(this.initializationContext, null, getLogFile(tmpDir() + "/tmp.log", null));
LoggerContext loggerContext = this.logger.getLoggerContext();
List<StatusListener> 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<ILoggingEvent> appender = new AppenderBase<>() {
@Override
protected void append(ILoggingEvent eventObject) {
throw new IllegalStateException("Fail to append");
}
};
this.logger.addAppender(appender);
assertThat(loggerContext.getStatusManager().getCopyOfStatusListenerList()).allSatisfy((listener) -> {
assertThat(listener).isInstanceOf(FilteringStatusListener.class);
assertThat(listener).hasFieldOrPropertyWithValue("levelThreshold", Status.WARN);
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 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.WARN);
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
@ -1042,4 +1049,13 @@ class LogbackLoggingSystemTests extends AbstractLoggingSystemTests {
return (SizeAndTimeBasedRollingPolicy<?>) getFileAppender().getRollingPolicy();
}
private static final class AlwaysFailAppender extends AppenderBase<ILoggingEvent> {
@Override
protected void append(ILoggingEvent eventObject) {
throw new RuntimeException("Always Fail Appender");
}
}
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%p] - %m%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>

View File

@ -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

View File

@ -0,0 +1,11 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="org.springframework.boot.logging.logback.StructuredLogEncoder">
<format>ecs</format>
<charset>UTF-8</charset>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>

View File

@ -71,4 +71,11 @@ class SampleStructuredLoggingApplicationTests {
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");
}
}