From 137f4ee4515b0b9706669e3b31035078ed3680ef Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 14 Jun 2023 15:01:25 -0700 Subject: [PATCH] Support reporting of custom Log4J2 log levels from the LoggersEndpoint Support custom Log4J2 log levels by changing `LoggerConfiguration` so that it can now report levels using a `LevelConfiguration` object rather than the limited `LogLevel` enum. The `Log4J2LoggingSystem` class now uses `LevelConfiguration.ofCustom` for custom logging levels, rather than throwing an exception. The `LoggersEndpoint` has also been updated so that it can return the custom logger name. Fixes gh-35227 --- .../boot/actuate/logging/LoggersEndpoint.java | 24 ++- .../actuate/logging/LoggersEndpointTests.java | 11 + .../boot/logging/LoggerConfiguration.java | 198 +++++++++++++++--- .../boot/logging/LoggerGroups.java | 3 +- .../logging/log4j2/Log4J2LoggingSystem.java | 14 +- .../logging/LoggerConfigurationTests.java | 187 +++++++++++++++++ .../log4j2/Log4J2LoggingSystemTests.java | 13 ++ 7 files changed, 410 insertions(+), 40 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggerConfigurationTests.java diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/logging/LoggersEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/logging/LoggersEndpoint.java index 565fa076c5..d6790076ba 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/logging/LoggersEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/logging/LoggersEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 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. @@ -31,6 +31,8 @@ import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.LoggerConfiguration; +import org.springframework.boot.logging.LoggerConfiguration.ConfigurationScope; +import org.springframework.boot.logging.LoggerConfiguration.LevelConfiguration; import org.springframework.boot.logging.LoggerGroup; import org.springframework.boot.logging.LoggerGroups; import org.springframework.boot.logging.LoggingSystem; @@ -124,10 +126,14 @@ public class LoggersEndpoint { */ public static class LoggerLevels { - private String configuredLevel; + private final String configuredLevel; public LoggerLevels(LogLevel configuredLevel) { - this.configuredLevel = getName(configuredLevel); + this.configuredLevel = (configuredLevel != null) ? configuredLevel.name() : null; + } + + LoggerLevels(LevelConfiguration directConfiguration) { + this.configuredLevel = (directConfiguration != null) ? directConfiguration.getName() : null; } protected final String getName(LogLevel level) { @@ -140,6 +146,9 @@ public class LoggersEndpoint { } + /** + * Levels configured for given logger group exposed in a JSON friendly way. + */ public static class GroupLoggerLevels extends LoggerLevels { private List members; @@ -155,13 +164,16 @@ public class LoggersEndpoint { } + /** + * Levels configured for single logger group exposed in a JSON friendly way. + */ public static class SingleLoggerLevels extends LoggerLevels { - private String effectiveLevel; + private final String effectiveLevel; public SingleLoggerLevels(LoggerConfiguration configuration) { - super(configuration.getConfiguredLevel()); - this.effectiveLevel = getName(configuration.getEffectiveLevel()); + super(configuration.getLevelConfiguration(ConfigurationScope.DIRECT)); + this.effectiveLevel = configuration.getLevelConfiguration().getName(); } public String getEffectiveLevel() { diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/logging/LoggersEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/logging/LoggersEndpointTests.java index f885d51e49..59fed14967 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/logging/LoggersEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/logging/LoggersEndpointTests.java @@ -30,6 +30,7 @@ import org.springframework.boot.actuate.logging.LoggersEndpoint.LoggerLevels; import org.springframework.boot.actuate.logging.LoggersEndpoint.SingleLoggerLevels; import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.LoggerConfiguration; +import org.springframework.boot.logging.LoggerConfiguration.LevelConfiguration; import org.springframework.boot.logging.LoggerGroups; import org.springframework.boot.logging.LoggingSystem; @@ -109,6 +110,16 @@ class LoggersEndpointTests { assertThat(levels.getEffectiveLevel()).isEqualTo("DEBUG"); } + @Test // gh-35227 + void loggerLevelsWhenCustomLevelShouldReturnLevels() { + given(this.loggingSystem.getLoggerConfiguration("ROOT")) + .willReturn(new LoggerConfiguration("ROOT", null, LevelConfiguration.ofCustom("FINEST"))); + SingleLoggerLevels levels = (SingleLoggerLevels) new LoggersEndpoint(this.loggingSystem, this.loggerGroups) + .loggerLevels("ROOT"); + assertThat(levels.getConfiguredLevel()).isNull(); + assertThat(levels.getEffectiveLevel()).isEqualTo("FINEST"); + } + @Test void groupNameSpecifiedShouldReturnConfiguredLevelAndMembers() { GroupLoggerLevels levels = (GroupLoggerLevels) new LoggersEndpoint(this.loggingSystem, this.loggerGroups) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfiguration.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfiguration.java index ba2a6142f2..170244b9c6 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfiguration.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 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. @@ -16,6 +16,8 @@ package org.springframework.boot.logging; +import java.util.Objects; + import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -23,15 +25,16 @@ import org.springframework.util.ObjectUtils; * Immutable class that represents the configuration of a {@link LoggingSystem}'s logger. * * @author Ben Hale + * @author Phillip Webb * @since 1.5.0 */ public final class LoggerConfiguration { private final String name; - private final LogLevel configuredLevel; + private final LevelConfiguration levelConfiguration; - private final LogLevel effectiveLevel; + private final LevelConfiguration inheritedLevelConfiguration; /** * Create a new {@link LoggerConfiguration instance}. @@ -43,32 +46,72 @@ public final class LoggerConfiguration { Assert.notNull(name, "Name must not be null"); Assert.notNull(effectiveLevel, "EffectiveLevel must not be null"); this.name = name; - this.configuredLevel = configuredLevel; - this.effectiveLevel = effectiveLevel; + this.levelConfiguration = (configuredLevel != null) ? LevelConfiguration.of(configuredLevel) : null; + this.inheritedLevelConfiguration = LevelConfiguration.of(effectiveLevel); + } + + /** + * Create a new {@link LoggerConfiguration instance}. + * @param name the name of the logger + * @param levelConfiguration the level configuration + * @param inheritedLevelConfiguration the inherited level configuration + * @since 2.7.13 + */ + public LoggerConfiguration(String name, LevelConfiguration levelConfiguration, + LevelConfiguration inheritedLevelConfiguration) { + Assert.notNull(name, "Name must not be null"); + Assert.notNull(inheritedLevelConfiguration, "EffectiveLevelConfiguration must not be null"); + this.name = name; + this.levelConfiguration = levelConfiguration; + this.inheritedLevelConfiguration = inheritedLevelConfiguration; + } + + /** + * Returns the name of the logger. + * @return the name of the logger + */ + public String getName() { + return this.name; } /** * Returns the configured level of the logger. * @return the configured level of the logger + * @see #getLevelConfiguration(ConfigurationScope) */ public LogLevel getConfiguredLevel() { - return this.configuredLevel; + LevelConfiguration configuration = getLevelConfiguration(ConfigurationScope.DIRECT); + return (configuration != null) ? configuration.getLevel() : null; } /** * Returns the effective level of the logger. * @return the effective level of the logger + * @see #getLevelConfiguration(ConfigurationScope) */ public LogLevel getEffectiveLevel() { - return this.effectiveLevel; + return getLevelConfiguration().getLevel(); } /** - * Returns the name of the logger. - * @return the name of the logger + * Return the level configuration, considering inherited loggers. + * @return the level configuration + * @since 2.7.13 */ - public String getName() { - return this.name; + public LevelConfiguration getLevelConfiguration() { + return getLevelConfiguration(ConfigurationScope.INHERITED); + } + + /** + * Return the level configuration for the given scope. + * @param scope the configuration scope + * @return the level configuration or {@code null} for + * {@link ConfigurationScope#DIRECT direct scope} results without applied + * configuration + * @since 2.7.13 + */ + public LevelConfiguration getLevelConfiguration(ConfigurationScope scope) { + return (scope != ConfigurationScope.DIRECT) ? this.inheritedLevelConfiguration : this.levelConfiguration; } @Override @@ -76,34 +119,131 @@ public final class LoggerConfiguration { if (this == obj) { return true; } - if (obj == null) { + if (obj == null || getClass() != obj.getClass()) { return false; } - if (obj instanceof LoggerConfiguration) { - LoggerConfiguration other = (LoggerConfiguration) obj; - boolean rtn = true; - rtn = rtn && ObjectUtils.nullSafeEquals(this.name, other.name); - rtn = rtn && ObjectUtils.nullSafeEquals(this.configuredLevel, other.configuredLevel); - rtn = rtn && ObjectUtils.nullSafeEquals(this.effectiveLevel, other.effectiveLevel); - return rtn; - } - return super.equals(obj); + LoggerConfiguration other = (LoggerConfiguration) obj; + return ObjectUtils.nullSafeEquals(this.name, other.name) + && ObjectUtils.nullSafeEquals(this.levelConfiguration, other.levelConfiguration) + && ObjectUtils.nullSafeEquals(this.inheritedLevelConfiguration, other.inheritedLevelConfiguration); } @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ObjectUtils.nullSafeHashCode(this.name); - result = prime * result + ObjectUtils.nullSafeHashCode(this.configuredLevel); - result = prime * result + ObjectUtils.nullSafeHashCode(this.effectiveLevel); - return result; + return Objects.hash(this.name, this.levelConfiguration, this.inheritedLevelConfiguration); } @Override public String toString() { - return "LoggerConfiguration [name=" + this.name + ", configuredLevel=" + this.configuredLevel - + ", effectiveLevel=" + this.effectiveLevel + "]"; + return "LoggerConfiguration [name=" + this.name + ", levelConfiguration=" + this.levelConfiguration + + ", inheritedLevelConfiguration=" + this.inheritedLevelConfiguration + "]"; + } + + /** + * Supported logger configurations scopes. + * + * @since 2.7.13 + */ + public enum ConfigurationScope { + + /** + * Only return configuration that has been applied directly applied. Often + * referred to as 'configured' or 'assigned' configuration. + */ + DIRECT, + + /** + * May return configuration that has been applied to a parent logger. Often + * referred to as 'effective' configuration. + */ + INHERITED + + } + + /** + * Logger level configuration. + * + * @since 2.7.13 + */ + public static final class LevelConfiguration { + + private final String name; + + private final LogLevel logLevel; + + private LevelConfiguration(String name, LogLevel logLevel) { + this.name = name; + this.logLevel = logLevel; + } + + /** + * Return the name of the level. + * @return the level name + */ + public String getName() { + return this.name; + } + + /** + * Return the actual level value if possible. + * @return the level value + * @throws IllegalStateException if this is a {@link #isCustom() custom} level + */ + public LogLevel getLevel() { + Assert.state(this.logLevel != null, "Unable to provide LogLevel for '" + this.name + "'"); + return this.logLevel; + } + + /** + * Return if this is a custom level and cannot be represented by {@link LogLevel}. + * @return if this is a custom level + */ + public boolean isCustom() { + return this.logLevel == null; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + LevelConfiguration other = (LevelConfiguration) obj; + return this.logLevel == other.logLevel && ObjectUtils.nullSafeEquals(this.name, other.name); + } + + @Override + public int hashCode() { + return Objects.hash(this.logLevel, this.name); + } + + @Override + public String toString() { + return "LevelConfiguration [name=" + this.name + ", logLevel=" + this.logLevel + "]"; + } + + /** + * Create a new {@link LevelConfiguration} instance of the given {@link LogLevel}. + * @param logLevel the log level + * @return a new {@link LevelConfiguration} instance + */ + public static LevelConfiguration of(LogLevel logLevel) { + Assert.notNull(logLevel, "LogLevel must not be null"); + return new LevelConfiguration(logLevel.name(), logLevel); + } + + /** + * Create a new {@link LevelConfiguration} instance for a custom level name. + * @param name the log level name + * @return a new {@link LevelConfiguration} instance + */ + public static LevelConfiguration ofCustom(String name) { + Assert.hasText(name, "Name must not be empty"); + return new LevelConfiguration(name, null); + } + } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggerGroups.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggerGroups.java index e450ea0100..14d657a8e4 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggerGroups.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggerGroups.java @@ -26,7 +26,8 @@ import java.util.concurrent.ConcurrentHashMap; * * @author HaiTao Zhang * @author Phillip Webb - * @since 2.2.0 #see {@link LoggerGroup} + * @since 2.2.0 + * @see LoggerGroup */ public final class LoggerGroups implements Iterable { 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 d3ed675c88..543a6aa19b 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 @@ -50,6 +50,7 @@ import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.logging.LogFile; import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.LoggerConfiguration; +import org.springframework.boot.logging.LoggerConfiguration.LevelConfiguration; import org.springframework.boot.logging.LoggingInitializationContext; import org.springframework.boot.logging.LoggingSystem; import org.springframework.boot.logging.LoggingSystemFactory; @@ -362,13 +363,18 @@ public class Log4J2LoggingSystem extends Slf4JLoggingSystem { if (loggerConfig == null) { return null; } - LogLevel level = LEVELS.convertNativeToSystem(loggerConfig.getLevel()); + LevelConfiguration effectiveLevelConfiguration = getLevelConfiguration(loggerConfig.getLevel()); if (!StringUtils.hasLength(name) || LogManager.ROOT_LOGGER_NAME.equals(name)) { name = ROOT_LOGGER_NAME; } - boolean isLoggerConfigured = loggerConfig.getName().equals(name); - LogLevel configuredLevel = (isLoggerConfigured) ? level : null; - return new LoggerConfiguration(name, configuredLevel, level); + boolean isAssigned = loggerConfig.getName().equals(name); + LevelConfiguration assignedLevelConfiguration = (!isAssigned) ? null : effectiveLevelConfiguration; + return new LoggerConfiguration(name, assignedLevelConfiguration, effectiveLevelConfiguration); + } + + private LevelConfiguration getLevelConfiguration(Level level) { + LogLevel logLevel = LEVELS.convertNativeToSystem(level); + return (logLevel != null) ? LevelConfiguration.of(logLevel) : LevelConfiguration.ofCustom(level.name()); } @Override diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggerConfigurationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggerConfigurationTests.java new file mode 100644 index 0000000000..425e3bc0a6 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggerConfigurationTests.java @@ -0,0 +1,187 @@ +/* + * Copyright 2012-2023 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 org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.logging.LoggerConfiguration.ConfigurationScope; +import org.springframework.boot.logging.LoggerConfiguration.LevelConfiguration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +/** + * Tests for {@link LoggerConfiguration}. + * + * @author Phillip Webb + */ +class LoggerConfigurationTests { + + @Test + void createWithLogLevelWhenNameIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> new LoggerConfiguration(null, null, LogLevel.DEBUG)) + .withMessage("Name must not be null"); + } + + @Test + void createWithLogLevelWhenEffectiveLevelIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> new LoggerConfiguration("test", null, (LogLevel) null)) + .withMessage("EffectiveLevel must not be null"); + } + + @Test + void createWithLevelConfigurationWhenNameIsNullThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new LoggerConfiguration(null, null, LevelConfiguration.of(LogLevel.DEBUG))) + .withMessage("Name must not be null"); + } + + @Test + void createWithLevelConfigurationWhenEffectiveLevelIsNullThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new LoggerConfiguration("test", null, (LevelConfiguration) null)) + .withMessage("EffectiveLevelConfiguration must not be null"); + } + + @Test + void getNameReturnsName() { + LoggerConfiguration configuration = new LoggerConfiguration("test", null, + LevelConfiguration.of(LogLevel.DEBUG)); + assertThat(configuration.getName()).isEqualTo("test"); + } + + @Test + void getConfiguredLevelWhenConfiguredReturnsLevel() { + LoggerConfiguration configuration = new LoggerConfiguration("test", LevelConfiguration.of(LogLevel.DEBUG), + LevelConfiguration.of(LogLevel.DEBUG)); + assertThat(configuration.getConfiguredLevel()).isEqualTo(LogLevel.DEBUG); + } + + @Test + void getConfiguredLevelWhenNotConfiguredReturnsNull() { + LoggerConfiguration configuration = new LoggerConfiguration("test", null, + LevelConfiguration.of(LogLevel.DEBUG)); + assertThat(configuration.getConfiguredLevel()).isNull(); + } + + @Test + void getEffectiveLevelReturnsEffectiveLevel() { + LoggerConfiguration configuration = new LoggerConfiguration("test", null, + LevelConfiguration.of(LogLevel.DEBUG)); + assertThat(configuration.getEffectiveLevel()).isEqualTo(LogLevel.DEBUG); + } + + @Test + void getLevelConfigurationWithDirectScopeWhenConfiguredReturnsConfiguration() { + LevelConfiguration assigned = LevelConfiguration.of(LogLevel.DEBUG); + LoggerConfiguration configuration = new LoggerConfiguration("test", assigned, + LevelConfiguration.of(LogLevel.DEBUG)); + assertThat(configuration.getLevelConfiguration(ConfigurationScope.DIRECT)).isEqualTo(assigned); + } + + @Test + void getLevelConfigurationWithDirectScopeWhenNotConfiguredReturnsNull() { + LoggerConfiguration configuration = new LoggerConfiguration("test", null, + LevelConfiguration.of(LogLevel.DEBUG)); + assertThat(configuration.getLevelConfiguration(ConfigurationScope.DIRECT)).isNull(); + } + + @Test + void getLevelConfigurationWithInheritedScopeReturnsConfiguration() { + LevelConfiguration effective = LevelConfiguration.of(LogLevel.DEBUG); + LoggerConfiguration configuration = new LoggerConfiguration("test", null, effective); + assertThat(configuration.getLevelConfiguration(ConfigurationScope.INHERITED)).isEqualTo(effective); + } + + /** + * Tests for {@link LevelConfiguration}. + */ + @Nested + class LevelConfigurationTests { + + @Test + void ofWhenLogLevelIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> LevelConfiguration.of(null)) + .withMessage("LogLevel must not be null"); + } + + @Test + void ofCreatesConfiguration() { + LevelConfiguration configuration = LevelConfiguration.of(LogLevel.DEBUG); + assertThat(configuration.getLevel()).isEqualTo(LogLevel.DEBUG); + } + + @Test + void ofCustomWhenNameIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> LevelConfiguration.ofCustom(null)) + .withMessage("Name must not be empty"); + } + + @Test + void ofCustomWhenNameIsEmptyThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> LevelConfiguration.ofCustom("")) + .withMessage("Name must not be empty"); + } + + @Test + void ofCustomCreatesConfiguration() { + LevelConfiguration configuration = LevelConfiguration.ofCustom("FINE"); + assertThat(configuration).isNotNull(); + } + + @Test + void getNameWhenFromLogLevelReturnsName() { + LevelConfiguration configuration = LevelConfiguration.of(LogLevel.DEBUG); + assertThat(configuration.getName()).isEqualTo("DEBUG"); + } + + @Test + void getNameWhenCustomReturnsName() { + LevelConfiguration configuration = LevelConfiguration.ofCustom("FINE"); + assertThat(configuration.getName()).isEqualTo("FINE"); + } + + @Test + void getLevelWhenCustomThrowsException() { + LevelConfiguration configuration = LevelConfiguration.ofCustom("FINE"); + assertThatIllegalStateException().isThrownBy(() -> configuration.getLevel()) + .withMessage("Unable to provide LogLevel for 'FINE'"); + } + + @Test + void getLevelReturnsLevel() { + LevelConfiguration configuration = LevelConfiguration.of(LogLevel.DEBUG); + assertThat(configuration.getLevel()).isEqualTo(LogLevel.DEBUG); + } + + @Test + void isCustomWhenNotCustomReturnsFalse() { + LevelConfiguration configuration = LevelConfiguration.of(LogLevel.DEBUG); + assertThat(configuration.isCustom()).isFalse(); + } + + @Test + void isCustomWhenCustomReturnsTrue() { + LevelConfiguration configuration = LevelConfiguration.ofCustom("DEBUG"); + assertThat(configuration.isCustom()).isTrue(); + } + + } + +} 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 4db75c0e93..f76f89549c 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 @@ -29,6 +29,7 @@ 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.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; @@ -206,6 +207,18 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests { assertIsPresent("org.springframework.boot.logging.log4j2.Log4J2LoggingSystemTests$Nested", loggers, null); } + @Test // gh-35227 + void getLoggingConfigurationsWhenHasCustomLevel() { + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(this.initializationContext, null, null); + LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false); + String loggerName = getClass().getName(); + Level level = Level.forName("CUSTOM_LEVEL", 1000); + loggerContext.getConfiguration().addLogger(loggerName, new LoggerConfig(loggerName, level, true)); + LoggerConfiguration configuration = this.loggingSystem.getLoggerConfiguration(loggerName); + assertThat(configuration.getLevelConfiguration().getName()).isEqualTo("CUSTOM_LEVEL"); + } + private void assertIsPresent(String loggerName, Map loggers, LogLevel logLevel) { assertThat(loggers.containsKey(loggerName)).isTrue(); assertThat(loggers.get(loggerName)).isEqualTo(logLevel);