Skip to content

Commit 6e1fb5a

Browse files
committed
Merge pull request #17515 from htztomic
* pr/17515: Polish "Add support for configuring logging groups" Add support for configuring logging groups via endpoint Closes gh-17515
2 parents 8197fea + 61b86ff commit 6e1fb5a

File tree

11 files changed

+598
-73
lines changed

11 files changed

+598
-73
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/asciidoc/endpoints/loggers.adoc

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,21 @@ include::{snippets}loggers/single/response-fields.adoc[]
5757

5858

5959

60+
[[loggers-group]]
61+
== Retrieving a Single Group
62+
63+
To retrieve a single group, make a `GET` request to `/actuator/loggers/{group.name}`,
64+
as shown in the following curl-based example:
65+
66+
include::{snippets}loggers/group/curl-request.adoc[]
67+
68+
The preceding example retrieves information about the logger group named `test`. The
69+
resulting response is similar to the following:
70+
71+
include::{snippets}loggers/group/http-response.adoc[]
72+
73+
74+
6075
[[loggers-setting-level]]
6176
== Setting a Log Level
6277

@@ -81,6 +96,30 @@ include::{snippets}loggers/set/request-fields.adoc[]
8196

8297

8398

99+
[[loggers-setting-level]]
100+
== Setting a Log Level for a Group
101+
102+
To set the level of a logger, make a `POST` request to
103+
`/actuator/loggers/{group.name}` with a JSON body that specifies the configured level
104+
for the logger group, as shown in the following curl-based example:
105+
106+
include::{snippets}loggers/setGroup/curl-request.adoc[]
107+
108+
The preceding example sets the `configuredLevel` of the `test` logger group to `DEBUG`.
109+
110+
111+
112+
[[loggers-setting-level-request-structure]]
113+
=== Request Structure
114+
115+
The request specifies the desired level of the logger group. The following table describes the
116+
structure of the request:
117+
118+
[cols="3,1,3"]
119+
include::{snippets}loggers/set/request-fields.adoc[]
120+
121+
122+
84123
[[loggers-clearing-level]]
85124
== Clearing a Log Level
86125

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.logging;
1818

19+
import org.springframework.beans.factory.ObjectProvider;
1920
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
2021
import org.springframework.boot.actuate.logging.LoggersEndpoint;
2122
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -24,6 +25,7 @@
2425
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2526
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2627
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
28+
import org.springframework.boot.logging.LoggerGroups;
2729
import org.springframework.boot.logging.LoggingSystem;
2830
import org.springframework.context.annotation.Bean;
2931
import org.springframework.context.annotation.ConditionContext;
@@ -45,8 +47,9 @@ public class LoggersEndpointAutoConfiguration {
4547
@ConditionalOnBean(LoggingSystem.class)
4648
@Conditional(OnEnabledLoggingSystemCondition.class)
4749
@ConditionalOnMissingBean
48-
public LoggersEndpoint loggersEndpoint(LoggingSystem loggingSystem) {
49-
return new LoggersEndpoint(loggingSystem);
50+
public LoggersEndpoint loggersEndpoint(LoggingSystem loggingSystem,
51+
ObjectProvider<LoggerGroups> springBootLoggerGroups) {
52+
return new LoggersEndpoint(loggingSystem, springBootLoggerGroups.getIfAvailable(LoggerGroups::new));
5053
}
5154

5255
static class OnEnabledLoggingSystemCondition extends SpringBootCondition {

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LoggersEndpointDocumentationTests.java

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,18 @@
1717
package org.springframework.boot.actuate.autoconfigure.endpoint.web.documentation;
1818

1919
import java.util.Arrays;
20+
import java.util.Collections;
2021
import java.util.EnumSet;
2122
import java.util.List;
23+
import java.util.Map;
2224

2325
import org.junit.jupiter.api.Test;
2426

27+
import org.springframework.beans.factory.annotation.Autowired;
2528
import org.springframework.boot.actuate.logging.LoggersEndpoint;
2629
import org.springframework.boot.logging.LogLevel;
2730
import org.springframework.boot.logging.LoggerConfiguration;
31+
import org.springframework.boot.logging.LoggerGroups;
2832
import org.springframework.boot.logging.LoggingSystem;
2933
import org.springframework.boot.test.mock.mockito.MockBean;
3034
import org.springframework.context.annotation.Bean;
@@ -54,9 +58,21 @@ class LoggersEndpointDocumentationTests extends MockMvcEndpointDocumentationTest
5458
fieldWithPath("configuredLevel").description("Configured level of the logger, if any.").optional(),
5559
fieldWithPath("effectiveLevel").description("Effective level of the logger."));
5660

61+
private static final List<FieldDescriptor> groupLevelFields;
62+
63+
static {
64+
groupLevelFields = Arrays.asList(
65+
fieldWithPath("configuredLevel").description("Configured level of the logger group")
66+
.type(LogLevel.class).optional(),
67+
fieldWithPath("members").description("Loggers that are part of this group").optional());
68+
}
69+
5770
@MockBean
5871
private LoggingSystem loggingSystem;
5972

73+
@Autowired
74+
private LoggerGroups loggerGroups;
75+
6076
@Test
6177
void allLoggers() throws Exception {
6278
given(this.loggingSystem.getSupportedLogLevels()).willReturn(EnumSet.allOf(LogLevel.class));
@@ -66,8 +82,10 @@ void allLoggers() throws Exception {
6682
this.mockMvc.perform(get("/actuator/loggers")).andExpect(status().isOk())
6783
.andDo(MockMvcRestDocumentation.document("loggers/all",
6884
responseFields(fieldWithPath("levels").description("Levels support by the logging system."),
69-
fieldWithPath("loggers").description("Loggers keyed by name."))
70-
.andWithPrefix("loggers.*.", levelFields)));
85+
fieldWithPath("loggers").description("Loggers keyed by name."),
86+
fieldWithPath("groups").description("Logger groups keyed by name"))
87+
.andWithPrefix("loggers.*.", levelFields)
88+
.andWithPrefix("groups.*.", groupLevelFields)));
7189
}
7290

