diff --git a/spring-boot-project/spring-boot-starters/spring-boot-starter-log4j2/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-log4j2/build.gradle index a7336d6c952..4ae6a334a3a 100644 --- a/spring-boot-project/spring-boot-starters/spring-boot-starter-log4j2/build.gradle +++ b/spring-boot-project/spring-boot-starters/spring-boot-starter-log4j2/build.gradle @@ -8,5 +8,4 @@ dependencies { api("org.apache.logging.log4j:log4j-slf4j-impl") api("org.apache.logging.log4j:log4j-core") api("org.apache.logging.log4j:log4j-jul") - api("org.slf4j:jul-to-slf4j") } diff --git a/spring-boot-project/spring-boot/build.gradle b/spring-boot-project/spring-boot/build.gradle index 3429211341d..680aa43df3f 100644 --- a/spring-boot-project/spring-boot/build.gradle +++ b/spring-boot-project/spring-boot/build.gradle @@ -52,6 +52,7 @@ dependencies { optional("org.apache.httpcomponents.client5:httpclient5") optional("org.apache.logging.log4j:log4j-api") optional("org.apache.logging.log4j:log4j-core") + optional("org.apache.logging.log4j:log4j-jul") optional("org.apache.tomcat.embed:tomcat-embed-core") optional("org.apache.tomcat.embed:tomcat-embed-jasper") optional("org.apache.tomcat:tomcat-jdbc") diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/Slf4JLoggingSystem.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/Slf4JLoggingSystem.java index f153f0281ed..31a3c7c81fe 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/Slf4JLoggingSystem.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/Slf4JLoggingSystem.java @@ -87,7 +87,7 @@ public abstract class Slf4JLoggingSystem extends AbstractLoggingSystem { return ClassUtils.isPresent(BRIDGE_HANDLER, getClassLoader()); } - private boolean isJulUsingASingleConsoleHandlerAtMost() { + protected boolean isJulUsingASingleConsoleHandlerAtMost() { Logger rootLogger = LogManager.getLogManager().getLogger(""); Handler[] handlers = rootLogger.getHandlers(); return handlers.length == 0 || (handlers.length == 1 && handlers[0] instanceof ConsoleHandler); @@ -103,7 +103,7 @@ public abstract class Slf4JLoggingSystem extends AbstractLoggingSystem { } } - private void removeDefaultRootHandler() { + protected void removeDefaultRootHandler() { try { Logger rootLogger = LogManager.getLogManager().getLogger(""); Handler[] handlers = rootLogger.getHandlers(); 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 19121a82cf1..22155fa271f 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 @@ -25,6 +25,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.logging.Handler; import java.util.stream.Collectors; import org.apache.logging.log4j.Level; @@ -42,6 +43,7 @@ import org.apache.logging.log4j.core.config.LoggerConfig; import org.apache.logging.log4j.core.config.composite.CompositeConfiguration; import org.apache.logging.log4j.core.filter.AbstractFilter; import org.apache.logging.log4j.core.util.NameUtil; +import org.apache.logging.log4j.jul.Log4jBridgeHandler; import org.apache.logging.log4j.message.Message; import org.springframework.boot.context.properties.bind.BindResult; @@ -75,6 +77,10 @@ public class Log4J2LoggingSystem extends Slf4JLoggingSystem { private static final String FILE_PROTOCOL = "file"; + private static final String LOG4J_BRIDGE_HANDLER = "org.apache.logging.log4j.jul.Log4jBridgeHandler"; + + private static final String LOG4J_LOG_MANAGER = "org.apache.logging.log4j.jul.LogManager"; + private static final LogLevels LEVELS = new LogLevels<>(); static { @@ -155,10 +161,51 @@ public class Log4J2LoggingSystem extends Slf4JLoggingSystem { if (isAlreadyInitialized(loggerContext)) { return; } - super.beforeInitialize(); + if (!configureJdkLoggingBridgeHandler()) { + super.beforeInitialize(); + } loggerContext.getConfiguration().addFilter(FILTER); } + private boolean configureJdkLoggingBridgeHandler() { + try { + if (isJulUsingASingleConsoleHandlerAtMost() && !isLog4jLogManagerInstalled()) { + if (isLog4jBridgeHandlerAvailable()) { + removeDefaultRootHandler(); + installLog4jBridgeHandler(); + return true; + } + } + } + catch (Throwable ex) { + // Ignore. No java.util.logging bridge is installed. + } + return false; + } + + private boolean isLog4jLogManagerInstalled() { + final String logManagerClassName = java.util.logging.LogManager.getLogManager().getClass().getName(); + return LOG4J_LOG_MANAGER.equals(logManagerClassName); + } + + private boolean isLog4jBridgeHandlerAvailable() { + return ClassUtils.isPresent(LOG4J_BRIDGE_HANDLER, getClassLoader()); + } + + private void installLog4jBridgeHandler() { + Log4jBridgeHandler.install(false, null, true); + } + + private void removeLog4jBridgeHandler() { + java.util.logging.Logger rootLogger = java.util.logging.LogManager.getLogManager().getLogger(""); + for (final Handler handler : rootLogger.getHandlers()) { + if (handler instanceof Log4jBridgeHandler) { + handler.close(); + rootLogger.removeHandler(handler); + } + } + } + @Override public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) { LoggerContext loggerContext = getLoggerContext(); @@ -366,6 +413,9 @@ public class Log4J2LoggingSystem extends Slf4JLoggingSystem { @Override public void cleanUp() { + if (isLog4jBridgeHandlerAvailable()) { + removeLog4jBridgeHandler(); + } super.cleanUp(); LoggerContext loggerContext = getLoggerContext(); markAsUninitialized(loggerContext); 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 96e719c7af4..7aa86c9fd8b 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 @@ -25,6 +25,8 @@ import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.logging.Handler; +import java.util.logging.Level; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.logging.Log; @@ -37,10 +39,10 @@ import org.apache.logging.log4j.core.config.LoggerConfig; import org.apache.logging.log4j.core.config.Reconfigurable; import org.apache.logging.log4j.core.config.composite.CompositeConfiguration; import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry; +import org.apache.logging.log4j.jul.Log4jBridgeHandler; import org.apache.logging.log4j.util.PropertiesUtil; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -247,7 +249,6 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests { } @Test - @Disabled("Uses Logback unintentionally") void loggingThatUsesJulIsCaptured(CapturedOutput output) { this.loggingSystem.beforeInitialize(); this.loggingSystem.initialize(this.initializationContext, null, null); @@ -381,6 +382,22 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests { .isEqualTo(new LoggerConfiguration("com.example.test", LogLevel.WARN, LogLevel.WARN)); } + @Test + void log4jLevelsArePropagatedToJul() { + this.loggingSystem.beforeInitialize(); + java.util.logging.Logger rootLogger = java.util.logging.Logger.getLogger(""); + // check if Log4jBridgeHandler is used + Handler[] handlers = rootLogger.getHandlers(); + assertThat(handlers.length).isEqualTo(1); + assertThat(handlers[0]).isInstanceOf(Log4jBridgeHandler.class); + + this.loggingSystem.initialize(this.initializationContext, null, null); + java.util.logging.Logger logger = java.util.logging.Logger.getLogger(Log4J2LoggingSystemTests.class.getName()); + assertThat(logger.getLevel()).isNull(); + this.loggingSystem.setLogLevel(Log4J2LoggingSystemTests.class.getName(), LogLevel.DEBUG); + assertThat(logger.getLevel()).isEqualTo(Level.FINE); + } + @Test void shutdownHookIsDisabled() { assertThat(