From 22958097e38a895cfc47dbd1a36df1c7ec0a2e0b Mon Sep 17 00:00:00 2001 From: Dmytro Nosan Date: Mon, 3 Feb 2025 22:01:13 +0200 Subject: [PATCH] Register Log42J StatusListener Update Log42JLoggingSystem to register Boot's StatusListener instead of resetting the fallbackListener during initialization. This fixes an issue when resetting the fallback listener leads to closing standard streams. See gh-44380 Signed-off-by: Dmytro Nosan --- .../logging/log4j2/Log4J2LoggingSystem.java | 32 +++++++-------- .../log4j2/Log4J2LoggingSystemTests.java | 40 ++++++++++++++++++- 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java index 4ea8863aeba..55b71ec8d89 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java @@ -47,6 +47,7 @@ import org.apache.logging.log4j.core.net.ssl.SslConfigurationFactory; import org.apache.logging.log4j.core.util.AuthorizationProvider; import org.apache.logging.log4j.core.util.NameUtil; import org.apache.logging.log4j.jul.Log4jBridgeHandler; +import org.apache.logging.log4j.status.StatusConsoleListener; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.PropertiesUtil; @@ -93,6 +94,9 @@ public class Log4J2LoggingSystem extends AbstractLoggingSystem { static final String ENVIRONMENT_KEY = Conventions.getQualifiedAttributeName(Log4J2LoggingSystem.class, "environment"); + static final String STATUS_LISTENER_KEY = Conventions.getQualifiedAttributeName(Log4J2LoggingSystem.class, + "statusListener"); + private static final LogLevels LEVELS = new LogLevels<>(); static { @@ -214,10 +218,12 @@ public class Log4J2LoggingSystem extends AbstractLoggingSystem { if (isAlreadyInitialized(loggerContext)) { return; } - resetFallbackListenerStream(StatusLogger.getLogger()); + StatusConsoleListener listener = new StatusConsoleListener(Level.WARN); + StatusLogger.getLogger().registerListener(listener); + loggerContext.putObject(STATUS_LISTENER_KEY, listener); Environment environment = initializationContext.getEnvironment(); if (environment != null) { - getLoggerContext().putObject(ENVIRONMENT_KEY, environment); + loggerContext.putObject(ENVIRONMENT_KEY, environment); Log4J2LoggingSystem.propertySource.setEnvironment(environment); PropertiesUtil.getProperties().addPropertySource(Log4J2LoggingSystem.propertySource); } @@ -226,21 +232,6 @@ public class Log4J2LoggingSystem extends AbstractLoggingSystem { markAsInitialized(loggerContext); } - /** - * Reset the stream used by the fallback listener to the current system out. This - * allows the fallback listener to work with any captured output streams in a similar - * way to the {@code follow} attribute of the {@code Console} appender. - * @param statusLogger the status logger to update - */ - private void resetFallbackListenerStream(StatusLogger statusLogger) { - try { - statusLogger.getFallbackListener().setStream(System.out); - } - catch (NoSuchMethodError ex) { - // Ignore for older versions of Log4J - } - } - @Override protected void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) { String location = getPackagedConfigFile((logFile != null) ? "log4j2-file.xml" : "log4j2.xml"); @@ -454,9 +445,14 @@ public class Log4J2LoggingSystem extends AbstractLoggingSystem { super.cleanUp(); LoggerContext loggerContext = getLoggerContext(); markAsUninitialized(loggerContext); + StatusConsoleListener listener = (StatusConsoleListener) getLoggerContext().getObject(STATUS_LISTENER_KEY); + if (listener != null) { + StatusLogger.getLogger().removeListener(listener); + loggerContext.removeObject(STATUS_LISTENER_KEY); + } loggerContext.getConfiguration().removeFilter(FILTER); Log4J2LoggingSystem.propertySource.setEnvironment(null); - getLoggerContext().removeObject(ENVIRONMENT_KEY); + loggerContext.removeObject(ENVIRONMENT_KEY); } private LoggerConfig getLogger(String name) { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java index fccfc2f8f15..24d4bfaaed4 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,8 @@ import org.apache.logging.log4j.core.config.composite.CompositeConfiguration; import org.apache.logging.log4j.core.config.plugins.util.PluginRegistry; import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry; import org.apache.logging.log4j.jul.Log4jBridgeHandler; +import org.apache.logging.log4j.status.StatusListener; +import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.PropertiesUtil; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -469,6 +471,42 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests { assertThat(environment).isSameAs(this.environment); } + @Test + void initializeRegisterStatusListenerAndAttachToLoggerContext() { + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(this.initializationContext, null, null); + LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false); + StatusListener statusListener = (StatusListener) loggerContext + .getObject(Log4J2LoggingSystem.STATUS_LISTENER_KEY); + assertThat(statusListener).isNotNull(); + assertThat(StatusLogger.getLogger().getListeners()).contains(statusListener); + } + + @Test + void statusListenerIsUpdatedUponReinitialization() { + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(this.initializationContext, null, null); + // listener should be registered + LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false); + StatusListener statusListener = (StatusListener) loggerContext + .getObject(Log4J2LoggingSystem.STATUS_LISTENER_KEY); + assertThat(statusListener).isNotNull(); + assertThat(StatusLogger.getLogger().getListeners()).contains(statusListener); + + this.loggingSystem.cleanUp(); + // listener should be removed from context and StatusLogger + assertThat(StatusLogger.getLogger().getListeners()).doesNotContain(statusListener); + assertThat(loggerContext.getObject(Log4J2LoggingSystem.STATUS_LISTENER_KEY)).isNull(); + + // a new listener should be registered + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(this.initializationContext, null, null); + StatusListener statusListener1 = (StatusListener) loggerContext + .getObject(Log4J2LoggingSystem.STATUS_LISTENER_KEY); + assertThat(statusListener1).isNotNull(); + assertThat(StatusLogger.getLogger().getListeners()).contains(statusListener1); + } + @Test void initializeAddsSpringEnvironmentPropertySource() { this.environment.setProperty("spring", "boot");