Skip to content

Commit 5fc58b4

Browse files
committed
Merge pull request #7086 from nebhale/logger-actuator
* pr/7086: Polish `/loggers` actuator endpoint Add `/loggers` actuator endpoint
2 parents ae4dd0d + a448183 commit 5fc58b4

23 files changed

+1097
-54
lines changed

spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.springframework.boot.actuate.endpoint.HealthEndpoint;
3838
import org.springframework.boot.actuate.endpoint.InfoEndpoint;
3939
import org.springframework.boot.actuate.endpoint.LiquibaseEndpoint;
40+
import org.springframework.boot.actuate.endpoint.LoggersEndpoint;
4041
import org.springframework.boot.actuate.endpoint.MetricsEndpoint;
4142
import org.springframework.boot.actuate.endpoint.PublicMetrics;
4243
import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint;
@@ -59,6 +60,7 @@
5960
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
6061
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
6162
import org.springframework.boot.context.properties.EnableConfigurationProperties;
63+
import org.springframework.boot.logging.LoggingSystem;
6264
import org.springframework.context.annotation.Bean;
6365
import org.springframework.context.annotation.Configuration;
6466
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
@@ -75,7 +77,7 @@
7577
* @author Stephane Nicoll
7678
* @author Eddú Meléndez
7779
* @author Meang Akira Tanaka
78-
*
80+
* @author Ben Hale
7981
*/
8082
@Configuration
8183
@AutoConfigureAfter({ FlywayAutoConfiguration.class, LiquibaseAutoConfiguration.class })
@@ -135,6 +137,13 @@ public InfoEndpoint infoEndpoint() throws Exception {
135137
? Collections.<InfoContributor>emptyList() : this.infoContributors);
136138
}
137139