7391
@Test
@@ -78,6 +96,12 @@ void logger() throws Exception {
7896
.andDo(MockMvcRestDocumentation.document("loggers/single", responseFields(levelFields)));
7997
}
8098

99+
@Test
100+
void loggerGroups() throws Exception {
101+
this.mockMvc.perform(get("/actuator/loggers/test")).andExpect(status().isOk())
102+
.andDo(MockMvcRestDocumentation.document("loggers/group", responseFields(groupLevelFields)));
103+
}
104+
81105
@Test
82106
void setLogLevel() throws Exception {
83107
this.mockMvc
@@ -89,6 +113,26 @@ void setLogLevel() throws Exception {
89113
verify(this.loggingSystem).setLogLevel("com.example", LogLevel.DEBUG);
90114
}
91115

116+
@Test
117+
void setLogLevelOfLoggerGroup() throws Exception {
118+
this.mockMvc
119+
.perform(post("/actuator/loggers/test")
120+
.content("{\"configuredLevel\":\"debug\"}").contentType(MediaType.APPLICATION_JSON))
121+
.andExpect(status().isNoContent()).andDo(
122+
MockMvcRestDocumentation.document("loggers/setGroup",
123+
requestFields(fieldWithPath("configuredLevel").description(
124+
"Level for the logger group. May be omitted to clear the level of the loggers.")
125+
.optional())));
126+
verify(this.loggingSystem).setLogLevel("test.member1", LogLevel.DEBUG);
127+
verify(this.loggingSystem).setLogLevel("test.member2", LogLevel.DEBUG);
128+
resetLogger();
129+
}
130+
131+
private void resetLogger() {
132+
this.loggerGroups.get("test").configureLogLevel(null, (a, b) -> {
133+
});
134+
}
135+
92136
@Test
93137
void clearLogLevel() throws Exception {
94138
this.mockMvc
@@ -102,8 +146,13 @@ void clearLogLevel() throws Exception {
102146
static class TestConfiguration {
103147

104148
@Bean
105-
LoggersEndpoint endpoint(LoggingSystem loggingSystem) {
106-
return new LoggersEndpoint(loggingSystem);
149+
LoggersEndpoint endpoint(LoggingSystem loggingSystem, LoggerGroups groups) {
150+
groups.putAll(getLoggerGroups());
151+
return new LoggersEndpoint(loggingSystem, groups);
152+
}
153+
154+
private Map<String, List<String>> getLoggerGroups() {
155+
return Collections.singletonMap("test", Arrays.asList("test.member1", "test.member2"));
107156
}
108157

109158
}

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

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Collection;
2020
import java.util.Collections;
2121
import java.util.LinkedHashMap;
22+
import java.util.List;
2223
import java.util.Map;
2324
import java.util.NavigableSet;
2425
import java.util.Set;
@@ -30,6 +31,8 @@
3031
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
3132
import org.springframework.boot.logging.LogLevel;
3233
import org.springframework.boot.logging.LoggerConfiguration;
34+
import org.springframework.boot.logging.LoggerGroup;
35+
import org.springframework.boot.logging.LoggerGroups;
3336
import org.springframework.boot.logging.LoggingSystem;
3437
import org.springframework.lang.Nullable;
3538
import org.springframework.util.Assert;
@@ -39,20 +42,26 @@
3942
*
4043
* @author Ben Hale
4144
* @author Phillip Webb
45+
* @author HaiTao Zhang
4246
* @since 2.0.0
4347
*/
4448
@Endpoint(id = "loggers")
4549
public class LoggersEndpoint {
4650

4751
private final LoggingSystem loggingSystem;
4852

53+
private final LoggerGroups loggerGroups;
54+
4955
/**
5056
* Create a new {@link LoggersEndpoint} instance.
5157
* @param loggingSystem the logging system to expose
58+
* @param loggerGroups the logger group to expose
5259
*/
53-
public LoggersEndpoint(LoggingSystem loggingSystem) {
60+
public LoggersEndpoint(LoggingSystem loggingSystem, LoggerGroups loggerGroups) {
5461
Assert.notNull(loggingSystem, "LoggingSystem must not be null");
62+
Assert.notNull(loggerGroups, "LoggerGroups must not be null");
5563
this.loggingSystem = loggingSystem;
64+
this.loggerGroups = loggerGroups;
5665
}
5766

5867
@ReadOperation
@@ -64,19 +73,36 @@ public Map<String, Object> loggers() {
6473
Map<String, Object> result = new LinkedHashMap<>();
6574
result.put("levels", getLevels());
6675
result.put("loggers", getLoggers(configurations));
76+
result.put("groups", getGroups());
6777
return result;
6878
}
6979

80+
private Map<String, LoggerLevels> getGroups() {
81+
Map<String, LoggerLevels> groups = new LinkedHashMap<>();
82+
this.loggerGroups.forEach((group) -> groups.put(group.getName(),
83+
new GroupLoggerLevels(group.getConfiguredLevel(), group.getMembers())));
84+
return groups;
85+
}
86+
7087
@ReadOperation
7188
public LoggerLevels loggerLevels(@Selector String name) {
7289
Assert.notNull(name, "Name must not be null");
90+
LoggerGroup group = this.loggerGroups.get(name);
91+
if (group != null) {
92+
return new GroupLoggerLevels(group.getConfiguredLevel(), group.getMembers());
93+
}
7394
LoggerConfiguration configuration = this.loggingSystem.getLoggerConfiguration(name);
74-
return (configuration != null) ? new LoggerLevels(configuration) : null;
95+
return (configuration != null) ? new SingleLoggerLevels(configuration) : null;
7596
}
7697

7798
@WriteOperation
7899
public void configureLogLevel(@Selector String name, @Nullable LogLevel configuredLevel) {
79100
Assert.notNull(name, "Name must not be empty");
101+
LoggerGroup group = this.loggerGroups.get(name);
102+
if (group != null && group.hasMembers()) {
103+
group.configureLogLevel(configuredLevel, this.loggingSystem::setLogLevel);
104+
return;
105+
}
80106
this.loggingSystem.setLogLevel(name, configuredLevel);
81107
}
82108

@@ -88,7 +114,7 @@ private NavigableSet<LogLevel> getLevels() {
88114
private Map<String, LoggerLevels> getLoggers(Collection<LoggerConfiguration> configurations) {
89115
Map<String, LoggerLevels> loggers = new LinkedHashMap<>(configurations.size());
90116
for (LoggerConfiguration configuration : configurations) {
91-
loggers.put(configuration.getName(), new LoggerLevels(configuration));
117+
loggers.put(configuration.getName(), new SingleLoggerLevels(configuration));
92118
}
93119
return loggers;
94120
}
@@ -100,21 +126,44 @@ public static class LoggerLevels {
100126

101127
private String configuredLevel;
102128

103-
private String effectiveLevel;
104-
105-
public LoggerLevels(LoggerConfiguration configuration) {
106-
this.configuredLevel = getName(configuration.getConfiguredLevel());
107-
this.effectiveLevel = getName(configuration.getEffectiveLevel());
129+
public LoggerLevels(LogLevel configuredLevel) {
130+
this.configuredLevel = getName(configuredLevel);
108131
}
109132

110-
private String getName(LogLevel level) {
133+
protected final String getName(LogLevel level) {
111134
return (level != null) ? level.name() : null;
112135
}
113136

114137
public String getConfiguredLevel() {
115138
return this.configuredLevel;
116139
}
117140

141+
}
142+
143+
public static class GroupLoggerLevels extends LoggerLevels {
144+
145+
private List<String> members;
146+
147+
public GroupLoggerLevels(LogLevel configuredLevel, List<String> members) {
148+
super(configuredLevel);
149+
this.members = members;
150+
}
151+
152+
public List<String> getMembers() {
153+
return this.members;
154+
}
155+
156+
}
157+
158+
public static class SingleLoggerLevels extends LoggerLevels {
159+
160+
private String effectiveLevel;
161+
162+
public SingleLoggerLevels(LoggerConfiguration configuration) {
163+
super(configuration.getConfiguredLevel());
164+
this.effectiveLevel = getName(configuration.getEffectiveLevel());
165+
}
166+
118167
public String getEffectiveLevel() {
119168
return this.effectiveLevel;
120169
}

0 commit comments

Comments
 (0)