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 4ea8863aeba3..55b71ec8d89b 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.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 void initialize(LoggingInitializationContext initializationContext, Strin 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 void initialize(LoggingInitializationContext initializationContext, Strin 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 void cleanUp() { 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 fccfc2f8f157..24d4bfaaed4f 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.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 @@ void initializeAttachesEnvironmentToLoggerContext() { 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");