From 4e014da9e9c5d8cbdb24818e7d0d6614554dd681 Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Wed, 10 Jun 2020 18:45:35 -0700 Subject: [PATCH] Return all loggers for Log4j2 This commit aligns log4j2's behavior with logback such that loggers with a null configuredLevel are also returned by the actuator endpoint. Fixes gh-20037 --- .../logging/log4j2/Log4J2LoggingSystem.java | 56 ++++++++++++++++--- .../log4j2/Log4J2LoggingSystemTests.java | 54 ++++++++++++++++++ 2 files changed, 101 insertions(+), 9 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 1ba4586aa92..1ea850a93e3 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 @@ -20,8 +20,11 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Set; import org.apache.logging.log4j.Level; @@ -31,11 +34,11 @@ import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationFactory; import org.apache.logging.log4j.core.config.ConfigurationSource; import org.apache.logging.log4j.core.config.LoggerConfig; import org.apache.logging.log4j.core.filter.AbstractFilter; +import org.apache.logging.log4j.core.util.NameUtil; import org.apache.logging.log4j.message.Message; import org.springframework.boot.logging.LogFile; @@ -221,30 +224,65 @@ public class Log4J2LoggingSystem extends Slf4JLoggingSystem { @Override public List getLoggerConfigurations() { + Map allLoggers = getAllLoggers(); List result = new ArrayList<>(); - Configuration configuration = getLoggerContext().getConfiguration(); - for (LoggerConfig loggerConfig : configuration.getLoggers().values()) { - result.add(convertLoggerConfiguration(loggerConfig)); - } + allLoggers.forEach((key, value) -> result.add(convertLoggerConfiguration(value, key))); result.sort(CONFIGURATION_COMPARATOR); return result; } + private Map getAllLoggers() { + Collection loggers = getLoggerContext().getLoggers(); + Map configuredLoggers = getLoggerContext().getConfiguration().getLoggers(); + Map result = new LinkedHashMap<>(); + for (Logger logger : loggers) { + String name = logger.getName(); + while (name != null) { + result.putIfAbsent(name, getLoggerContext().getConfiguration().getLoggerConfig(name)); + name = getSubName(name); + } + } + configuredLoggers.keySet().forEach((name) -> { + String currentName = name; + while (currentName != null) { + result.putIfAbsent(currentName, getLoggerContext().getConfiguration().getLoggerConfig(currentName)); + currentName = getSubName(currentName); + } + }); + return result; + } + + private String getSubName(String name) { + if (StringUtils.isEmpty(name)) { + return null; + } + int nested = name.lastIndexOf('$'); + if (nested != -1) { + return name.substring(0, nested); + } + return NameUtil.getSubName(name); + } + @Override public LoggerConfiguration getLoggerConfiguration(String loggerName) { - return convertLoggerConfiguration(getLoggerConfig(loggerName)); + LoggerConfig loggerConfig = getAllLoggers().get(loggerName); + if (loggerConfig == null) { + return null; + } + return convertLoggerConfiguration(loggerConfig, loggerName); } - private LoggerConfiguration convertLoggerConfiguration(LoggerConfig loggerConfig) { + private LoggerConfiguration convertLoggerConfiguration(LoggerConfig loggerConfig, String name) { if (loggerConfig == null) { return null; } LogLevel level = LEVELS.convertNativeToSystem(loggerConfig.getLevel()); - String name = loggerConfig.getName(); if (!StringUtils.hasLength(name) || LogManager.ROOT_LOGGER_NAME.equals(name)) { name = ROOT_LOGGER_NAME; } - return new LoggerConfiguration(name, level, level); + boolean isLoggerConfigured = loggerConfig.getName().equals(name); + LogLevel configuredLevel = (isLoggerConfigured) ? level : null; + return new LoggerConfiguration(name, configuredLevel, level); } @Override 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 e1a2b4b8054..aa6d08f20ea 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 @@ -22,9 +22,13 @@ import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; @@ -169,6 +173,28 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests { assertThat(configurations.get(0).getName()).isEqualTo(LoggingSystem.ROOT_LOGGER_NAME); } + @Test + void getLoggingConfigurationsShouldReturnAllLoggers() { + LogManager.getLogger("org.springframework.boot.logging.log4j2.Log4J2LoggingSystemTests$Nested"); + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(null, null, null); + this.loggingSystem.setLogLevel(getClass().getName(), LogLevel.DEBUG); + List configurations = this.loggingSystem.getLoggerConfigurations(); + assertThat(configurations).isNotEmpty(); + assertThat(configurations.get(0).getName()).isEqualTo(LoggingSystem.ROOT_LOGGER_NAME); + Map loggers = new LinkedHashMap<>(); + configurations.forEach((logger) -> loggers.put(logger.getName(), logger.getConfiguredLevel())); + assertIsPresent("org", loggers, null); + assertIsPresent("org.springframework.boot.logging.log4j2", loggers, null); + assertIsPresent("org.springframework.boot.logging.log4j2.Log4J2LoggingSystemTests", loggers, LogLevel.DEBUG); + assertIsPresent("org.springframework.boot.logging.log4j2.Log4J2LoggingSystemTests$Nested", loggers, null); + } + + private void assertIsPresent(String loggerName, Map loggers, LogLevel logLevel) { + assertThat(loggers.containsKey(loggerName)).isTrue(); + assertThat(loggers.get(loggerName)).isEqualTo(logLevel); + } + @Test void getLoggingConfiguration() { this.loggingSystem.beforeInitialize(); @@ -179,6 +205,24 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests { .isEqualTo(new LoggerConfiguration(getClass().getName(), LogLevel.DEBUG, LogLevel.DEBUG)); } + @Test + void getLoggingConfigurationShouldReturnLoggerWithNullConfiguredLevel() { + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(null, null, null); + this.loggingSystem.setLogLevel(getClass().getName(), LogLevel.DEBUG); + LoggerConfiguration configuration = this.loggingSystem.getLoggerConfiguration("org"); + assertThat(configuration).isEqualTo(new LoggerConfiguration("org", null, LogLevel.INFO)); + } + + @Test + void getLoggingConfigurationForNonExistentLoggerShouldReturnNull() { + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(null, null, null); + this.loggingSystem.setLogLevel(getClass().getName(), LogLevel.DEBUG); + LoggerConfiguration configuration = this.loggingSystem.getLoggerConfiguration("doesnotexist"); + assertThat(configuration).isEqualTo(null); + } + @Test void setLevelOfUnconfiguredLoggerDoesNotAffectRootConfiguration(CapturedOutput output) { this.loggingSystem.beforeInitialize(); @@ -321,4 +365,14 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests { } + /** + * Used for testing that loggers in nested classes are returned by + * {@link Log4J2LoggingSystem#getLoggerConfigurations()} . + */ + static class Nested { + + private static final Log logger = LogFactory.getLog(Nested.class); + + } + }