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 a7336d6c95..4ae6a334a3 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 3429211341..680aa43df3 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 deleted file mode 100644 index f153f0281e..0000000000 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/Slf4JLoggingSystem.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2012-2019 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.logging; - -import java.util.logging.ConsoleHandler; -import java.util.logging.Handler; -import java.util.logging.LogManager; -import java.util.logging.Logger; - -import org.slf4j.bridge.SLF4JBridgeHandler; - -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; - -/** - * Abstract base class for {@link LoggingSystem} implementations that utilize SLF4J. - * - * @author Andy Wilkinson - * @since 1.2.0 - */ -public abstract class Slf4JLoggingSystem extends AbstractLoggingSystem { - - private static final String BRIDGE_HANDLER = "org.slf4j.bridge.SLF4JBridgeHandler"; - - public Slf4JLoggingSystem(ClassLoader classLoader) { - super(classLoader); - } - - @Override - public void beforeInitialize() { - super.beforeInitialize(); - configureJdkLoggingBridgeHandler(); - } - - @Override - public void cleanUp() { - if (isBridgeHandlerAvailable()) { - removeJdkLoggingBridgeHandler(); - } - } - - @Override - protected void loadConfiguration(LoggingInitializationContext initializationContext, String location, - LogFile logFile) { - Assert.notNull(location, "Location must not be null"); - if (initializationContext != null) { - applySystemProperties(initializationContext.getEnvironment(), logFile); - } - } - - private void configureJdkLoggingBridgeHandler() { - try { - if (isBridgeJulIntoSlf4j()) { - removeJdkLoggingBridgeHandler(); - SLF4JBridgeHandler.install(); - } - } - catch (Throwable ex) { - // Ignore. No java.util.logging bridge is installed. - } - } - - /** - * Return whether bridging JUL into SLF4J or not. - * @return whether bridging JUL into SLF4J or not - * @since 2.0.4 - */ - protected final boolean isBridgeJulIntoSlf4j() { - return isBridgeHandlerAvailable() && isJulUsingASingleConsoleHandlerAtMost(); - } - - protected final boolean isBridgeHandlerAvailable() { - return ClassUtils.isPresent(BRIDGE_HANDLER, getClassLoader()); - } - - private boolean isJulUsingASingleConsoleHandlerAtMost() { - Logger rootLogger = LogManager.getLogManager().getLogger(""); - Handler[] handlers = rootLogger.getHandlers(); - return handlers.length == 0 || (handlers.length == 1 && handlers[0] instanceof ConsoleHandler); - } - - private void removeJdkLoggingBridgeHandler() { - try { - removeDefaultRootHandler(); - SLF4JBridgeHandler.uninstall(); - } - catch (Throwable ex) { - // Ignore and continue - } - } - - private void removeDefaultRootHandler() { - try { - Logger rootLogger = LogManager.getLogManager().getLogger(""); - Handler[] handlers = rootLogger.getHandlers(); - if (handlers.length == 1 && handlers[0] instanceof ConsoleHandler) { - rootLogger.removeHandler(handlers[0]); - } - } - catch (Throwable ex) { - // Ignore and continue - } - } - -} 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 19121a82cf..7ac194c9d0 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,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.logging.ConsoleHandler; +import java.util.logging.Handler; import java.util.stream.Collectors; import org.apache.logging.log4j.Level; @@ -42,18 +44,19 @@ 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; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.logging.AbstractLoggingSystem; import org.springframework.boot.logging.LogFile; import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.LoggerConfiguration; import org.springframework.boot.logging.LoggingInitializationContext; import org.springframework.boot.logging.LoggingSystem; import org.springframework.boot.logging.LoggingSystemFactory; -import org.springframework.boot.logging.Slf4JLoggingSystem; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.util.Assert; @@ -71,10 +74,14 @@ import org.springframework.util.StringUtils; * @author Ben Hale * @since 1.2.0 */ -public class Log4J2LoggingSystem extends Slf4JLoggingSystem { +public class Log4J2LoggingSystem extends AbstractLoggingSystem { 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 +162,66 @@ 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() + && isLog4jBridgeHandlerAvailable()) { + removeDefaultRootHandler(); + Log4jBridgeHandler.install(false, null, true); + return true; + } + } + catch (Throwable ex) { + // Ignore. No java.util.logging bridge is installed. + } + return false; + } + + private boolean isJulUsingASingleConsoleHandlerAtMost() { + java.util.logging.Logger rootLogger = java.util.logging.LogManager.getLogManager().getLogger(""); + Handler[] handlers = rootLogger.getHandlers(); + return handlers.length == 0 || (handlers.length == 1 && handlers[0] instanceof ConsoleHandler); + } + + 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 removeLog4jBridgeHandler() { + removeDefaultRootHandler(); + 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); + } + } + } + + private void removeDefaultRootHandler() { + try { + java.util.logging.Logger rootLogger = java.util.logging.LogManager.getLogManager().getLogger(""); + Handler[] handlers = rootLogger.getHandlers(); + if (handlers.length == 1 && handlers[0] instanceof ConsoleHandler) { + rootLogger.removeHandler(handlers[0]); + } + } + catch (Throwable ex) { + // Ignore and continue + } + } + @Override public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) { LoggerContext loggerContext = getLoggerContext(); @@ -189,7 +252,9 @@ public class Log4J2LoggingSystem extends Slf4JLoggingSystem { @Override protected void loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile) { - super.loadConfiguration(initializationContext, location, logFile); + if (initializationContext != null) { + applySystemProperties(initializationContext.getEnvironment(), logFile); + } loadConfiguration(location, logFile, getOverrides(initializationContext)); } @@ -366,6 +431,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/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java index 6a73a973ac..0fde1b977c 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -22,6 +22,7 @@ import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.logging.ConsoleHandler; import java.util.logging.Handler; import java.util.logging.LogManager; @@ -42,6 +43,7 @@ import org.slf4j.Marker; import org.slf4j.bridge.SLF4JBridgeHandler; import org.slf4j.impl.StaticLoggerBinder; +import org.springframework.boot.logging.AbstractLoggingSystem; import org.springframework.boot.logging.LogFile; import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.LoggerConfiguration; @@ -49,7 +51,6 @@ import org.springframework.boot.logging.LoggingInitializationContext; import org.springframework.boot.logging.LoggingSystem; import org.springframework.boot.logging.LoggingSystemFactory; import org.springframework.boot.logging.LoggingSystemProperties; -import org.springframework.boot.logging.Slf4JLoggingSystem; import org.springframework.core.Ordered; import org.springframework.core.SpringProperties; import org.springframework.core.annotation.Order; @@ -69,7 +70,9 @@ import org.springframework.util.StringUtils; * @author Ben Hale * @since 1.0.0 */ -public class LogbackLoggingSystem extends Slf4JLoggingSystem { +public class LogbackLoggingSystem extends AbstractLoggingSystem { + + private static final String BRIDGE_HANDLER = "org.slf4j.bridge.SLF4JBridgeHandler"; // Static final field to facilitate code removal by Graal private static final boolean XML_ENABLED = !SpringProperties.getFlag("spring.xml.ignore"); @@ -120,9 +123,59 @@ public class LogbackLoggingSystem extends Slf4JLoggingSystem { return; } super.beforeInitialize(); + configureJdkLoggingBridgeHandler(); loggerContext.getTurboFilterList().add(FILTER); } + private void configureJdkLoggingBridgeHandler() { + try { + if (isBridgeJulIntoSlf4j()) { + removeJdkLoggingBridgeHandler(); + SLF4JBridgeHandler.install(); + } + } + catch (Throwable ex) { + // Ignore. No java.util.logging bridge is installed. + } + } + + private boolean isBridgeJulIntoSlf4j() { + return isBridgeHandlerAvailable() && isJulUsingASingleConsoleHandlerAtMost(); + } + + private boolean isBridgeHandlerAvailable() { + return ClassUtils.isPresent(BRIDGE_HANDLER, getClassLoader()); + } + + private boolean isJulUsingASingleConsoleHandlerAtMost() { + java.util.logging.Logger rootLogger = LogManager.getLogManager().getLogger(""); + Handler[] handlers = rootLogger.getHandlers(); + return handlers.length == 0 || (handlers.length == 1 && handlers[0] instanceof ConsoleHandler); + } + + private void removeJdkLoggingBridgeHandler() { + try { + removeDefaultRootHandler(); + SLF4JBridgeHandler.uninstall(); + } + catch (Throwable ex) { + // Ignore and continue + } + } + + private void removeDefaultRootHandler() { + try { + java.util.logging.Logger rootLogger = LogManager.getLogManager().getLogger(""); + Handler[] handlers = rootLogger.getHandlers(); + if (handlers.length == 1 && handlers[0] instanceof ConsoleHandler) { + rootLogger.removeHandler(handlers[0]); + } + } + catch (Throwable ex) { + // Ignore and continue + } + } + @Override public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) { LoggerContext loggerContext = getLoggerContext(); @@ -158,7 +211,9 @@ public class LogbackLoggingSystem extends Slf4JLoggingSystem { @Override protected void loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile) { - super.loadConfiguration(initializationContext, location, logFile); + if (initializationContext != null) { + applySystemProperties(initializationContext.getEnvironment(), logFile); + } LoggerContext loggerContext = getLoggerContext(); stopAndReset(loggerContext); try { @@ -221,6 +276,9 @@ public class LogbackLoggingSystem extends Slf4JLoggingSystem { LoggerContext context = getLoggerContext(); markAsUninitialized(context); super.cleanUp(); + if (isBridgeHandlerAvailable()) { + removeJdkLoggingBridgeHandler(); + } context.getStatusManager().clear(); context.getTurboFilterList().remove(FILTER); } 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 96e719c7af..7aa86c9fd8 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(