Skip to content

Commit 137f4ee

Browse files
committed
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
1 parent e779fb0 commit 137f4ee

File tree

7 files changed

+410
-40
lines changed

7 files changed

+410
-40
lines changed

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/logging/LoggersEndpoint.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,6 +31,8 @@
3131
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
3232
import org.springframework.boot.logging.LogLevel;
3333
import org.springframework.boot.logging.LoggerConfiguration;
34+
import org.springframework.boot.logging.LoggerConfiguration.ConfigurationScope;
35+
import org.springframework.boot.logging.LoggerConfiguration.LevelConfiguration;
3436
import org.springframework.boot.logging.LoggerGroup;
3537
import org.springframework.boot.logging.LoggerGroups;
3638
import org.springframework.boot.logging.LoggingSystem;
@@ -124,10 +126,14 @@ private Map<String, LoggerLevels> getLoggers(Collection<LoggerConfiguration> con
124126
*/
125127
public static class LoggerLevels {
126128

127-
private String configuredLevel;
129+
private final String configuredLevel;
128130

129131
public LoggerLevels(LogLevel configuredLevel) {
130-
this.configuredLevel = getName(configuredLevel);
132+
this.configuredLevel = (configuredLevel != null) ? configuredLevel.name() : null;
133+
}
134+
135+
LoggerLevels(LevelConfiguration directConfiguration) {
136+
this.configuredLevel = (directConfiguration != null) ? directConfiguration.getName() : null;
131137
}
132138

133139
protected final String getName(LogLevel level) {
@@ -140,6 +146,9 @@ public String getConfiguredLevel() {
140146

141147
}
142148

149+
/**
150+
* Levels configured for given logger group exposed in a JSON friendly way.
151+
*/
143152
public static class GroupLoggerLevels extends LoggerLevels {
144153

145154
private List<String> members;
@@ -155,13 +164,16 @@ public List<String> getMembers() {
155164

156165
}
157166

167+
/**
168+
* Levels configured for single logger group exposed in a JSON friendly way.
169+
*/
158170
public static class SingleLoggerLevels extends LoggerLevels {
159171

160-
private String effectiveLevel;
172+
private final String effectiveLevel;
161173

162174
public SingleLoggerLevels(LoggerConfiguration configuration) {
163-
super(configuration.getConfiguredLevel());
164-
this.effectiveLevel = getName(configuration.getEffectiveLevel());
175+
super(configuration.getLevelConfiguration(ConfigurationScope.DIRECT));
176+
this.effectiveLevel = configuration.getLevelConfiguration().getName();
165177
}
166178

167179
public String getEffectiveLevel() {

spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/logging/LoggersEndpointTests.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springframework.boot.actuate.logging.LoggersEndpoint.SingleLoggerLevels;
3131
import org.springframework.boot.logging.LogLevel;
3232
import org.springframework.boot.logging.LoggerConfiguration;
33+
import org.springframework.boot.logging.LoggerConfiguration.LevelConfiguration;
3334
import org.springframework.boot.logging.LoggerGroups;
3435
import org.springframework.boot.logging.LoggingSystem;
3536

@@ -109,6 +110,16 @@ void loggerLevelsWhenNameSpecifiedShouldReturnLevels() {
109110
assertThat(levels.getEffectiveLevel()).isEqualTo("DEBUG");
110111
}
111112

113+
@Test // gh-35227
114+
void loggerLevelsWhenCustomLevelShouldReturnLevels() {
115+
given(this.loggingSystem.getLoggerConfiguration("ROOT"))
116+
.willReturn(new LoggerConfiguration("ROOT", null, LevelConfiguration.ofCustom("FINEST")));
117+
SingleLoggerLevels levels = (SingleLoggerLevels) new LoggersEndpoint(this.loggingSystem, this.loggerGroups)
118+
.loggerLevels("ROOT");
119+
assertThat(levels.getConfiguredLevel()).isNull();
120+
assertThat(levels.getEffectiveLevel()).isEqualTo("FINEST");
121+
}
122+
112123
@Test
113124
void groupNameSpecifiedShouldReturnConfiguredLevelAndMembers() {
114125
GroupLoggerLevels levels = (GroupLoggerLevels) new LoggersEndpoint(this.loggingSystem, this.loggerGroups)
Lines changed: 169 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,22 +16,25 @@
1616

1717
package org.springframework.boot.logging;
1818

19+
import java.util.Objects;
20+
1921
import org.springframework.util.Assert;
2022
import org.springframework.util.ObjectUtils;
2123

2224
/**
2325
* Immutable class that represents the configuration of a {@link LoggingSystem}'s logger.
2426
*
2527
* @author Ben Hale
28+
* @author Phillip Webb
2629
* @since 1.5.0
2730
*/
2831
public final class LoggerConfiguration {
2932

3033
private final String name;
3134

32-
private final LogLevel configuredLevel;
35+
private final LevelConfiguration levelConfiguration;
3336

34-
private final LogLevel effectiveLevel;
37+
private final LevelConfiguration inheritedLevelConfiguration;
3538

3639
/**
3740
* Create a new {@link LoggerConfiguration instance}.
@@ -43,67 +46,204 @@ public LoggerConfiguration(String name, LogLevel configuredLevel, LogLevel effec
4346
Assert.notNull(name, "Name must not be null");
4447
Assert.notNull(effectiveLevel, "EffectiveLevel must not be null");
4548
this.name = name;
46-
this.configuredLevel = configuredLevel;
47-
this.effectiveLevel = effectiveLevel;
49+
this.levelConfiguration = (configuredLevel != null) ? LevelConfiguration.of(configuredLevel) : null;
50+
this.inheritedLevelConfiguration = LevelConfiguration.of(effectiveLevel);
51+
}
52+
53+
/**
54+
* Create a new {@link LoggerConfiguration instance}.
55+
* @param name the name of the logger
56+
* @param levelConfiguration the level configuration
57+
* @param inheritedLevelConfiguration the inherited level configuration
58+
* @since 2.7.13
59+
*/
60+
public LoggerConfiguration(String name, LevelConfiguration levelConfiguration,
61+
LevelConfiguration inheritedLevelConfiguration) {
62+
Assert.notNull(name, "Name must not be null");
63+
Assert.notNull(inheritedLevelConfiguration, "EffectiveLevelConfiguration must not be null");
64+
this.name = name;
65+
this.levelConfiguration = levelConfiguration;
66+
this.inheritedLevelConfiguration = inheritedLevelConfiguration;
67+
}
68+
69+
/**
70+
* Returns the name of the logger.
71+
* @return the name of the logger
72+
*/
73+
public String getName() {
74+
return this.name;
4875
}
4976

5077
/**
5178
* Returns the configured level of the logger.
5279
* @return the configured level of the logger
80+
* @see #getLevelConfiguration(ConfigurationScope)
5381
*/
5482
public LogLevel getConfiguredLevel() {
55-
return this.configuredLevel;
83+
LevelConfiguration configuration = getLevelConfiguration(ConfigurationScope.DIRECT);
84+
return (configuration != null) ? configuration.getLevel() : null;
5685
}
5786

5887
/**
5988
* Returns the effective level of the logger.
6089
* @return the effective level of the logger
90+
* @see #getLevelConfiguration(ConfigurationScope)
6191
*/
6292
public LogLevel getEffectiveLevel() {
63-
return this.effectiveLevel;
93+
return getLevelConfiguration().getLevel();
6494
}
6595

6696
/**
67-
* Returns the name of the logger.
68-
* @return the name of the logger
97+
* Return the level configuration, considering inherited loggers.
98+
* @return the level configuration
99+
* @since 2.7.13
69100
*/
70-
public String getName() {
71-
return this.name;
101+
public LevelConfiguration getLevelConfiguration() {
102+
return getLevelConfiguration(ConfigurationScope.INHERITED);
103+
}
104+
105+
/**
106+
* Return the level configuration for the given scope.
107+
* @param scope the configuration scope
108+
* @return the level configuration or {@code null} for
109+
* {@link ConfigurationScope#DIRECT direct scope} results without applied
110+
* configuration
111+
* @since 2.7.13
112+
*/
113+
public LevelConfiguration getLevelConfiguration(ConfigurationScope scope) {
114+
return (scope != ConfigurationScope.DIRECT) ? this.inheritedLevelConfiguration : this.levelConfiguration;
72115
}
73116

74117
@Override
75118
public boolean equals(Object obj) {
76119
if (this == obj) {
77120
return true;
78121
}
79-
if (obj == null) {
122+
if (obj == null || getClass() != obj.getClass()) {
80123
return false;
81124
}
82-
if (obj instanceof LoggerConfiguration) {
83-
LoggerConfiguration other = (LoggerConfiguration) obj;
84-
boolean rtn = true;
85-
rtn = rtn && ObjectUtils.nullSafeEquals(this.name, other.name);
86-
rtn = rtn && ObjectUtils.nullSafeEquals(this.configuredLevel, other.configuredLevel);
87-
rtn = rtn && ObjectUtils.nullSafeEquals(this.effectiveLevel, other.effectiveLevel);
88-
return rtn;
89-
}
90-
return super.equals(obj);
125+
LoggerConfiguration other = (LoggerConfiguration) obj;
126+
return ObjectUtils.nullSafeEquals(this.name, other.name)
127+
&& ObjectUtils.nullSafeEquals(this.levelConfiguration, other.levelConfiguration)
128+
&& ObjectUtils.nullSafeEquals(this.inheritedLevelConfiguration, other.inheritedLevelConfiguration);
91129
}
92130

93131
@Override
94132
public int hashCode() {
95-
final int prime = 31;
96-
int result = 1;
97-
result = prime * result + ObjectUtils.nullSafeHashCode(this.name);
98-
result = prime * result + ObjectUtils.nullSafeHashCode(this.configuredLevel);
99-
result = prime * result + ObjectUtils.nullSafeHashCode(this.effectiveLevel);
100-
return result;
133+
return Objects.hash(this.name, this.levelConfiguration, this.inheritedLevelConfiguration);
101134
}
102135

103136
@Override
104137
public String toString() {
105-
return "LoggerConfiguration [name=" + this.name + ", configuredLevel=" + this.configuredLevel
106-
+ ", effectiveLevel=" + this.effectiveLevel + "]";
138+
return "LoggerConfiguration [name=" + this.name + ", levelConfiguration=" + this.levelConfiguration
139+
+ ", inheritedLevelConfiguration=" + this.inheritedLevelConfiguration + "]";
140+
}
141+
142+
/**
143+
* Supported logger configurations scopes.
144+
*
145+
* @since 2.7.13
146+
*/
147+
public enum ConfigurationScope {
148+
149+
/**
150+
* Only return configuration that has been applied directly applied. Often
151+
* referred to as 'configured' or 'assigned' configuration.
152+
*/
153+
DIRECT,
154+
155+
/**
156+
* May return configuration that has been applied to a parent logger. Often
157+
* referred to as 'effective' configuration.
158+
*/
159+
INHERITED
160+
161+
}
162+
163+
/**
164+
* Logger level configuration.
165+
*
166+
* @since 2.7.13
167+
*/
168+
public static final class LevelConfiguration {
169+
170+
private final String name;
171+
172+
private final LogLevel logLevel;
173+
174+
private LevelConfiguration(String name, LogLevel logLevel) {
175+
this.name = name;
176+
this.logLevel = logLevel;
177+
}
178+
179+
/**
180+
* Return the name of the level.
181+
* @return the level name
182+
*/
183+
public String getName() {
184+
return this.name;
185+
}
186+
187+
/**
188+
* Return the actual level value if possible.
189+
* @return the level value
190+
* @throws IllegalStateException if this is a {@link #isCustom() custom} level
191+
*/
192+
public LogLevel getLevel() {
193+
Assert.state(this.logLevel != null, "Unable to provide LogLevel for '" + this.name + "'");
194+
return this.logLevel;
195+
}
196+
197+
/**
198+
* Return if this is a custom level and cannot be represented by {@link LogLevel}.
199+
* @return if this is a custom level
200+
*/
201+
public boolean isCustom() {
202+
return this.logLevel == null;
203+
}
204+
205+
@Override
206+
public boolean equals(Object obj) {
207+
if (this == obj) {
208+
return true;
209+
}
210+
if (obj == null || getClass() != obj.getClass()) {
211+
return false;
212+
}
213+
LevelConfiguration other = (LevelConfiguration) obj;
214+
return this.logLevel == other.logLevel && ObjectUtils.nullSafeEquals(this.name, other.name);
215+
}
216+
217+
@Override
218+
public int hashCode() {
219+
return Objects.hash(this.logLevel, this.name);
220+
}
221+
222+
@Override
223+
public String toString() {
224+
return "LevelConfiguration [name=" + this.name + ", logLevel=" + this.logLevel + "]";
225+
}
226+
227+
/**
228+
* Create a new {@link LevelConfiguration} instance of the given {@link LogLevel}.
229+
* @param logLevel the log level
230+
* @return a new {@link LevelConfiguration} instance
231+
*/
232+
public static LevelConfiguration of(LogLevel logLevel) {
233+
Assert.notNull(logLevel, "LogLevel must not be null");
234+
return new LevelConfiguration(logLevel.name(), logLevel);
235+
}
236+
237+
/**
238+
* Create a new {@link LevelConfiguration} instance for a custom level name.
239+
* @param name the log level name
240+
* @return a new {@link LevelConfiguration} instance
241+
*/
242+
public static LevelConfiguration ofCustom(String name) {
243+
Assert.hasText(name, "Name must not be empty");
244+
return new LevelConfiguration(name, null);
245+
}
246+
107247
}
108248

109249
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggerGroups.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
*
2727
* @author HaiTao Zhang
2828
* @author Phillip Webb
29-
* @since 2.2.0 #see {@link LoggerGroup}
29+
* @since 2.2.0
30+
* @see LoggerGroup
3031
*/
3132
public final class LoggerGroups implements Iterable<LoggerGroup> {
3233

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import org.springframework.boot.logging.LogFile;
5151
import org.springframework.boot.logging.LogLevel;
5252
import org.springframework.boot.logging.LoggerConfiguration;
53+
import org.springframework.boot.logging.LoggerConfiguration.LevelConfiguration;
5354
import org.springframework.boot.logging.LoggingInitializationContext;
5455
import org.springframework.boot.logging.LoggingSystem;
5556
import org.springframework.boot.logging.LoggingSystemFactory;
@@ -362,13 +363,18 @@ private LoggerConfiguration convertLoggerConfig(String name, LoggerConfig logger
362363
if (loggerConfig == null) {
363364
return null;
364365
}
365-
LogLevel level = LEVELS.convertNativeToSystem(loggerConfig.getLevel());
366+
LevelConfiguration effectiveLevelConfiguration = getLevelConfiguration(loggerConfig.getLevel());
366367
if (!StringUtils.hasLength(name) || LogManager.ROOT_LOGGER_NAME.equals(name)) {
367368
name = ROOT_LOGGER_NAME;
368369
}
369-
boolean isLoggerConfigured = loggerConfig.getName().equals(name);
370-
LogLevel configuredLevel = (isLoggerConfigured) ? level : null;
371-
return new LoggerConfiguration(name, configuredLevel, level);
370+
boolean isAssigned = loggerConfig.getName().equals(name);
371+
LevelConfiguration assignedLevelConfiguration = (!isAssigned) ? null : effectiveLevelConfiguration;
372+
return new LoggerConfiguration(name, assignedLevelConfiguration, effectiveLevelConfiguration);
373+
}
374+
375+
private LevelConfiguration getLevelConfiguration(Level level) {
376+
LogLevel logLevel = LEVELS.convertNativeToSystem(level);
377+
return (logLevel != null) ? LevelConfiguration.of(logLevel) : LevelConfiguration.ofCustom(level.name());
372378
}
373379

374380
@Override

0 commit comments

Comments
 (0)