Fix SystemStatusListener to prevent superfluous output

Fix `SystemStatusListener` so that superfluous output is not
printed when starting an application. This change ensures that
the `SystemStatusListener` is not added twice, and that
retrospective logging only occurs when `debug` is true.

See gh-43931

Signed-off-by: Dmytro Nosan <dimanosan@gmail.com>
This commit is contained in:
Dmytro Nosan 2025-01-28 12:41:16 +02:00 committed by Phillip Webb
parent fc01b011d5
commit 6ba8e9b089
3 changed files with 68 additions and 6 deletions

View File

@ -22,6 +22,7 @@ import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.core.status.OnPrintStreamStatusListenerBase; import ch.qos.logback.core.status.OnPrintStreamStatusListenerBase;
import ch.qos.logback.core.status.Status; import ch.qos.logback.core.status.Status;
import ch.qos.logback.core.status.StatusListener; 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} * {@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) { private SystemStatusListener(boolean debug) {
this.debug = debug; this.debug = debug;
setResetResistant(false);
if (!this.debug) {
setRetrospective(0);
}
} }
@Override @Override
@ -57,9 +62,20 @@ final class SystemStatusListener extends OnPrintStreamStatusListenerBase {
static void addTo(LoggerContext loggerContext, boolean debug) { static void addTo(LoggerContext loggerContext, boolean debug) {
SystemStatusListener listener = new SystemStatusListener(debug); SystemStatusListener listener = new SystemStatusListener(debug);
listener.setContext(loggerContext); listener.setContext(loggerContext);
if (loggerContext.getStatusManager().add(listener)) { StatusManager sm = loggerContext.getStatusManager();
if (!sm.getCopyOfStatusListenerList().contains(listener) && sm.add(listener)) {
listener.start(); listener.start();
} }
} }
@Override
public boolean equals(Object obj) {
return (obj != null) && (obj.getClass() == getClass());
}
@Override
public int hashCode() {
return getClass().hashCode();
}
} }

View File

@ -40,6 +40,10 @@ import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
import ch.qos.logback.core.joran.spi.JoranException; import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.rolling.RollingFileAppender; import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy; 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 ch.qos.logback.core.util.DynamicClassLoadingException;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -645,13 +649,20 @@ class LogbackLoggingSystemTests extends AbstractLoggingSystemTests {
System.setProperty("logback.debug", "true"); System.setProperty("logback.debug", "true");
try { try {
this.loggingSystem.beforeInitialize(); 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"); File file = new File(tmpDir(), "logback-test.log");
LogFile logFile = getLogFile(file.getPath(), null); LogFile logFile = getLogFile(file.getPath(), null);
initialize(this.initializationContext, null, logFile); initialize(this.initializationContext, null, logFile);
assertThat(output).contains("LevelChangePropagator") assertThat(output).contains("LevelChangePropagator")
.contains("SizeAndTimeBasedFileNamingAndTriggeringPolicy") .contains("SizeAndTimeBasedFileNamingAndTriggeringPolicy")
.contains("DebugLogbackConfigurator"); .contains("DebugLogbackConfigurator")
LoggerContext loggerContext = this.logger.getLoggerContext(); .contains("INFO STATUS MESSAGE")
.contains("WARN STATUS MESSAGE")
.contains("ERROR STATUS MESSAGE");
assertThat(loggerContext.getStatusManager().getCopyOfStatusListenerList()).allSatisfy((listener) -> { assertThat(loggerContext.getStatusManager().getCopyOfStatusListenerList()).allSatisfy((listener) -> {
assertThat(listener).isInstanceOf(SystemStatusListener.class); assertThat(listener).isInstanceOf(SystemStatusListener.class);
assertThat(listener).hasFieldOrPropertyWithValue("debug", true); assertThat(listener).hasFieldOrPropertyWithValue("debug", true);
@ -663,7 +674,7 @@ class LogbackLoggingSystemTests extends AbstractLoggingSystemTests {
} }
@Test @Test
void logbackErrorStatusListenerShouldBeRegistered(CapturedOutput output) { void logbackSystemStatusListenerShouldBeRegistered(CapturedOutput output) {
this.loggingSystem.beforeInitialize(); this.loggingSystem.beforeInitialize();
initialize(this.initializationContext, null, getLogFile(tmpDir() + "/tmp.log", null)); initialize(this.initializationContext, null, getLogFile(tmpDir() + "/tmp.log", null));
LoggerContext loggerContext = this.logger.getLoggerContext(); LoggerContext loggerContext = this.logger.getLoggerContext();
@ -680,7 +691,41 @@ class LogbackLoggingSystemTests extends AbstractLoggingSystemTests {
} }
@Test @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(); this.loggingSystem.beforeInitialize();
initialize(this.initializationContext, "classpath:logback-include-defaults.xml", null); initialize(this.initializationContext, "classpath:logback-include-defaults.xml", null);
LoggerContext loggerContext = this.logger.getLoggerContext(); LoggerContext loggerContext = this.logger.getLoggerContext();

View File

@ -36,11 +36,12 @@ import static org.assertj.core.api.Assertions.assertThat;
class SampleStructuredLoggingApplicationTests { class SampleStructuredLoggingApplicationTests {
@AfterEach @AfterEach
void reset() { void reset(CapturedOutput output) {
LoggingSystem.get(getClass().getClassLoader()).cleanUp(); LoggingSystem.get(getClass().getClassLoader()).cleanUp();
for (LoggingSystemProperty property : LoggingSystemProperty.values()) { for (LoggingSystemProperty property : LoggingSystemProperty.values()) {
System.getProperties().remove(property.getEnvironmentVariableName()); System.getProperties().remove(property.getEnvironmentVariableName());
} }
assertThat(output).doesNotContain("-INFO in ch.qos.logback.classic.LoggerContext");
} }
@Test @Test