Skip to content

Commit 27ad19f

Browse files
committed
Register Logback OnErrorConsoleStatusListener when using custom Logback file
Prior to this commit, OnErrorConsoleStatusListener was not registered for a custom Logback configuration file. This commit registers OnErrorConsoleStatusListener when the Logback configuration is loaded from a custom Logback file that does not include any StatusListener. See gh-43822 Signed-off-by: Dmytro Nosan <[email protected]>
1 parent 3272148 commit 27ad19f

File tree

6 files changed

+99
-25
lines changed

6 files changed

+99
-25
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import ch.qos.logback.core.status.OnConsoleStatusListener;
3939
import ch.qos.logback.core.status.OnErrorConsoleStatusListener;
4040
import ch.qos.logback.core.status.Status;
41+
import ch.qos.logback.core.status.StatusListener;
4142
import ch.qos.logback.core.status.StatusUtil;
4243
import ch.qos.logback.core.util.StatusListenerConfigHelper;
4344
import ch.qos.logback.core.util.StatusPrinter2;
@@ -220,6 +221,7 @@ private boolean initializeFromAotGeneratedArtifactsIfPossible(LoggingInitializat
220221
configurator.setContext(loggerContext);
221222
boolean configuredUsingAotGeneratedArtifacts = configurator.configureUsingAotGeneratedArtifacts();
222223
if (configuredUsingAotGeneratedArtifacts) {
224+
addOnErrorConsoleStatusListenerIfNecessary(loggerContext);
223225
reportConfigurationErrorsIfNecessary(loggerContext);
224226
}
225227
return configuredUsingAotGeneratedArtifacts;
@@ -235,9 +237,7 @@ protected void loadDefaults(LoggingInitializationContext initializationContext,
235237
if (debug) {
236238
StatusListenerConfigHelper.addOnConsoleListenerInstance(loggerContext, new OnConsoleStatusListener());
237239
}
238-
else {
239-
addOnErrorConsoleStatusListener(loggerContext);
240-
}
240+
addOnErrorConsoleStatusListenerIfNecessary(loggerContext);
241241
Environment environment = initializationContext.getEnvironment();
242242
// Apply system properties directly in case the same JVM runs multiple apps
243243
new LogbackLoggingSystemProperties(environment, getDefaultValueResolver(environment),
@@ -264,6 +264,7 @@ protected void loadConfiguration(LoggingInitializationContext initializationCont
264264
try {
265265
Resource resource = ApplicationResourceLoader.get().getResource(location);
266266
configureByResourceUrl(initializationContext, loggerContext, resource.getURL());
267+
addOnErrorConsoleStatusListenerIfNecessary(loggerContext);
267268
}
268269
catch (Exception ex) {
269270
throw new IllegalStateException("Could not initialize Logback logging from " + location, ex);
@@ -286,7 +287,7 @@ private void reportConfigurationErrorsIfNecessary(LoggerContext loggerContext) {
286287
}
287288
}
288289
if (errors.isEmpty()) {
289-
if (!StatusUtil.contextHasStatusListener(loggerContext)) {
290+
if (shouldPrintErrors(loggerContext)) {
290291
this.statusPrinter.printInCaseOfErrorsOrWarnings(loggerContext);
291292
}
292293
return;
@@ -297,6 +298,14 @@ private void reportConfigurationErrorsIfNecessary(LoggerContext loggerContext) {
297298
throw ex;
298299
}
299300

301+
private static boolean shouldPrintErrors(LoggerContext loggerContext) {
302+
if (!StatusUtil.contextHasStatusListener(loggerContext)) {
303+
return true;
304+
}
305+
List<StatusListener> listeners = loggerContext.getStatusManager().getCopyOfStatusListenerList();
306+
return listeners.stream().allMatch((listener) -> listener instanceof FilteringStatusListener);
307+
}
308+
300309
private void configureByResourceUrl(LoggingInitializationContext initializationContext, LoggerContext loggerContext,
301310
URL url) throws JoranException {
302311
JoranConfigurator configurator = new SpringBootJoranConfigurator(initializationContext);
@@ -491,7 +500,10 @@ private void withLoggingSuppressed(Runnable action) {
491500
}
492501
}
493502

494-
private void addOnErrorConsoleStatusListener(LoggerContext context) {
503+
private void addOnErrorConsoleStatusListenerIfNecessary(LoggerContext context) {
504+
if (StatusUtil.contextHasStatusListener(context)) {
505+
return;
506+
}
495507
FilteringStatusListener listener = new FilteringStatusListener(new OnErrorConsoleStatusListener(),
496508
Status.ERROR);
497509
listener.setContext(context);

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
import ch.qos.logback.core.status.OnConsoleStatusListener;
4444
import ch.qos.logback.core.status.OnErrorConsoleStatusListener;
4545
import ch.qos.logback.core.status.Status;
46-
import ch.qos.logback.core.status.StatusListener;
4746
import ch.qos.logback.core.util.DynamicClassLoadingException;
4847
import org.junit.jupiter.api.AfterEach;
4948
import org.junit.jupiter.api.BeforeEach;
@@ -656,10 +655,8 @@ void logbackDebugPropertyIsHonored(CapturedOutput output) {
656655
.contains("SizeAndTimeBasedFileNamingAndTriggeringPolicy")
657656
.contains("DebugLogbackConfigurator");
658657
LoggerContext loggerContext = this.logger.getLoggerContext();
659-
List<StatusListener> statusListeners = loggerContext.getStatusManager().getCopyOfStatusListenerList();
660-
assertThat(statusListeners).hasSize(1);
661-
StatusListener statusListener = statusListeners.get(0);
662-
assertThat(statusListener).isInstanceOf(OnConsoleStatusListener.class);
658+
assertThat(loggerContext.getStatusManager().getCopyOfStatusListenerList())
659+
.allSatisfy((listener) -> assertThat(listener).isInstanceOf(OnConsoleStatusListener.class));
663660
}
664661
finally {
665662
System.clearProperty("logback.debug");
@@ -671,25 +668,46 @@ void logbackErrorStatusListenerShouldBeRegistered(CapturedOutput output) {
671668
this.loggingSystem.beforeInitialize();
672669
initialize(this.initializationContext, null, getLogFile(tmpDir() + "/tmp.log", null));
673670
LoggerContext loggerContext = this.logger.getLoggerContext();
674-
List<StatusListener> statusListeners = loggerContext.getStatusManager().getCopyOfStatusListenerList();
675-
assertThat(statusListeners).hasSize(1);
676-
StatusListener statusListener = statusListeners.get(0);
677-
assertThat(statusListener).isInstanceOf(FilteringStatusListener.class);
678-
assertThat(statusListener).hasFieldOrPropertyWithValue("levelThreshold", Status.ERROR);
679-
assertThat(statusListener).extracting("delegate").isInstanceOf(OnErrorConsoleStatusListener.class);
680-
AppenderBase<ILoggingEvent> appender = new AppenderBase<>() {
681-
682-
@Override
683-
protected void append(ILoggingEvent eventObject) {
684-
throw new IllegalStateException("Fail to append");
685-
}
686-
687-
};
671+
assertThat(loggerContext.getStatusManager().getCopyOfStatusListenerList()).allSatisfy((listener) -> {
672+
assertThat(listener).isInstanceOf(FilteringStatusListener.class);
673+
assertThat(listener).hasFieldOrPropertyWithValue("levelThreshold", Status.ERROR);
674+
assertThat(listener).extracting("delegate").isInstanceOf(OnErrorConsoleStatusListener.class);
675+
});
676+
AlwaysFailAppender appender = new AlwaysFailAppender();
677+
appender.setContext(loggerContext);
678+
appender.start();
688679
this.logger.addAppender(appender);
680+
this.logger.info("Hello world");
681+
assertThat(output).contains("Always Fail Appender").contains("Hello world");
682+
}
683+
684+
@Test
685+
void logbackErrorStatusListenerShouldBeRegisteredWhenUsingCustomLogbackXml(CapturedOutput output) {
686+
this.loggingSystem.beforeInitialize();
687+
initialize(this.initializationContext, "classpath:logback-include-defaults.xml", null);
688+
LoggerContext loggerContext = this.logger.getLoggerContext();
689+
assertThat(loggerContext.getStatusManager().getCopyOfStatusListenerList()).allSatisfy((listener) -> {
690+
assertThat(listener).isInstanceOf(FilteringStatusListener.class);
691+
assertThat(listener).hasFieldOrPropertyWithValue("levelThreshold", Status.ERROR);
692+
assertThat(listener).extracting("delegate").isInstanceOf(OnErrorConsoleStatusListener.class);
693+
});
694+
AlwaysFailAppender appender = new AlwaysFailAppender();
689695
appender.setContext(loggerContext);
690696
appender.start();
697+
this.logger.addAppender(appender);
691698
this.logger.info("Hello world");
692-
assertThat(output).contains("Fail to append").contains("Hello world");
699+
assertThat(output).contains("Always Fail Appender").contains("Hello world");
700+
}
701+
702+
@Test
703+
void logbackErrorStatusListenerShouldNotBeRegisteredWhenCustomLogbackXmlHasStatusListener(CapturedOutput output) {
704+
this.loggingSystem.beforeInitialize();
705+
initialize(this.initializationContext, "classpath:logback-include-status-listener.xml", null);
706+
LoggerContext loggerContext = this.logger.getLoggerContext();
707+
assertThat(loggerContext.getStatusManager().getCopyOfStatusListenerList())
708+
.allSatisfy((listener) -> assertThat(listener).isInstanceOf(OnConsoleStatusListener.class));
709+
this.logger.info("Hello world");
710+
assertThat(output).contains("Hello world");
693711
}
694712

695713
@Test
@@ -1042,4 +1060,13 @@ private static SizeAndTimeBasedRollingPolicy<?> getRollingPolicy() {
10421060
return (SizeAndTimeBasedRollingPolicy<?>) getFileAppender().getRollingPolicy();
10431061
}
10441062

1063+
private static final class AlwaysFailAppender extends AppenderBase<ILoggingEvent> {
1064+
1065+
@Override
1066+
protected void append(ILoggingEvent eventObject) {
1067+
throw new RuntimeException("Always Fail Appender");
1068+
}
1069+
1070+
}
1071+
10451072
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<configuration>
3+
<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/>
4+
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
5+
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
6+
<encoder>
7+
<pattern>[%p] - %m%n</pattern>
8+
</encoder>
9+
</appender>
10+
<root level="INFO">
11+
<appender-ref ref="CONSOLE"/>
12+
</root>
13+
</configuration>

spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-structured-logging/src/main/resources/application.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@ logging.structured.format.console=smoketest.structuredlogging.CustomStructuredLo
1010
#---
1111
spring.config.activate.on-profile=on-error
1212
logging.structured.json.customizer=smoketest.structuredlogging.DuplicateJsonMembersCustomizer
13+
#---
14+
logging.config=classpath:custom-logback.xml
15+
spring.config.activate.on-profile=on-error-custom-logback-file
16+
logging.structured.json.customizer=smoketest.structuredlogging.DuplicateJsonMembersCustomizer
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<configuration>
2+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
3+
<encoder class="org.springframework.boot.logging.logback.StructuredLogEncoder">
4+
<format>ecs</format>
5+
<charset>UTF-8</charset>
6+
</encoder>
7+
</appender>
8+
<root level="INFO">
9+
<appender-ref ref="STDOUT"/>
10+
</root>
11+
</configuration>

spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-structured-logging/src/test/java/smoketest/structuredlogging/SampleStructuredLoggingApplicationTests.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,11 @@ void shouldCaptureCustomizerError(CapturedOutput output) {
7171
assertThat(output).contains("The name 'test' has already been written");
7272
}
7373

74+
@Test
75+
void shouldCaptureCustomizerErrorWhenUsingCustomLogbackFile(CapturedOutput output) {
76+
SampleStructuredLoggingApplication
77+
.main(new String[] { "--spring.profiles.active=on-error-custom-logback-file" });
78+
assertThat(output).contains("The name 'test' has already been written");
79+
}
80+
7481
}

0 commit comments

Comments
 (0)