140+
@Bean
141+
@ConditionalOnBean(LoggingSystem.class)
142+
@ConditionalOnMissingBean
143+
public LoggersEndpoint loggersEndpoint(LoggingSystem loggingSystem) {
144+
return new LoggersEndpoint(loggingSystem);
145+
}
146+
138147
@Bean
139148
@ConditionalOnMissingBean
140149
public MetricsEndpoint metricsEndpoint() {

spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcManagementContextConfiguration.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.springframework.boot.actuate.endpoint.Endpoint;
2626
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint;
2727
import org.springframework.boot.actuate.endpoint.HealthEndpoint;
28+
import org.springframework.boot.actuate.endpoint.LoggersEndpoint;
2829
import org.springframework.boot.actuate.endpoint.MetricsEndpoint;
2930
import org.springframework.boot.actuate.endpoint.ShutdownEndpoint;
3031
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
@@ -33,6 +34,7 @@
3334
import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint;
3435
import org.springframework.boot.actuate.endpoint.mvc.HeapdumpMvcEndpoint;
3536
import org.springframework.boot.actuate.endpoint.mvc.LogFileMvcEndpoint;
37+
import org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint;
3638
import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint;
3739
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
3840
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
@@ -57,6 +59,7 @@
5759
* Configuration to expose {@link Endpoint} instances over Spring MVC.
5860
*
5961
* @author Dave Syer
62+
* @author Ben Hale
6063
* @since 1.3.0
6164
*/
6265
@ManagementContextConfiguration
@@ -150,6 +153,13 @@ public HealthMvcEndpoint healthMvcEndpoint(HealthEndpoint delegate) {
150153
return healthMvcEndpoint;
151154
}
152155

156+
@Bean
157+
@ConditionalOnBean(LoggersEndpoint.class)
158+
@ConditionalOnEnabledEndpoint("loggers")
159+
public LoggersMvcEndpoint loggersMvcEndpoint(LoggersEndpoint delegate) {
160+
return new LoggersMvcEndpoint(delegate);
161+
}
162+
153163
@Bean
154164
@ConditionalOnBean(MetricsEndpoint.class)
155165
@ConditionalOnEnabledEndpoint("metrics")
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright 2016-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.endpoint;
18+
19+
import java.util.Collection;
20+
import java.util.Collections;
21+
import java.util.LinkedHashMap;
22+
import java.util.Map;
23+
24+
import org.springframework.boot.context.properties.ConfigurationProperties;
25+
import org.springframework.boot.logging.LogLevel;
26+
import org.springframework.boot.logging.LoggerConfiguration;
27+
import org.springframework.boot.logging.LoggingSystem;
28+
import org.springframework.util.Assert;
29+
30+
/**
31+
* {@link Endpoint} to expose a collection of {@link LoggerConfiguration}s.
32+
*
33+
* @author Ben Hale
34+
* @author Phillip Webb
35+
* @since 1.5.0
36+
*/
37+
@ConfigurationProperties(prefix = "endpoints.loggers")
38+
public class LoggersEndpoint
39+
extends AbstractEndpoint<Map<String, LoggersEndpoint.LoggerLevels>> {
40+
41+
private final LoggingSystem loggingSystem;
42+
43+
/**
44+
* Create a new {@link LoggersEndpoint} instance.
45+
* @param loggingSystem the logging system to expose
46+
*/
47+
public LoggersEndpoint(LoggingSystem loggingSystem) {
48+
super("loggers");
49+
Assert.notNull(loggingSystem, "LoggingSystem must not be null");
50+
this.loggingSystem = loggingSystem;
51+
}
52+
53+
@Override
54+
public Map<String, LoggerLevels> invoke() {
55+
Collection<LoggerConfiguration> configurations = this.loggingSystem
56+
.getLoggerConfigurations();
57+
if (configurations == null) {
58+
return Collections.emptyMap();
59+
}
60+
Map<String, LoggerLevels> result = new LinkedHashMap<String, LoggerLevels>(
61+
configurations.size());
62+
for (LoggerConfiguration configuration : configurations) {
63+
result.put(configuration.getName(), new LoggerLevels(configuration));
64+
}
65+
return result;
66+
}
67+
68+
public LoggerLevels invoke(String name) {
69+
Assert.notNull(name, "Name must not be null");
70+
LoggerConfiguration configuration = this.loggingSystem
71+
.getLoggerConfiguration(name);
72+
return (configuration == null ? null : new LoggerLevels(configuration));
73+
}
74+
75+
public void setLogLevel(String name, LogLevel level) {
76+
Assert.notNull(name, "Name must not be empty");
77+
this.loggingSystem.setLogLevel(name, level);
78+
}
79+
80+
/**
81+
* Levels configured for a given logger exposed in a JSON friendly way.
82+
*/
83+
public static class LoggerLevels {
84+
85+
private String configuredLevel;
86+
87+
private String effectiveLevel;
88+
89+
public LoggerLevels(LoggerConfiguration configuration) {
90+
this.configuredLevel = getName(configuration.getConfiguredLevel());
91+
this.effectiveLevel = getName(configuration.getEffectiveLevel());
92+
}
93+
94+
private String getName(LogLevel level) {
95+
return (level == null ? null : level.name());
96+
}
97+
98+
public String getConfiguredLevel() {
99+
return this.configuredLevel;
100+
}
101+
102+
public String getEffectiveLevel() {
103+
return this.effectiveLevel;
104+
}
105+
106+
}
107+
108+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2016-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.endpoint.mvc;
18+
19+
import java.util.Map;
20+
21+
import org.springframework.boot.actuate.endpoint.LoggersEndpoint;
22+
import org.springframework.boot.actuate.endpoint.LoggersEndpoint.LoggerLevels;
23+
import org.springframework.boot.context.properties.ConfigurationProperties;
24+
import org.springframework.boot.logging.LogLevel;
25+
import org.springframework.http.HttpEntity;
26+
import org.springframework.http.MediaType;
27+
import org.springframework.http.ResponseEntity;
28+
import org.springframework.web.bind.annotation.GetMapping;
29+
import org.springframework.web.bind.annotation.PathVariable;
30+
import org.springframework.web.bind.annotation.PostMapping;
31+
import org.springframework.web.bind.annotation.RequestBody;
32+
import org.springframework.web.bind.annotation.ResponseBody;
33+
34+
/**
35+
* Adapter to expose {@link LoggersEndpoint} as an {@link MvcEndpoint}.
36+
*
37+
* @author Ben Hale
38+
* @since 1.5.0
39+
*/
40+
@ConfigurationProperties(prefix = "endpoints.loggers")
41+
public class LoggersMvcEndpoint extends EndpointMvcAdapter {
42+
43+
private final LoggersEndpoint delegate;
44+
45+
public LoggersMvcEndpoint(LoggersEndpoint delegate) {
46+
super(delegate);
47+
this.delegate = delegate;
48+
}
49+
50+
@GetMapping(value = "/{name:.*}", produces = MediaType.APPLICATION_JSON_VALUE)
51+
@ResponseBody
52+
@HypermediaDisabled
53+
public Object get(@PathVariable String name) {
54+
if (!this.delegate.isEnabled()) {
55+
// Shouldn't happen - MVC endpoint shouldn't be registered when delegate's
56+
// disabled
57+
return getDisabledResponse();
58+
}
59+
LoggerLevels levels = this.delegate.invoke(name);
60+
return (levels == null ? ResponseEntity.notFound().build() : levels);
61+
}
62+
63+
@PostMapping(value = "/{name:.*}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
64+
@ResponseBody
65+
@HypermediaDisabled
66+
public Object set(@PathVariable String name,
67+
@RequestBody Map<String, String> configuration) {
68+
if (!this.delegate.isEnabled()) {
69+
// Shouldn't happen - MVC endpoint shouldn't be registered when delegate's
70+
// disabled
71+
return getDisabledResponse();
72+
}
73+
String level = configuration.get("configuredLevel");
74+
this.delegate.setLogLevel(name, level == null ? null : LogLevel.valueOf(level));
75+
return HttpEntity.EMPTY;
76+
}
77+
78+
}

spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
import org.springframework.boot.actuate.endpoint.HealthEndpoint;
3737
import org.springframework.boot.actuate.endpoint.InfoEndpoint;
3838
import org.springframework.boot.actuate.endpoint.LiquibaseEndpoint;
39+
import org.springframework.boot.actuate.endpoint.LoggersEndpoint;
40+
import org.springframework.boot.actuate.endpoint.LoggersEndpoint.LoggerLevels;
3941
import org.springframework.boot.actuate.endpoint.MetricsEndpoint;
4042
import org.springframework.boot.actuate.endpoint.PublicMetrics;
4143
import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint;
@@ -52,6 +54,7 @@
5254
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
5355
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
5456
import org.springframework.boot.bind.PropertySourcesBinder;
57+
import org.springframework.boot.logging.LoggingSystem;
5558
import org.springframework.boot.test.util.EnvironmentTestUtils;
5659
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
5760
import org.springframework.context.annotation.Bean;
@@ -75,6 +78,7 @@
7578
* @author Stephane Nicoll
7679
* @author Eddú Meléndez
7780
* @author Meang Akira Tanaka
81+
* @author Ben Hale
7882
*/
7983
public class EndpointAutoConfigurationTests {
8084

@@ -89,12 +93,13 @@ public void close() {
8993

9094
@Test
9195
public void endpoints() throws Exception {
92-
load(EndpointAutoConfiguration.class);
96+
load(CustomLoggingConfig.class, EndpointAutoConfiguration.class);
9397
assertThat(this.context.getBean(BeansEndpoint.class)).isNotNull();
9498
assertThat(this.context.getBean(DumpEndpoint.class)).isNotNull();
9599
assertThat(this.context.getBean(EnvironmentEndpoint.class)).isNotNull();
96100
assertThat(this.context.getBean(HealthEndpoint.class)).isNotNull();
97101
assertThat(this.context.getBean(InfoEndpoint.class)).isNotNull();
102+
assertThat(this.context.getBean(LoggersEndpoint.class)).isNotNull();
98103
assertThat(this.context.getBean(MetricsEndpoint.class)).isNotNull();
99104
assertThat(this.context.getBean(ShutdownEndpoint.class)).isNotNull();
100105
assertThat(this.context.getBean(TraceEndpoint.class)).isNotNull();
@@ -121,6 +126,14 @@ public void healthEndpointWithDefaultHealthIndicator() {
121126
assertThat(result).isNotNull();
122127
}
123128

129+
@Test
130+
public void loggersEndpointHasLoggers() throws Exception {
131+
load(CustomLoggingConfig.class, EndpointAutoConfiguration.class);
132+
LoggersEndpoint endpoint = this.context.getBean(LoggersEndpoint.class);
133+
Map<String, LoggerLevels> loggers = endpoint.invoke();
134+
assertThat(loggers.size()).isGreaterThan(0);
135+
}
136+
124137
@Test
125138
public void metricEndpointsHasSystemMetricsByDefault() {
126139
load(PublicMetricsAutoConfiguration.class, EndpointAutoConfiguration.class);
@@ -244,6 +257,16 @@ private void load(Class<?>... config) {
244257
this.context.refresh();
245258
}
246259

260+
@Configuration
261+
static class CustomLoggingConfig {
262+
263+
@Bean
264+
LoggingSystem loggingSystem() {
265+
return LoggingSystem.get(getClass().getClassLoader());
266+
}
267+
268+
}
269+
247270
@Configuration
248271
static class CustomPublicMetricsConfig {
249272

0 commit comments

Comments
 (0)