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 1ba4586aa9..1ea850a93e 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/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java index 8c7322d50c..e10012f1e9 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java @@ -109,6 +109,7 @@ class DefaultLogbackConfiguration { config.logger("org.apache.tomcat.util.net.NioSelectorPool", Level.WARN); config.logger("org.eclipse.jetty.util.component.AbstractLifeCycle", Level.ERROR); config.logger("org.hibernate.validator.internal.util.Version", Level.WARN); + config.logger("org.springframework.boot.actuate.endpoint.jmx", Level.WARN); } private Appender consoleAppender(LogbackConfigurator config) { diff --git a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml index e351f0008f..e4d99314c9 100644 --- a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml +++ b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml @@ -18,4 +18,5 @@ Default logback configuration provided for import + 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 1a4964e2e8..4ccc6bb31a 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,10 +22,14 @@ 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 java.util.logging.Level; 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; @@ -171,6 +175,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(); @@ -181,6 +207,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(); @@ -325,4 +369,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); + + } + }