From 06cb4fcca58d2c05860046b7b878c1bb45b3ffab Mon Sep 17 00:00:00 2001 From: Ben Hale Date: Thu, 22 Sep 2016 16:52:22 -0700 Subject: [PATCH 1/2] Add `/loggers` actuator endpoint Add `LoggersEndpoint` that can enables listing and configuration of log levels. This actuator builds on top of the `LoggingSystem` abstraction and implements support for Logback, Log4J2, and JUL. The LoggingSystem interface is modified to require each implementation to list the configuration of all loggers as well as an individual logger by name. The MVC endpoint exposes these behaviors at `GET /loggers` and `GET /loggers/{name}` (much like the metrics actuator). In addition `POST /loggers/{name}` allows users to modify the level for a given logger. This modification is passed to the logging implementation, which then decides, as an internal implementation detail, what the final outcome of the modification is (e.g. changing all unconfigured children). Users are then expected to request the listing of all loggers to see what has changed internally to the logging system. Closes gh-7086 --- .../EndpointAutoConfiguration.java | 11 +- ...tWebMvcManagementContextConfiguration.java | 10 ++ .../actuate/endpoint/LoggersEndpoint.java | 92 +++++++++++ .../endpoint/mvc/LoggersMvcEndpoint.java | 76 +++++++++ .../EndpointAutoConfigurationTests.java | 24 ++- .../EndpointWebMvcAutoConfigurationTests.java | 39 ++++- .../MvcEndpointPathConfigurationTests.java | 9 ++ .../endpoint/LoggersEndpointTests.java | 90 +++++++++++ ...serMvcEndpointVanillaIntegrationTests.java | 2 +- .../endpoint/mvc/LoggersMvcEndpointTests.java | 147 ++++++++++++++++++ .../boot/logging/LoggerConfiguration.java | 111 +++++++++++++ .../LoggerConfigurationComparator.java | 56 +++++++ .../boot/logging/LoggingSystem.java | 32 ++++ .../boot/logging/java/JavaLoggingSystem.java | 50 ++++++ .../logging/log4j2/Log4J2LoggingSystem.java | 37 +++++ .../logging/logback/LogbackLoggingSystem.java | 38 +++++ .../LoggerConfigurationComparatorTests.java | 82 ++++++++++ .../LoggingApplicationListenerTests.java | 22 +++ .../boot/logging/LoggingSystemTests.java | 24 +++ .../logging/java/JavaLoggingSystemTests.java | 30 ++++ .../log4j2/Log4J2LoggingSystemTests.java | 24 +++ .../logback/LogbackLoggingSystemTests.java | 25 +++ 22 files changed, 1021 insertions(+), 10 deletions(-) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LoggersEndpoint.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpoint.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LoggersEndpointTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpointTests.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfiguration.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfigurationComparator.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/logging/LoggerConfigurationComparatorTests.java diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java index dbda6d4d41..909fd041fa 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java @@ -37,6 +37,7 @@ import org.springframework.boot.actuate.endpoint.FlywayEndpoint; import org.springframework.boot.actuate.endpoint.HealthEndpoint; import org.springframework.boot.actuate.endpoint.InfoEndpoint; import org.springframework.boot.actuate.endpoint.LiquibaseEndpoint; +import org.springframework.boot.actuate.endpoint.LoggersEndpoint; import org.springframework.boot.actuate.endpoint.MetricsEndpoint; import org.springframework.boot.actuate.endpoint.PublicMetrics; import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint; @@ -59,6 +60,7 @@ import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.logging.LoggingSystem; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.AnnotationAwareOrderComparator; @@ -75,7 +77,7 @@ import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping; * @author Stephane Nicoll * @author Eddú Meléndez * @author Meang Akira Tanaka - * + * @author Ben Hale */ @Configuration @AutoConfigureAfter({ FlywayAutoConfiguration.class, LiquibaseAutoConfiguration.class }) @@ -135,6 +137,13 @@ public class EndpointAutoConfiguration { ? Collections.emptyList() : this.infoContributors); } + @Bean + @ConditionalOnBean(LoggingSystem.class) + @ConditionalOnMissingBean + public LoggersEndpoint loggersEndpoint(LoggingSystem loggingSystem) { + return new LoggersEndpoint(loggingSystem); + } + @Bean @ConditionalOnMissingBean public MetricsEndpoint metricsEndpoint() { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcManagementContextConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcManagementContextConfiguration.java index d1101af2fa..2c95a5a229 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcManagementContextConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcManagementContextConfiguration.java @@ -25,6 +25,7 @@ import org.springframework.boot.actuate.condition.ConditionalOnEnabledEndpoint; import org.springframework.boot.actuate.endpoint.Endpoint; import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint; import org.springframework.boot.actuate.endpoint.HealthEndpoint; +import org.springframework.boot.actuate.endpoint.LoggersEndpoint; import org.springframework.boot.actuate.endpoint.MetricsEndpoint; import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; @@ -33,6 +34,7 @@ import org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.HeapdumpMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.LogFileMvcEndpoint; +import org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints; @@ -57,6 +59,7 @@ import org.springframework.web.cors.CorsConfiguration; * Configuration to expose {@link Endpoint} instances over Spring MVC. * * @author Dave Syer + * @author Ben Hale * @since 1.3.0 */ @ManagementContextConfiguration @@ -150,6 +153,13 @@ public class EndpointWebMvcManagementContextConfiguration { return healthMvcEndpoint; } + @Bean + @ConditionalOnBean(LoggersEndpoint.class) + @ConditionalOnEnabledEndpoint("loggers") + public LoggersMvcEndpoint loggersMvcEndpoint(LoggersEndpoint delegate) { + return new LoggersMvcEndpoint(delegate); + } + @Bean @ConditionalOnBean(MetricsEndpoint.class) @ConditionalOnEnabledEndpoint("metrics") diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LoggersEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LoggersEndpoint.java new file mode 100644 index 0000000000..1cdae7dc5e --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LoggersEndpoint.java @@ -0,0 +1,92 @@ +/* + * Copyright 2016-2016 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 + * + * http://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.actuate.endpoint; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.logging.LogLevel; +import org.springframework.boot.logging.LoggerConfiguration; +import org.springframework.boot.logging.LoggingSystem; +import org.springframework.util.Assert; + +/** + * {@link Endpoint} to expose a collection of {@link LoggerConfiguration}s. + * + * @author Ben Hale + * @since 1.5.0 + */ +@ConfigurationProperties(prefix = "endpoints.loggers") +public class LoggersEndpoint extends AbstractEndpoint>> { + + private final LoggingSystem loggingSystem; + + /** + * Create a new {@link LoggersEndpoint} instance. + * @param loggingSystem the logging system to expose + */ + public LoggersEndpoint(LoggingSystem loggingSystem) { + super("loggers"); + Assert.notNull(loggingSystem, "LoggingSystem must not be null"); + this.loggingSystem = loggingSystem; + } + + @Override + public Map> invoke() { + Collection loggerConfigurations = this.loggingSystem + .listLoggerConfigurations(); + + if (loggerConfigurations == null) { + return Collections.emptyMap(); + } + + Map> result = new LinkedHashMap>(loggerConfigurations.size()); + + for (LoggerConfiguration loggerConfiguration : loggerConfigurations) { + result.put(loggerConfiguration.getName(), result(loggerConfiguration)); + } + + return result; + } + + public Map get(String name) { + Assert.notNull(name, "Name must not be null"); + return result(this.loggingSystem.getLoggerConfiguration(name)); + } + + public void set(String name, LogLevel level) { + Assert.notNull(name, "Name must not be empty"); + this.loggingSystem.setLogLevel(name, level); + } + + private static Map result(LoggerConfiguration loggerConfiguration) { + if (loggerConfiguration == null) { + return Collections.emptyMap(); + } + Map result = new LinkedHashMap(3); + LogLevel configuredLevel = loggerConfiguration.getConfiguredLevel(); + result.put("configuredLevel", + configuredLevel != null ? configuredLevel.name() : null); + result.put("effectiveLevel", loggerConfiguration.getEffectiveLevel().name()); + return result; + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpoint.java new file mode 100644 index 0000000000..c0fac73af7 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpoint.java @@ -0,0 +1,76 @@ +/* + * Copyright 2016-2016 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 + * + * http://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.actuate.endpoint.mvc; + +import java.util.Map; + +import org.springframework.boot.actuate.endpoint.LoggersEndpoint; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.logging.LogLevel; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * Adapter to expose {@link LoggersEndpoint} as an {@link MvcEndpoint}. + * + * @author Ben Hale + * @since 1.5.0 + */ +@ConfigurationProperties(prefix = "endpoints.loggers") +public class LoggersMvcEndpoint extends EndpointMvcAdapter { + + private final LoggersEndpoint delegate; + + public LoggersMvcEndpoint(LoggersEndpoint delegate) { + super(delegate); + this.delegate = delegate; + } + + @GetMapping(value = "/{name:.*}", produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + @HypermediaDisabled + public Object get(@PathVariable String name) { + if (!this.delegate.isEnabled()) { + // Shouldn't happen - MVC endpoint shouldn't be registered when delegate's + // disabled + return getDisabledResponse(); + } + return this.delegate.get(name); + } + + @PostMapping(value = "/{name:.*}", consumes = MediaType.APPLICATION_JSON_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + @HypermediaDisabled + public Object set(@PathVariable String name, + @RequestBody Map configuration) { + if (!this.delegate.isEnabled()) { + // Shouldn't happen - MVC endpoint shouldn't be registered when delegate's + // disabled + return getDisabledResponse(); + } + String level = configuration.get("configuredLevel"); + this.delegate.set(name, level == null ? null : LogLevel.valueOf(level)); + return ResponseEntity.EMPTY; + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java index e4d9c0f9fe..31044b0b02 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java @@ -36,6 +36,7 @@ import org.springframework.boot.actuate.endpoint.FlywayEndpoint; import org.springframework.boot.actuate.endpoint.HealthEndpoint; import org.springframework.boot.actuate.endpoint.InfoEndpoint; import org.springframework.boot.actuate.endpoint.LiquibaseEndpoint; +import org.springframework.boot.actuate.endpoint.LoggersEndpoint; import org.springframework.boot.actuate.endpoint.MetricsEndpoint; import org.springframework.boot.actuate.endpoint.PublicMetrics; import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint; @@ -52,6 +53,7 @@ import org.springframework.boot.autoconfigure.info.ProjectInfoProperties; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; import org.springframework.boot.bind.PropertySourcesBinder; +import org.springframework.boot.logging.LoggingSystem; import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; @@ -75,6 +77,7 @@ import static org.mockito.Mockito.mock; * @author Stephane Nicoll * @author Eddú Meléndez * @author Meang Akira Tanaka + * @author Ben Hale */ public class EndpointAutoConfigurationTests { @@ -89,12 +92,13 @@ public class EndpointAutoConfigurationTests { @Test public void endpoints() throws Exception { - load(EndpointAutoConfiguration.class); + load(CustomLoggingConfig.class, EndpointAutoConfiguration.class); assertThat(this.context.getBean(BeansEndpoint.class)).isNotNull(); assertThat(this.context.getBean(DumpEndpoint.class)).isNotNull(); assertThat(this.context.getBean(EnvironmentEndpoint.class)).isNotNull(); assertThat(this.context.getBean(HealthEndpoint.class)).isNotNull(); assertThat(this.context.getBean(InfoEndpoint.class)).isNotNull(); + assertThat(this.context.getBean(LoggersEndpoint.class)).isNotNull(); assertThat(this.context.getBean(MetricsEndpoint.class)).isNotNull(); assertThat(this.context.getBean(ShutdownEndpoint.class)).isNotNull(); assertThat(this.context.getBean(TraceEndpoint.class)).isNotNull(); @@ -121,6 +125,14 @@ public class EndpointAutoConfigurationTests { assertThat(result).isNotNull(); } + @Test + public void loggersEndpointHasLoggers() throws Exception { + load(CustomLoggingConfig.class, EndpointAutoConfiguration.class); + LoggersEndpoint endpoint = this.context.getBean(LoggersEndpoint.class); + Map> loggers = endpoint.invoke(); + assertThat(loggers.size()).isGreaterThan(0); + } + @Test public void metricEndpointsHasSystemMetricsByDefault() { load(PublicMetricsAutoConfiguration.class, EndpointAutoConfiguration.class); @@ -244,6 +256,16 @@ public class EndpointAutoConfigurationTests { this.context.refresh(); } + @Configuration + static class CustomLoggingConfig { + + @Bean + LoggingSystem loggingSystem() { + return LoggingSystem.get(getClass().getClassLoader()); + } + + } + @Configuration static class CustomPublicMetricsConfig { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java index 09a10ca285..e0ffed25b4 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java @@ -49,6 +49,7 @@ import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMappingCusto import org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.HalJsonMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint; +import org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.ShutdownMvcEndpoint; @@ -71,6 +72,7 @@ import org.springframework.boot.context.embedded.ServerPortInfoApplicationContex import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory; import org.springframework.boot.context.event.ApplicationFailedEvent; +import org.springframework.boot.logging.LoggingSystem; import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.boot.testutil.Matched; import org.springframework.context.ApplicationContext; @@ -108,6 +110,7 @@ import static org.mockito.Mockito.mock; * @author Greg Turnquist * @author Andy Wilkinson * @author Eddú Meléndez + * @author Ben Hale */ public class EndpointWebMvcAutoConfigurationTests { @@ -429,12 +432,13 @@ public class EndpointWebMvcAutoConfigurationTests { @Test public void endpointsDefaultConfiguration() throws Exception { - this.applicationContext.register(RootConfig.class, BaseConfiguration.class, - ServerPortConfig.class, EndpointWebMvcAutoConfiguration.class); + this.applicationContext.register(LoggingConfig.class, RootConfig.class, + BaseConfiguration.class, ServerPortConfig.class, + EndpointWebMvcAutoConfiguration.class); this.applicationContext.refresh(); - // /health, /metrics, /env, /actuator, /heapdump (/shutdown is disabled by - // default) - assertThat(this.applicationContext.getBeansOfType(MvcEndpoint.class)).hasSize(5); + // /health, /metrics, /loggers, /env, /actuator, /heapdump (/shutdown is disabled + // by default) + assertThat(this.applicationContext.getBeansOfType(MvcEndpoint.class)).hasSize(6); } @Test @@ -457,6 +461,16 @@ public class EndpointWebMvcAutoConfigurationTests { endpointEnabledOverride("env", EnvironmentMvcEndpoint.class); } + @Test + public void loggersEndpointDisabled() throws Exception { + endpointDisabled("loggers", LoggersMvcEndpoint.class); + } + + @Test + public void loggersEndpointEnabledOverride() throws Exception { + endpointEnabledOverride("loggers", LoggersMvcEndpoint.class); + } + @Test public void metricsEndpointDisabled() throws Exception { endpointDisabled("metrics", MetricsMvcEndpoint.class); @@ -625,8 +639,9 @@ public class EndpointWebMvcAutoConfigurationTests { private void endpointEnabledOverride(String name, Class type) throws Exception { - this.applicationContext.register(RootConfig.class, BaseConfiguration.class, - ServerPortConfig.class, EndpointWebMvcAutoConfiguration.class); + this.applicationContext.register(LoggingConfig.class, RootConfig.class, + BaseConfiguration.class, ServerPortConfig.class, + EndpointWebMvcAutoConfiguration.class); EnvironmentTestUtils.addEnvironment(this.applicationContext, "endpoints.enabled:false", String.format("endpoints_%s_enabled:true", name)); @@ -740,6 +755,16 @@ public class EndpointWebMvcAutoConfigurationTests { } + @Configuration + public static class LoggingConfig { + + @Bean + public LoggingSystem loggingSystem() { + return LoggingSystem.get(getClass().getClassLoader()); + } + + } + @Configuration public static class ServerPortConfig { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MvcEndpointPathConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MvcEndpointPathConfigurationTests.java index 6131ce3d6e..049c882635 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MvcEndpointPathConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MvcEndpointPathConfigurationTests.java @@ -42,6 +42,7 @@ import org.springframework.boot.actuate.endpoint.mvc.HalJsonMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.JolokiaMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.LogFileMvcEndpoint; +import org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.ManagementServletContext; import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; @@ -50,6 +51,7 @@ import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration; +import org.springframework.boot.logging.LoggingSystem; import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -62,6 +64,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for configuring the path of an MVC endpoint. * * @author Andy Wilkinson + * @author Ben Hale */ @RunWith(Parameterized.class) public class MvcEndpointPathConfigurationTests { @@ -95,6 +98,7 @@ public class MvcEndpointPathConfigurationTests { new Object[] { "jolokia", JolokiaMvcEndpoint.class }, new Object[] { "liquibase", LiquibaseEndpoint.class }, new Object[] { "logfile", LogFileMvcEndpoint.class }, + new Object[] { "loggers", LoggersMvcEndpoint.class }, new Object[] { "mappings", RequestMappingEndpoint.class }, new Object[] { "metrics", MetricsMvcEndpoint.class }, new Object[] { "shutdown", ShutdownEndpoint.class }, @@ -151,6 +155,11 @@ public class MvcEndpointPathConfigurationTests { return ConditionEvaluationReport.get(beanFactory); } + @Bean + LoggingSystem loggingSystem() { + return LoggingSystem.get(getClass().getClassLoader()); + } + @Bean public FlywayEndpoint flyway() { return new FlywayEndpoint(new Flyway()); diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LoggersEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LoggersEndpointTests.java new file mode 100644 index 0000000000..53572d4a07 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LoggersEndpointTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2016-2016 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 + * + * http://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.actuate.endpoint; + +import java.util.Collections; +import java.util.Map; + +import org.junit.Test; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.logging.LogLevel; +import org.springframework.boot.logging.LoggerConfiguration; +import org.springframework.boot.logging.LoggingSystem; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link LoggersEndpoint}. + * + * @author Ben Hale + */ +public class LoggersEndpointTests extends AbstractEndpointTests { + + public LoggersEndpointTests() { + super(Config.class, LoggersEndpoint.class, "loggers", true, "endpoints.loggers"); + } + + @Test + public void invoke() throws Exception { + given(getLoggingSystem().listLoggerConfigurations()).willReturn(Collections + .singleton(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG))); + Map loggingConfiguration = getEndpointBean().invoke() + .get("ROOT"); + assertThat(loggingConfiguration.get("configuredLevel")).isNull(); + assertThat(loggingConfiguration.get("effectiveLevel")).isEqualTo("DEBUG"); + } + + public void get() throws Exception { + given(getLoggingSystem().getLoggerConfiguration("ROOT")) + .willReturn(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG)); + Map loggingConfiguration = getEndpointBean().get("ROOT"); + assertThat(loggingConfiguration.get("configuredLevel")).isNull(); + assertThat(loggingConfiguration.get("effectiveLevel")).isEqualTo("DEBUG"); + } + + public void set() throws Exception { + getEndpointBean().set("ROOT", LogLevel.DEBUG); + verify(getLoggingSystem()).setLogLevel("ROOT", LogLevel.DEBUG); + } + + private LoggingSystem getLoggingSystem() { + return this.context.getBean(LoggingSystem.class); + } + + @Configuration + @EnableConfigurationProperties + public static class Config { + + @Bean + public LoggingSystem loggingSystem() { + return mock(LoggingSystem.class); + } + + @Bean + public LoggersEndpoint endpoint(LoggingSystem loggingSystem) { + return new LoggersEndpoint(loggingSystem); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HalBrowserMvcEndpointVanillaIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HalBrowserMvcEndpointVanillaIntegrationTests.java index 4afe550a14..049c8dfbc3 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HalBrowserMvcEndpointVanillaIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HalBrowserMvcEndpointVanillaIntegrationTests.java @@ -119,7 +119,7 @@ public class HalBrowserMvcEndpointVanillaIntegrationTests { @Test public void endpointsEachHaveSelf() throws Exception { Set collections = new HashSet( - Arrays.asList("/trace", "/beans", "/dump", "/heapdump")); + Arrays.asList("/trace", "/beans", "/dump", "/heapdump", "/loggers")); for (MvcEndpoint endpoint : this.mvcEndpoints.getEndpoints()) { String path = endpoint.getPath(); if (collections.contains(path)) { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpointTests.java new file mode 100644 index 0000000000..8ada13d112 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpointTests.java @@ -0,0 +1,147 @@ +/* + * Copyright 2016-2016 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 + * + * http://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.actuate.endpoint.mvc; + +import java.util.Collections; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration; +import org.springframework.boot.actuate.endpoint.LoggersEndpoint; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; +import org.springframework.boot.logging.LogLevel; +import org.springframework.boot.logging.LoggerConfiguration; +import org.springframework.boot.logging.LoggingSystem; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Tests for {@link LoggersMvcEndpoint}. + * + * @author Ben Hale + */ +@RunWith(SpringRunner.class) +@DirtiesContext +@SpringBootTest +public class LoggersMvcEndpointTests { + + @Autowired + private WebApplicationContext context; + + @Autowired + private LoggingSystem loggingSystem; + + private MockMvc mvc; + + @Before + public void setUp() { + this.context.getBean(LoggersEndpoint.class).setEnabled(true); + this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + } + + @Test + public void list() throws Exception { + given(this.loggingSystem.listLoggerConfigurations()).willReturn(Collections + .singleton(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG))); + + this.mvc.perform(get("/loggers")).andExpect(status().isOk()) + .andExpect(content().string(equalTo("{\"ROOT\":{\"configuredLevel\":" + + "null,\"effectiveLevel\":\"DEBUG\"}}"))); + } + + @Test + public void listDisabled() throws Exception { + this.context.getBean(LoggersEndpoint.class).setEnabled(false); + this.mvc.perform(get("/loggers")).andExpect(status().isNotFound()); + } + + @Test + public void getLogger() throws Exception { + given(this.loggingSystem.getLoggerConfiguration("ROOT")) + .willReturn(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG)); + + this.mvc.perform(get("/loggers/ROOT")).andExpect(status().isOk()) + .andExpect(content().string(equalTo("{\"configuredLevel\":null," + + "\"effectiveLevel\":\"DEBUG\"}"))); + } + + @Test + public void getLoggerDisabled() throws Exception { + this.context.getBean(LoggersEndpoint.class).setEnabled(false); + this.mvc.perform(get("/loggers/ROOT")).andExpect(status().isNotFound()); + } + + @Test + public void setLogger() throws Exception { + this.mvc.perform(post("/loggers/ROOT").contentType(MediaType.APPLICATION_JSON) + .content("{\"configuredLevel\":\"DEBUG\"}")).andExpect(status().isOk()); + verify(this.loggingSystem).setLogLevel("ROOT", LogLevel.DEBUG); + } + + @Test + public void setLoggerDisabled() throws Exception { + this.context.getBean(LoggersEndpoint.class).setEnabled(false); + this.mvc.perform(post("/loggers/ROOT").contentType(MediaType.APPLICATION_JSON) + .content("{\"configuredLevel\":\"DEBUG\"}")) + .andExpect(status().isNotFound()); + verifyZeroInteractions(this.loggingSystem); + } + + @Import({ JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class, + ManagementServerPropertiesAutoConfiguration.class }) + @Configuration + public static class TestConfiguration { + + @Bean + public LoggingSystem loggingSystem() { + return mock(LoggingSystem.class); + } + + @Bean + public LoggersEndpoint endpoint(LoggingSystem loggingSystem) { + return new LoggersEndpoint(loggingSystem); + } + + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfiguration.java b/spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfiguration.java new file mode 100644 index 0000000000..7b695c2520 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfiguration.java @@ -0,0 +1,111 @@ +/* + * Copyright 2016-2016 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 + * + * http://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.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Immutable class that represents the configuration of a {@link LoggingSystem}'s logger. + * + * @author Ben Hale + * @since 1.5.0 + */ +public class LoggerConfiguration { + + private final LogLevel configuredLevel; + + private final LogLevel effectiveLevel; + + private final String name; + + /** + * Create a new {@link LoggerConfiguration instance}. + * @param name the name of the logger + * @param configuredLevel the configured level of the logger + * @param effectiveLevel the effective level of the logger + */ + public LoggerConfiguration(String name, LogLevel configuredLevel, + LogLevel effectiveLevel) { + 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; + } + + /** + * Returns the configured level of the logger. + * @return the configured level of the logger + */ + public LogLevel getConfiguredLevel() { + return this.configuredLevel; + } + + /** + * Returns the effective level of the logger. + * @return the effective level of the logger + */ + public LogLevel getEffectiveLevel() { + return this.effectiveLevel; + } + + /** + * Returns the name of the logger. + * @return the name of the logger + */ + public String getName() { + return this.name; + } + + @Override + public String toString() { + return "LoggerConfiguration [name=" + this.name + ", configuredLevel=" + + this.configuredLevel + ", effectiveLevel=" + this.effectiveLevel + "]"; + } + + @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; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (obj instanceof LoggerConfiguration) { + LoggerConfiguration other = (LoggerConfiguration) obj; + boolean rtn = true; + rtn &= ObjectUtils.nullSafeEquals(this.name, other.name); + rtn &= ObjectUtils.nullSafeEquals(this.configuredLevel, + other.configuredLevel); + rtn &= ObjectUtils.nullSafeEquals(this.effectiveLevel, other.effectiveLevel); + return rtn; + } + return super.equals(obj); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfigurationComparator.java b/spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfigurationComparator.java new file mode 100644 index 0000000000..d40b7478ac --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfigurationComparator.java @@ -0,0 +1,56 @@ +/* + * Copyright 2016-2016 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 + * + * http://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 java.util.Comparator; + +import org.springframework.util.Assert; + +/** + * An implementation of {@link Comparator} for comparing {@link LoggerConfiguration}s. + * Sorts the "root" logger as the first logger and then lexically by name after that. + * + * @author Ben Hale + * @since 1.5.0 + */ +public class LoggerConfigurationComparator implements Comparator { + + private final String rootLoggerName; + + /** + * Create a new {@link LoggerConfigurationComparator} instance. + * @param rootLoggerName the name of the "root" logger + */ + public LoggerConfigurationComparator(String rootLoggerName) { + Assert.notNull(rootLoggerName, "RootLoggerName must not be null"); + this.rootLoggerName = rootLoggerName; + } + + @Override + public int compare(LoggerConfiguration o1, LoggerConfiguration o2) { + if (this.rootLoggerName.equals(o1.getName())) { + return -1; + } + + if (this.rootLoggerName.equals(o2.getName())) { + return 1; + } + + return o1.getName().compareTo(o2.getName()); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystem.java b/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystem.java index 4664f8fde6..f23e7047f0 100644 --- a/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystem.java +++ b/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystem.java @@ -16,6 +16,7 @@ package org.springframework.boot.logging; +import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; @@ -29,6 +30,7 @@ import org.springframework.util.StringUtils; * @author Phillip Webb * @author Dave Syer * @author Andy Wilkinson + * @author Ben Hale */ public abstract class LoggingSystem { @@ -92,6 +94,26 @@ public abstract class LoggingSystem { return null; } + /** + * Returns the current configuration for a {@link LoggingSystem}'s logger. + * @param loggerName the name of the logger + * @return the current configuration + */ + public LoggerConfiguration getLoggerConfiguration(String loggerName) { + throw new UnsupportedOperationException( + "Getting a logger configuration is not supported"); + } + + /** + * Returns a collection of the current configuration for all a {@link LoggingSystem}'s + * loggers. + * @return the current configurations + */ + public Collection listLoggerConfigurations() { + throw new UnsupportedOperationException( + "Listing logger configurations is not supported"); + } + /** * Sets the logging level for a given logger. * @param loggerName the name of the logger to set @@ -141,6 +163,16 @@ public abstract class LoggingSystem { } + @Override + public LoggerConfiguration getLoggerConfiguration(String loggerName) { + return null; + } + + @Override + public Collection listLoggerConfigurations() { + return Collections.emptyList(); + } + @Override public void setLogLevel(String loggerName, LogLevel level) { diff --git a/spring-boot/src/main/java/org/springframework/boot/logging/java/JavaLoggingSystem.java b/spring-boot/src/main/java/org/springframework/boot/logging/java/JavaLoggingSystem.java index 5730ad4d98..74a09cb7ea 100644 --- a/spring-boot/src/main/java/org/springframework/boot/logging/java/JavaLoggingSystem.java +++ b/spring-boot/src/main/java/org/springframework/boot/logging/java/JavaLoggingSystem.java @@ -18,8 +18,12 @@ package org.springframework.boot.logging.java; import java.io.ByteArrayInputStream; import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.Enumeration; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.LogManager; @@ -28,6 +32,8 @@ import java.util.logging.Logger; import org.springframework.boot.logging.AbstractLoggingSystem; import org.springframework.boot.logging.LogFile; import org.springframework.boot.logging.LogLevel; +import org.springframework.boot.logging.LoggerConfiguration; +import org.springframework.boot.logging.LoggerConfigurationComparator; import org.springframework.boot.logging.LoggingInitializationContext; import org.springframework.boot.logging.LoggingSystem; import org.springframework.util.Assert; @@ -41,11 +47,17 @@ import org.springframework.util.StringUtils; * @author Phillip Webb * @author Dave Syer * @author Andy Wilkinson + * @author Ben Hale */ public class JavaLoggingSystem extends AbstractLoggingSystem { + private static final LoggerConfigurationComparator COMPARATOR = + new LoggerConfigurationComparator(""); + private static final Map LEVELS; + private static final Map LOG_LEVELS; + static { Map levels = new HashMap(); levels.put(LogLevel.TRACE, Level.FINEST); @@ -56,6 +68,12 @@ public class JavaLoggingSystem extends AbstractLoggingSystem { levels.put(LogLevel.FATAL, Level.SEVERE); levels.put(LogLevel.OFF, Level.OFF); LEVELS = Collections.unmodifiableMap(levels); + + Map logLevels = new HashMap(); + for (Map.Entry entry : LEVELS.entrySet()) { + logLevels.put(entry.getValue(), entry.getKey()); + } + LOG_LEVELS = Collections.unmodifiableMap(logLevels); } public JavaLoggingSystem(ClassLoader classLoader) { @@ -108,6 +126,24 @@ public class JavaLoggingSystem extends AbstractLoggingSystem { } } + @Override + public LoggerConfiguration getLoggerConfiguration(String loggerName) { + return toLoggerConfiguration(Logger.getLogger(loggerName)); + } + + @Override + public Collection listLoggerConfigurations() { + List result = new ArrayList(); + for (Enumeration loggerNames = + LogManager.getLogManager().getLoggerNames(); + loggerNames.hasMoreElements(); ) { + result.add(toLoggerConfiguration(Logger.getLogger( + loggerNames.nextElement()))); + } + Collections.sort(result, COMPARATOR); + return result; + } + @Override public void setLogLevel(String loggerName, LogLevel level) { Assert.notNull(level, "Level must not be null"); @@ -121,6 +157,20 @@ public class JavaLoggingSystem extends AbstractLoggingSystem { return new ShutdownHandler(); } + private static LoggerConfiguration toLoggerConfiguration(Logger logger) { + return new LoggerConfiguration(logger.getName(), + LOG_LEVELS.get(logger.getLevel()), + LOG_LEVELS.get(getEffectiveLevel(logger))); + } + + private static Level getEffectiveLevel(Logger root) { + Logger logger = root; + while (logger.getLevel() == null) { + logger = logger.getParent(); + } + return logger.getLevel(); + } + private final class ShutdownHandler implements Runnable { @Override diff --git a/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java b/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java index 955fa748a0..9e232d6569 100644 --- a/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java +++ b/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java @@ -20,6 +20,7 @@ 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.HashMap; import java.util.List; @@ -40,6 +41,8 @@ import org.apache.logging.log4j.message.Message; import org.springframework.boot.logging.LogFile; import org.springframework.boot.logging.LogLevel; +import org.springframework.boot.logging.LoggerConfiguration; +import org.springframework.boot.logging.LoggerConfigurationComparator; import org.springframework.boot.logging.LoggingInitializationContext; import org.springframework.boot.logging.LoggingSystem; import org.springframework.boot.logging.Slf4JLoggingSystem; @@ -54,14 +57,20 @@ import org.springframework.util.StringUtils; * @author Daniel Fullarton * @author Andy Wilkinson * @author Alexander Heusingfeld + * @author Ben Hale * @since 1.2.0 */ public class Log4J2LoggingSystem extends Slf4JLoggingSystem { + private static final LoggerConfigurationComparator COMPARATOR = + new LoggerConfigurationComparator(LogManager.ROOT_LOGGER_NAME); + private static final String FILE_PROTOCOL = "file"; private static final Map LEVELS; + private static final Map LOG_LEVELS; + static { Map levels = new HashMap(); levels.put(LogLevel.TRACE, Level.TRACE); @@ -72,6 +81,12 @@ public class Log4J2LoggingSystem extends Slf4JLoggingSystem { levels.put(LogLevel.FATAL, Level.FATAL); levels.put(LogLevel.OFF, Level.OFF); LEVELS = Collections.unmodifiableMap(levels); + + Map logLevels = new HashMap(); + for (Map.Entry entry : LEVELS.entrySet()) { + logLevels.put(entry.getValue(), entry.getKey()); + } + LOG_LEVELS = Collections.unmodifiableMap(logLevels); } private static final Filter FILTER = new AbstractFilter() { @@ -194,6 +209,22 @@ public class Log4J2LoggingSystem extends Slf4JLoggingSystem { getLoggerContext().reconfigure(); } + @Override + public LoggerConfiguration getLoggerConfiguration(String loggerName) { + return toLoggerConfiguration(getLoggerConfig(loggerName)); + } + + @Override + public Collection listLoggerConfigurations() { + List result = new ArrayList(); + for (LoggerConfig loggerConfig : + getLoggerContext().getConfiguration().getLoggers().values()) { + result.add(toLoggerConfiguration(loggerConfig)); + } + Collections.sort(result, COMPARATOR); + return result; + } + @Override public void setLogLevel(String loggerName, LogLevel logLevel) { Level level = LEVELS.get(logLevel); @@ -241,6 +272,12 @@ public class Log4J2LoggingSystem extends Slf4JLoggingSystem { loggerContext.setExternalContext(null); } + private static LoggerConfiguration toLoggerConfiguration(LoggerConfig loggerConfig) { + return new LoggerConfiguration(loggerConfig.getName(), + LOG_LEVELS.get(loggerConfig.getLevel()), + LOG_LEVELS.get(loggerConfig.getLevel())); + } + private final class ShutdownHandler implements Runnable { @Override diff --git a/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java b/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java index b681bc9729..f32cf80b9b 100644 --- a/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java +++ b/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java @@ -19,6 +19,8 @@ package org.springframework.boot.logging.logback; import java.net.URL; import java.security.CodeSource; import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -40,6 +42,8 @@ import org.slf4j.impl.StaticLoggerBinder; import org.springframework.boot.logging.LogFile; import org.springframework.boot.logging.LogLevel; +import org.springframework.boot.logging.LoggerConfiguration; +import org.springframework.boot.logging.LoggerConfigurationComparator; import org.springframework.boot.logging.LoggingInitializationContext; import org.springframework.boot.logging.LoggingSystem; import org.springframework.boot.logging.Slf4JLoggingSystem; @@ -53,13 +57,19 @@ import org.springframework.util.StringUtils; * @author Phillip Webb * @author Dave Syer * @author Andy Wilkinson + * @author Ben Hale */ public class LogbackLoggingSystem extends Slf4JLoggingSystem { + private static final LoggerConfigurationComparator COMPARATOR = + new LoggerConfigurationComparator(Logger.ROOT_LOGGER_NAME); + private static final String CONFIGURATION_FILE_PROPERTY = "logback.configurationFile"; private static final Map LEVELS; + private static final Map LOG_LEVELS; + static { Map levels = new HashMap(); levels.put(LogLevel.TRACE, Level.TRACE); @@ -70,6 +80,12 @@ public class LogbackLoggingSystem extends Slf4JLoggingSystem { levels.put(LogLevel.FATAL, Level.ERROR); levels.put(LogLevel.OFF, Level.OFF); LEVELS = Collections.unmodifiableMap(levels); + + Map logLevels = new HashMap(); + for (Map.Entry entry : LEVELS.entrySet()) { + logLevels.put(entry.getValue(), entry.getKey()); + } + LOG_LEVELS = Collections.unmodifiableMap(logLevels); } private static final TurboFilter FILTER = new TurboFilter() { @@ -209,6 +225,21 @@ public class LogbackLoggingSystem extends Slf4JLoggingSystem { System.setProperty("org.jboss.logging.provider", "slf4j"); } + @Override + public LoggerConfiguration getLoggerConfiguration(String loggerName) { + return toLoggerConfiguration(getLogger(loggerName)); + } + + @Override + public Collection listLoggerConfigurations() { + List result = new ArrayList(); + for (ch.qos.logback.classic.Logger logger : getLoggerContext().getLoggerList()) { + result.add(toLoggerConfiguration(logger)); + } + Collections.sort(result, COMPARATOR); + return result; + } + @Override public void setLogLevel(String loggerName, LogLevel level) { getLogger(loggerName).setLevel(LEVELS.get(level)); @@ -265,6 +296,13 @@ public class LogbackLoggingSystem extends Slf4JLoggingSystem { loggerContext.removeObject(LoggingSystem.class.getName()); } + private static LoggerConfiguration toLoggerConfiguration( + ch.qos.logback.classic.Logger logger) { + return new LoggerConfiguration(logger.getName(), + LOG_LEVELS.get(logger.getLevel()), + LOG_LEVELS.get(logger.getEffectiveLevel())); + } + private final class ShutdownHandler implements Runnable { @Override diff --git a/spring-boot/src/test/java/org/springframework/boot/logging/LoggerConfigurationComparatorTests.java b/spring-boot/src/test/java/org/springframework/boot/logging/LoggerConfigurationComparatorTests.java new file mode 100644 index 0000000000..1a3cb4f765 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/logging/LoggerConfigurationComparatorTests.java @@ -0,0 +1,82 @@ +/* + * Copyright 2016-2016 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 + * + * http://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.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link LoggerConfigurationComparator}. + * + * @author Ben Hale + */ +public class LoggerConfigurationComparatorTests { + + private final LoggerConfigurationComparator comparator = + new LoggerConfigurationComparator("ROOT"); + + @Test + public void rootLoggerFirst() { + LoggerConfiguration first = new LoggerConfiguration("ROOT", null, LogLevel.OFF); + LoggerConfiguration second = new LoggerConfiguration("alpha", null, LogLevel.OFF); + assertThat(this.comparator.compare(first, second)).isLessThan(0); + } + + @Test + public void rootLoggerSecond() { + LoggerConfiguration first = new LoggerConfiguration("alpha", null, LogLevel.OFF); + LoggerConfiguration second = new LoggerConfiguration("ROOT", null, LogLevel.OFF); + assertThat(this.comparator.compare(first, second)).isGreaterThan(0); + } + + @Test + public void rootLoggerFirstEmpty() { + LoggerConfiguration first = new LoggerConfiguration("ROOT", null, LogLevel.OFF); + LoggerConfiguration second = new LoggerConfiguration("", null, LogLevel.OFF); + assertThat(this.comparator.compare(first, second)).isLessThan(0); + } + + @Test + public void rootLoggerSecondEmpty() { + LoggerConfiguration first = new LoggerConfiguration("", null, LogLevel.OFF); + LoggerConfiguration second = new LoggerConfiguration("ROOT", null, LogLevel.OFF); + assertThat(this.comparator.compare(first, second)).isGreaterThan(0); + } + + @Test + public void lexicalFirst() { + LoggerConfiguration first = new LoggerConfiguration("alpha", null, LogLevel.OFF); + LoggerConfiguration second = new LoggerConfiguration("bravo", null, LogLevel.OFF); + assertThat(this.comparator.compare(first, second)).isLessThan(0); + } + + @Test + public void lexicalSecond() { + LoggerConfiguration first = new LoggerConfiguration("bravo", null, LogLevel.OFF); + LoggerConfiguration second = new LoggerConfiguration("alpha", null, LogLevel.OFF); + assertThat(this.comparator.compare(first, second)).isGreaterThan(0); + } + + @Test + public void lexicalEqual() { + LoggerConfiguration first = new LoggerConfiguration("alpha", null, LogLevel.OFF); + LoggerConfiguration second = new LoggerConfiguration("alpha", null, LogLevel.OFF); + assertThat(this.comparator.compare(first, second)).isEqualTo(0); + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/logging/LoggingApplicationListenerTests.java b/spring-boot/src/test/java/org/springframework/boot/logging/LoggingApplicationListenerTests.java index 6378cbee5d..b6c6ff2465 100644 --- a/spring-boot/src/test/java/org/springframework/boot/logging/LoggingApplicationListenerTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/logging/LoggingApplicationListenerTests.java @@ -18,6 +18,7 @@ package org.springframework.boot.logging; import java.io.File; import java.io.IOException; +import java.util.Collection; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.logging.Handler; @@ -57,6 +58,7 @@ import static org.hamcrest.Matchers.not; * @author Phillip Webb * @author Andy Wilkinson * @author Stephane Nicoll + * @author Ben Hale */ public class LoggingApplicationListenerTests { @@ -516,6 +518,16 @@ public class LoggingApplicationListenerTests { LogFile logFile) { } + @Override + public LoggerConfiguration getLoggerConfiguration(String loggerName) { + return null; + } + + @Override + public Collection listLoggerConfigurations() { + return null; + } + @Override public void setLogLevel(String loggerName, LogLevel level) { @@ -560,6 +572,16 @@ public class LoggingApplicationListenerTests { } + @Override + public LoggerConfiguration getLoggerConfiguration(String loggerName) { + return null; + } + + @Override + public Collection listLoggerConfigurations() { + return null; + } + @Override public void setLogLevel(String loggerName, LogLevel level) { diff --git a/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemTests.java b/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemTests.java index 56e263c9f5..735e5814a4 100644 --- a/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemTests.java @@ -42,4 +42,28 @@ public class LoggingSystemTests { assertThat(loggingSystem).isInstanceOf(NoOpLoggingSystem.class); } + @Test(expected = UnsupportedOperationException.class) + public void getLoggerConfigurationIsUnsupported() { + new StubLoggingSystem().getLoggerConfiguration("test-logger-name"); + } + + @Test(expected = UnsupportedOperationException.class) + public void listLoggerConfigurationsIsUnsupported() { + new StubLoggingSystem().listLoggerConfigurations(); + } + + private static final class StubLoggingSystem extends LoggingSystem { + + @Override + public void beforeInitialize() { + // Stub implementation + } + + @Override + public void setLogLevel(String loggerName, LogLevel level) { + // Stub implementation + } + + } + } diff --git a/spring-boot/src/test/java/org/springframework/boot/logging/java/JavaLoggingSystemTests.java b/spring-boot/src/test/java/org/springframework/boot/logging/java/JavaLoggingSystemTests.java index fb4799a7cb..a624c50b05 100644 --- a/spring-boot/src/test/java/org/springframework/boot/logging/java/JavaLoggingSystemTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/logging/java/JavaLoggingSystemTests.java @@ -19,7 +19,9 @@ package org.springframework.boot.logging.java; import java.io.File; import java.io.FileFilter; import java.io.IOException; +import java.util.Collection; import java.util.Locale; +import java.util.logging.Level; import org.apache.commons.logging.impl.Jdk14Logger; import org.junit.After; @@ -29,6 +31,7 @@ import org.junit.Test; import org.springframework.boot.logging.AbstractLoggingSystemTests; import org.springframework.boot.logging.LogLevel; +import org.springframework.boot.logging.LoggerConfiguration; import org.springframework.boot.testutil.InternalOutputCapture; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -40,6 +43,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Dave Syer * @author Phillip Webb + * @author Ben Hale */ public class JavaLoggingSystemTests extends AbstractLoggingSystemTests { @@ -74,6 +78,11 @@ public class JavaLoggingSystemTests extends AbstractLoggingSystemTests { Locale.setDefault(this.defaultLocale); } + @After + public void resetLogger() { + this.logger.getLogger().setLevel(Level.OFF); + } + @Test public void noFile() throws Exception { this.loggingSystem.beforeInitialize(); @@ -139,6 +148,27 @@ public class JavaLoggingSystemTests extends AbstractLoggingSystemTests { null); } + @Test + public void getLoggingConfiguration() throws Exception { + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(null, null, null); + this.loggingSystem.setLogLevel(getClass().getName(), LogLevel.DEBUG); + assertThat(this.loggingSystem.getLoggerConfiguration(getClass().getName())) + .isEqualTo(new LoggerConfiguration(getClass().getName(), + LogLevel.DEBUG, LogLevel.DEBUG)); + } + + @Test + public void listLoggingConfigurations() throws Exception { + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(null, null, null); + this.loggingSystem.setLogLevel(getClass().getName(), LogLevel.DEBUG); + Collection loggerConfigurations = this.loggingSystem + .listLoggerConfigurations(); + assertThat(loggerConfigurations.size()).isGreaterThan(0); + assertThat(loggerConfigurations.iterator().next().getName()).isEmpty(); + } + @Test public void setLevel() throws Exception { this.loggingSystem.beforeInitialize(); diff --git a/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java b/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java index a9eff17d1b..25de8b8f31 100644 --- a/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java @@ -21,6 +21,7 @@ import java.beans.PropertyChangeListener; import java.io.File; import java.io.FileReader; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; @@ -38,6 +39,7 @@ import org.junit.Test; import org.springframework.boot.logging.AbstractLoggingSystemTests; import org.springframework.boot.logging.LogLevel; +import org.springframework.boot.logging.LoggerConfiguration; import org.springframework.boot.testutil.InternalOutputCapture; import org.springframework.boot.testutil.Matched; import org.springframework.util.FileCopyUtils; @@ -57,6 +59,7 @@ import static org.mockito.Mockito.verify; * @author Daniel Fullarton * @author Phillip Webb * @author Andy Wilkinson + * @author Ben Hale */ public class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests { @@ -120,6 +123,27 @@ public class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests { this.loggingSystem.initialize(null, "classpath:log4j2-nonexistent.xml", null); } + @Test + public void getLoggingConfiguration() throws Exception { + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(null, null, null); + this.loggingSystem.setLogLevel(getClass().getName(), LogLevel.DEBUG); + assertThat(this.loggingSystem.getLoggerConfiguration(getClass().getName())) + .isEqualTo(new LoggerConfiguration(getClass().getName(), + LogLevel.DEBUG, LogLevel.DEBUG)); + } + + @Test + public void listLoggingConfigurations() throws Exception { + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(null, null, null); + this.loggingSystem.setLogLevel(getClass().getName(), LogLevel.DEBUG); + Collection loggerConfigurations = this.loggingSystem + .listLoggerConfigurations(); + assertThat(loggerConfigurations.size()).isGreaterThan(0); + assertThat(loggerConfigurations.iterator().next().getName()).isEmpty(); + } + @Test public void setLevel() throws Exception { this.loggingSystem.beforeInitialize(); diff --git a/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java b/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java index 39b4d9810a..83a8fd60cb 100644 --- a/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java @@ -18,6 +18,7 @@ package org.springframework.boot.logging.logback; import java.io.File; import java.io.FileReader; +import java.util.Collection; import java.util.logging.Handler; import java.util.logging.LogManager; @@ -39,6 +40,7 @@ import org.slf4j.impl.StaticLoggerBinder; import org.springframework.boot.logging.AbstractLoggingSystemTests; import org.springframework.boot.logging.LogFile; import org.springframework.boot.logging.LogLevel; +import org.springframework.boot.logging.LoggerConfiguration; import org.springframework.boot.logging.LoggingInitializationContext; import org.springframework.boot.testutil.InternalOutputCapture; import org.springframework.boot.testutil.Matched; @@ -59,6 +61,7 @@ import static org.mockito.Mockito.verify; * @author Dave Syer * @author Phillip Webb * @author Andy Wilkinson + * @author Ben Hale */ public class LogbackLoggingSystemTests extends AbstractLoggingSystemTests { @@ -159,6 +162,28 @@ public class LogbackLoggingSystemTests extends AbstractLoggingSystemTests { "classpath:logback-nonexistent.xml", null); } + @Test + public void getLoggingConfiguration() throws Exception { + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(this.initializationContext, null, null); + this.loggingSystem.setLogLevel(getClass().getName(), LogLevel.DEBUG); + assertThat(this.loggingSystem.getLoggerConfiguration(getClass().getName())) + .isEqualTo(new LoggerConfiguration(getClass().getName(), + LogLevel.DEBUG, LogLevel.DEBUG)); + } + + @Test + public void listLoggingConfigurations() throws Exception { + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(this.initializationContext, null, null); + this.loggingSystem.setLogLevel(getClass().getName(), LogLevel.DEBUG); + Collection loggerConfigurations = this.loggingSystem + .listLoggerConfigurations(); + assertThat(loggerConfigurations.size()).isGreaterThan(0); + assertThat(loggerConfigurations.iterator().next().getName()).isEqualTo( + org.slf4j.Logger.ROOT_LOGGER_NAME); + } + @Test public void setLevel() throws Exception { this.loggingSystem.beforeInitialize(); From a4481836812ffe10eba8cceb749ee96bbffc1b48 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 21 Oct 2016 18:40:54 -0700 Subject: [PATCH 2/2] Polish `/loggers` actuator endpoint See gh-7086 --- .../actuate/endpoint/LoggersEndpoint.java | 66 ++++++++------ .../endpoint/mvc/LoggersMvcEndpoint.java | 12 +-- .../EndpointAutoConfigurationTests.java | 3 +- .../endpoint/LoggersEndpointTests.java | 27 +++--- .../endpoint/mvc/LoggersMvcEndpointTests.java | 46 ++++++---- .../boot/logging/AbstractLoggingSystem.java | 33 +++++++ .../boot/logging/LoggerConfiguration.java | 6 +- .../LoggerConfigurationComparator.java | 2 - .../boot/logging/LoggingSystem.java | 40 ++++----- .../boot/logging/java/JavaLoggingSystem.java | 85 ++++++++----------- .../logging/log4j2/Log4J2LoggingSystem.java | 82 ++++++++---------- .../logging/logback/LogbackLoggingSystem.java | 74 ++++++++-------- .../LoggerConfigurationComparatorTests.java | 4 +- .../LoggingApplicationListenerTests.java | 40 ++++----- .../boot/logging/LoggingSystemTests.java | 2 +- .../logging/java/JavaLoggingSystemTests.java | 45 +++++----- .../log4j2/Log4J2LoggingSystemTests.java | 44 +++++----- .../logback/LogbackLoggingSystemTests.java | 47 +++++----- 18 files changed, 345 insertions(+), 313 deletions(-) diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LoggersEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LoggersEndpoint.java index 1cdae7dc5e..334d578295 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LoggersEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LoggersEndpoint.java @@ -31,10 +31,12 @@ import org.springframework.util.Assert; * {@link Endpoint} to expose a collection of {@link LoggerConfiguration}s. * * @author Ben Hale + * @author Phillip Webb * @since 1.5.0 */ @ConfigurationProperties(prefix = "endpoints.loggers") -public class LoggersEndpoint extends AbstractEndpoint>> { +public class LoggersEndpoint + extends AbstractEndpoint> { private final LoggingSystem loggingSystem; @@ -49,44 +51,58 @@ public class LoggersEndpoint extends AbstractEndpoint> invoke() { - Collection loggerConfigurations = this.loggingSystem - .listLoggerConfigurations(); - - if (loggerConfigurations == null) { + public Map invoke() { + Collection configurations = this.loggingSystem + .getLoggerConfigurations(); + if (configurations == null) { return Collections.emptyMap(); } - - Map> result = new LinkedHashMap>(loggerConfigurations.size()); - - for (LoggerConfiguration loggerConfiguration : loggerConfigurations) { - result.put(loggerConfiguration.getName(), result(loggerConfiguration)); + Map result = new LinkedHashMap( + configurations.size()); + for (LoggerConfiguration configuration : configurations) { + result.put(configuration.getName(), new LoggerLevels(configuration)); } - return result; } - public Map get(String name) { + public LoggerLevels invoke(String name) { Assert.notNull(name, "Name must not be null"); - return result(this.loggingSystem.getLoggerConfiguration(name)); + LoggerConfiguration configuration = this.loggingSystem + .getLoggerConfiguration(name); + return (configuration == null ? null : new LoggerLevels(configuration)); } - public void set(String name, LogLevel level) { + public void setLogLevel(String name, LogLevel level) { Assert.notNull(name, "Name must not be empty"); this.loggingSystem.setLogLevel(name, level); } - private static Map result(LoggerConfiguration loggerConfiguration) { - if (loggerConfiguration == null) { - return Collections.emptyMap(); + /** + * Levels configured for a given logger exposed in a JSON friendly way. + */ + public static class LoggerLevels { + + private String configuredLevel; + + private String effectiveLevel; + + public LoggerLevels(LoggerConfiguration configuration) { + this.configuredLevel = getName(configuration.getConfiguredLevel()); + this.effectiveLevel = getName(configuration.getEffectiveLevel()); } - Map result = new LinkedHashMap(3); - LogLevel configuredLevel = loggerConfiguration.getConfiguredLevel(); - result.put("configuredLevel", - configuredLevel != null ? configuredLevel.name() : null); - result.put("effectiveLevel", loggerConfiguration.getEffectiveLevel().name()); - return result; + + private String getName(LogLevel level) { + return (level == null ? null : level.name()); + } + + public String getConfiguredLevel() { + return this.configuredLevel; + } + + public String getEffectiveLevel() { + return this.effectiveLevel; + } + } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpoint.java index c0fac73af7..fc635a10ed 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpoint.java @@ -19,8 +19,10 @@ package org.springframework.boot.actuate.endpoint.mvc; import java.util.Map; import org.springframework.boot.actuate.endpoint.LoggersEndpoint; +import org.springframework.boot.actuate.endpoint.LoggersEndpoint.LoggerLevels; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.logging.LogLevel; +import org.springframework.http.HttpEntity; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -54,11 +56,11 @@ public class LoggersMvcEndpoint extends EndpointMvcAdapter { // disabled return getDisabledResponse(); } - return this.delegate.get(name); + LoggerLevels levels = this.delegate.invoke(name); + return (levels == null ? ResponseEntity.notFound().build() : levels); } - @PostMapping(value = "/{name:.*}", consumes = MediaType.APPLICATION_JSON_VALUE, - produces = MediaType.APPLICATION_JSON_VALUE) + @PostMapping(value = "/{name:.*}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody @HypermediaDisabled public Object set(@PathVariable String name, @@ -69,8 +71,8 @@ public class LoggersMvcEndpoint extends EndpointMvcAdapter { return getDisabledResponse(); } String level = configuration.get("configuredLevel"); - this.delegate.set(name, level == null ? null : LogLevel.valueOf(level)); - return ResponseEntity.EMPTY; + this.delegate.setLogLevel(name, level == null ? null : LogLevel.valueOf(level)); + return HttpEntity.EMPTY; } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java index 31044b0b02..e41fc55a8c 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java @@ -37,6 +37,7 @@ import org.springframework.boot.actuate.endpoint.HealthEndpoint; import org.springframework.boot.actuate.endpoint.InfoEndpoint; import org.springframework.boot.actuate.endpoint.LiquibaseEndpoint; import org.springframework.boot.actuate.endpoint.LoggersEndpoint; +import org.springframework.boot.actuate.endpoint.LoggersEndpoint.LoggerLevels; import org.springframework.boot.actuate.endpoint.MetricsEndpoint; import org.springframework.boot.actuate.endpoint.PublicMetrics; import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint; @@ -129,7 +130,7 @@ public class EndpointAutoConfigurationTests { public void loggersEndpointHasLoggers() throws Exception { load(CustomLoggingConfig.class, EndpointAutoConfiguration.class); LoggersEndpoint endpoint = this.context.getBean(LoggersEndpoint.class); - Map> loggers = endpoint.invoke(); + Map loggers = endpoint.invoke(); assertThat(loggers.size()).isGreaterThan(0); } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LoggersEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LoggersEndpointTests.java index 53572d4a07..21e27f7b8c 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LoggersEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LoggersEndpointTests.java @@ -17,10 +17,10 @@ package org.springframework.boot.actuate.endpoint; import java.util.Collections; -import java.util.Map; import org.junit.Test; +import org.springframework.boot.actuate.endpoint.LoggersEndpoint.LoggerLevels; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.LoggerConfiguration; @@ -45,25 +45,24 @@ public class LoggersEndpointTests extends AbstractEndpointTests } @Test - public void invoke() throws Exception { - given(getLoggingSystem().listLoggerConfigurations()).willReturn(Collections - .singleton(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG))); - Map loggingConfiguration = getEndpointBean().invoke() - .get("ROOT"); - assertThat(loggingConfiguration.get("configuredLevel")).isNull(); - assertThat(loggingConfiguration.get("effectiveLevel")).isEqualTo("DEBUG"); + public void invokeShouldReturnConfigurations() throws Exception { + given(getLoggingSystem().getLoggerConfigurations()).willReturn(Collections + .singletonList(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG))); + LoggerLevels levels = getEndpointBean().invoke().get("ROOT"); + assertThat(levels.getConfiguredLevel()).isNull(); + assertThat(levels.getEffectiveLevel()).isEqualTo("DEBUG"); } - public void get() throws Exception { + public void invokeWhenNameSpecifiedShouldReturnLevels() throws Exception { given(getLoggingSystem().getLoggerConfiguration("ROOT")) .willReturn(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG)); - Map loggingConfiguration = getEndpointBean().get("ROOT"); - assertThat(loggingConfiguration.get("configuredLevel")).isNull(); - assertThat(loggingConfiguration.get("effectiveLevel")).isEqualTo("DEBUG"); + LoggerLevels levels = getEndpointBean().invoke("ROOT"); + assertThat(levels.getConfiguredLevel()).isNull(); + assertThat(levels.getEffectiveLevel()).isEqualTo("DEBUG"); } - public void set() throws Exception { - getEndpointBean().set("ROOT", LogLevel.DEBUG); + public void setLogLevelShouldSetLevelOnLoggingSystem() throws Exception { + getEndpointBean().setLogLevel("ROOT", LogLevel.DEBUG); verify(getLoggingSystem()).setLogLevel("ROOT", LogLevel.DEBUG); } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpointTests.java index 8ada13d112..bcde5aecc6 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpointTests.java @@ -18,9 +18,11 @@ package org.springframework.boot.actuate.endpoint.mvc; import java.util.Collections; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration; @@ -37,9 +39,9 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; -import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; @@ -57,9 +59,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. * Tests for {@link LoggersMvcEndpoint}. * * @author Ben Hale + * @author Phillip Webb */ @RunWith(SpringRunner.class) -@DirtiesContext @SpringBootTest public class LoggersMvcEndpointTests { @@ -74,50 +76,60 @@ public class LoggersMvcEndpointTests { @Before public void setUp() { this.context.getBean(LoggersEndpoint.class).setEnabled(true); - this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + this.mvc = MockMvcBuilders.webAppContextSetup(this.context) + .alwaysDo(MockMvcResultHandlers.print()).build(); + } + + @After + public void reset() { + Mockito.reset(this.loggingSystem); } @Test - public void list() throws Exception { - given(this.loggingSystem.listLoggerConfigurations()).willReturn(Collections - .singleton(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG))); - + public void getLoggerShouldReturnAllLoggerConfigurations() throws Exception { + given(this.loggingSystem.getLoggerConfigurations()).willReturn(Collections + .singletonList(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG))); this.mvc.perform(get("/loggers")).andExpect(status().isOk()) .andExpect(content().string(equalTo("{\"ROOT\":{\"configuredLevel\":" + "null,\"effectiveLevel\":\"DEBUG\"}}"))); } @Test - public void listDisabled() throws Exception { + public void getLoggersWhenDisabledShouldReturnNotFound() throws Exception { this.context.getBean(LoggersEndpoint.class).setEnabled(false); this.mvc.perform(get("/loggers")).andExpect(status().isNotFound()); } @Test - public void getLogger() throws Exception { + public void getLoggerShouldReturnLogLevels() throws Exception { given(this.loggingSystem.getLoggerConfiguration("ROOT")) .willReturn(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG)); - this.mvc.perform(get("/loggers/ROOT")).andExpect(status().isOk()) - .andExpect(content().string(equalTo("{\"configuredLevel\":null," - + "\"effectiveLevel\":\"DEBUG\"}"))); + .andExpect(content().string(equalTo( + "{\"configuredLevel\":null," + "\"effectiveLevel\":\"DEBUG\"}"))); } @Test - public void getLoggerDisabled() throws Exception { + public void getLoggesWhenDisabledShouldReturnNotFound() throws Exception { this.context.getBean(LoggersEndpoint.class).setEnabled(false); this.mvc.perform(get("/loggers/ROOT")).andExpect(status().isNotFound()); } @Test - public void setLogger() throws Exception { + public void getLoggersWhenLoggerNotFoundShouldReturnNotFound() throws Exception { + this.mvc.perform(get("/loggers/com.does.not.exist")) + .andExpect(status().isNotFound()); + } + + @Test + public void setLoggerShouldSetLogLevel() throws Exception { this.mvc.perform(post("/loggers/ROOT").contentType(MediaType.APPLICATION_JSON) - .content("{\"configuredLevel\":\"DEBUG\"}")).andExpect(status().isOk()); + .content("{\"configuredLevel\":\"DEBUG\"}")).andExpect(status().isOk()); verify(this.loggingSystem).setLogLevel("ROOT", LogLevel.DEBUG); } @Test - public void setLoggerDisabled() throws Exception { + public void setLoggerWhenDisabledShouldReturnNotFound() throws Exception { this.context.getBean(LoggersEndpoint.class).setEnabled(false); this.mvc.perform(post("/loggers/ROOT").contentType(MediaType.APPLICATION_JSON) .content("{\"configuredLevel\":\"DEBUG\"}")) @@ -125,11 +137,11 @@ public class LoggersMvcEndpointTests { verifyZeroInteractions(this.loggingSystem); } + @Configuration @Import({ JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class }) - @Configuration public static class TestConfiguration { @Bean diff --git a/spring-boot/src/main/java/org/springframework/boot/logging/AbstractLoggingSystem.java b/spring-boot/src/main/java/org/springframework/boot/logging/AbstractLoggingSystem.java index d89f445062..d3521e2ced 100644 --- a/spring-boot/src/main/java/org/springframework/boot/logging/AbstractLoggingSystem.java +++ b/spring-boot/src/main/java/org/springframework/boot/logging/AbstractLoggingSystem.java @@ -16,6 +16,9 @@ package org.springframework.boot.logging; +import java.util.HashMap; +import java.util.Map; + import org.springframework.core.env.Environment; import org.springframework.core.io.ClassPathResource; import org.springframework.util.ClassUtils; @@ -173,4 +176,34 @@ public abstract class AbstractLoggingSystem extends LoggingSystem { new LoggingSystemProperties(environment).apply(logFile); } + /** + * Maintains a mapping between native levels and {@link LogLevel}. + * @param The native level type + */ + protected static class LogLevels { + + private final Map systemToNative; + + private final Map nativeToSystem; + + public LogLevels() { + this.systemToNative = new HashMap(); + this.nativeToSystem = new HashMap(); + } + + public void map(LogLevel system, T nativeLevel) { + this.systemToNative.put(system, nativeLevel); + this.nativeToSystem.put(nativeLevel, system); + } + + public LogLevel convertNativeToSystem(T level) { + return this.nativeToSystem.get(level); + } + + public T convertSystemToNative(LogLevel level) { + return this.systemToNative.get(level); + } + + } + } diff --git a/spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfiguration.java b/spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfiguration.java index 7b695c2520..4172df641e 100644 --- a/spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfiguration.java +++ b/spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfiguration.java @@ -25,14 +25,14 @@ import org.springframework.util.ObjectUtils; * @author Ben Hale * @since 1.5.0 */ -public class LoggerConfiguration { +public final class LoggerConfiguration { + + private final String name; private final LogLevel configuredLevel; private final LogLevel effectiveLevel; - private final String name; - /** * Create a new {@link LoggerConfiguration instance}. * @param name the name of the logger diff --git a/spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfigurationComparator.java b/spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfigurationComparator.java index d40b7478ac..ae0cb44095 100644 --- a/spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfigurationComparator.java +++ b/spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfigurationComparator.java @@ -45,11 +45,9 @@ public class LoggerConfigurationComparator implements Comparator listLoggerConfigurations() { - throw new UnsupportedOperationException( - "Listing logger configurations is not supported"); + public List getLoggerConfigurations() { + throw new UnsupportedOperationException("Unable to get logger configurations"); } /** - * Sets the logging level for a given logger. - * @param loggerName the name of the logger to set - * @param level the log level + * Returns the current configuration for a {@link LoggingSystem}'s logger. + * @param loggerName the name of the logger + * @return the current configuration + * @since 1.5.0 */ - public abstract void setLogLevel(String loggerName, LogLevel level); + public LoggerConfiguration getLoggerConfiguration(String loggerName) { + throw new UnsupportedOperationException("Unable to get logger configuration"); + } /** * Detect and return the logging system in use. Supports Logback and Java Logging. @@ -164,18 +166,18 @@ public abstract class LoggingSystem { } @Override - public LoggerConfiguration getLoggerConfiguration(String loggerName) { - return null; + public void setLogLevel(String loggerName, LogLevel level) { + } @Override - public Collection listLoggerConfigurations() { + public List getLoggerConfigurations() { return Collections.emptyList(); } @Override - public void setLogLevel(String loggerName, LogLevel level) { - + public LoggerConfiguration getLoggerConfiguration(String loggerName) { + return null; } } diff --git a/spring-boot/src/main/java/org/springframework/boot/logging/java/JavaLoggingSystem.java b/spring-boot/src/main/java/org/springframework/boot/logging/java/JavaLoggingSystem.java index 74a09cb7ea..d23a99d337 100644 --- a/spring-boot/src/main/java/org/springframework/boot/logging/java/JavaLoggingSystem.java +++ b/spring-boot/src/main/java/org/springframework/boot/logging/java/JavaLoggingSystem.java @@ -19,12 +19,9 @@ package org.springframework.boot.logging.java; import java.io.ByteArrayInputStream; import java.io.InputStreamReader; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.Enumeration; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.Logger; @@ -51,29 +48,19 @@ import org.springframework.util.StringUtils; */ public class JavaLoggingSystem extends AbstractLoggingSystem { - private static final LoggerConfigurationComparator COMPARATOR = - new LoggerConfigurationComparator(""); + private static final LoggerConfigurationComparator COMPARATOR = new LoggerConfigurationComparator( + ""); - private static final Map LEVELS; - - private static final Map LOG_LEVELS; + private static final LogLevels LEVELS = new LogLevels(); static { - Map levels = new HashMap(); - levels.put(LogLevel.TRACE, Level.FINEST); - levels.put(LogLevel.DEBUG, Level.FINE); - levels.put(LogLevel.INFO, Level.INFO); - levels.put(LogLevel.WARN, Level.WARNING); - levels.put(LogLevel.ERROR, Level.SEVERE); - levels.put(LogLevel.FATAL, Level.SEVERE); - levels.put(LogLevel.OFF, Level.OFF); - LEVELS = Collections.unmodifiableMap(levels); - - Map logLevels = new HashMap(); - for (Map.Entry entry : LEVELS.entrySet()) { - logLevels.put(entry.getValue(), entry.getKey()); - } - LOG_LEVELS = Collections.unmodifiableMap(logLevels); + LEVELS.map(LogLevel.TRACE, Level.FINEST); + LEVELS.map(LogLevel.DEBUG, Level.FINE); + LEVELS.map(LogLevel.INFO, Level.INFO); + LEVELS.map(LogLevel.WARN, Level.WARNING); + LEVELS.map(LogLevel.ERROR, Level.SEVERE); + LEVELS.map(LogLevel.FATAL, Level.SEVERE); + LEVELS.map(LogLevel.OFF, Level.OFF); } public JavaLoggingSystem(ClassLoader classLoader) { @@ -126,44 +113,39 @@ public class JavaLoggingSystem extends AbstractLoggingSystem { } } - @Override - public LoggerConfiguration getLoggerConfiguration(String loggerName) { - return toLoggerConfiguration(Logger.getLogger(loggerName)); - } - - @Override - public Collection listLoggerConfigurations() { - List result = new ArrayList(); - for (Enumeration loggerNames = - LogManager.getLogManager().getLoggerNames(); - loggerNames.hasMoreElements(); ) { - result.add(toLoggerConfiguration(Logger.getLogger( - loggerNames.nextElement()))); - } - Collections.sort(result, COMPARATOR); - return result; - } - @Override public void setLogLevel(String loggerName, LogLevel level) { Assert.notNull(level, "Level must not be null"); String name = (StringUtils.hasText(loggerName) ? loggerName : ""); Logger logger = Logger.getLogger(name); - logger.setLevel(LEVELS.get(level)); + if (logger != null) { + logger.setLevel(LEVELS.convertSystemToNative(level)); + } } @Override - public Runnable getShutdownHandler() { - return new ShutdownHandler(); + public List getLoggerConfigurations() { + List result = new ArrayList(); + Enumeration names = LogManager.getLogManager().getLoggerNames(); + while (names.hasMoreElements()) { + result.add(getLoggerConfiguration(names.nextElement())); + } + Collections.sort(result, COMPARATOR); + return Collections.unmodifiableList(result); } - private static LoggerConfiguration toLoggerConfiguration(Logger logger) { - return new LoggerConfiguration(logger.getName(), - LOG_LEVELS.get(logger.getLevel()), - LOG_LEVELS.get(getEffectiveLevel(logger))); + @Override + public LoggerConfiguration getLoggerConfiguration(String loggerName) { + Logger logger = Logger.getLogger(loggerName); + if (logger == null) { + return null; + } + LogLevel level = LEVELS.convertNativeToSystem(logger.getLevel()); + LogLevel effectiveLevel = LEVELS.convertNativeToSystem(getEffectiveLevel(logger)); + return new LoggerConfiguration(logger.getName(), level, effectiveLevel); } - private static Level getEffectiveLevel(Logger root) { + private Level getEffectiveLevel(Logger root) { Logger logger = root; while (logger.getLevel() == null) { logger = logger.getParent(); @@ -171,6 +153,11 @@ public class JavaLoggingSystem extends AbstractLoggingSystem { return logger.getLevel(); } + @Override + public Runnable getShutdownHandler() { + return new ShutdownHandler(); + } + private final class ShutdownHandler implements Runnable { @Override diff --git a/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java b/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java index 9e232d6569..999daf16cb 100644 --- a/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java +++ b/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java @@ -20,11 +20,8 @@ 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.HashMap; import java.util.List; -import java.util.Map; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; @@ -33,6 +30,7 @@ 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; @@ -62,31 +60,21 @@ import org.springframework.util.StringUtils; */ public class Log4J2LoggingSystem extends Slf4JLoggingSystem { - private static final LoggerConfigurationComparator COMPARATOR = - new LoggerConfigurationComparator(LogManager.ROOT_LOGGER_NAME); + private static final LoggerConfigurationComparator COMPARATOR = new LoggerConfigurationComparator( + LogManager.ROOT_LOGGER_NAME); private static final String FILE_PROTOCOL = "file"; - private static final Map LEVELS; - - private static final Map LOG_LEVELS; + private static final LogLevels LEVELS = new LogLevels(); static { - Map levels = new HashMap(); - levels.put(LogLevel.TRACE, Level.TRACE); - levels.put(LogLevel.DEBUG, Level.DEBUG); - levels.put(LogLevel.INFO, Level.INFO); - levels.put(LogLevel.WARN, Level.WARN); - levels.put(LogLevel.ERROR, Level.ERROR); - levels.put(LogLevel.FATAL, Level.FATAL); - levels.put(LogLevel.OFF, Level.OFF); - LEVELS = Collections.unmodifiableMap(levels); - - Map logLevels = new HashMap(); - for (Map.Entry entry : LEVELS.entrySet()) { - logLevels.put(entry.getValue(), entry.getKey()); - } - LOG_LEVELS = Collections.unmodifiableMap(logLevels); + LEVELS.map(LogLevel.TRACE, Level.TRACE); + LEVELS.map(LogLevel.DEBUG, Level.DEBUG); + LEVELS.map(LogLevel.INFO, Level.INFO); + LEVELS.map(LogLevel.WARN, Level.WARN); + LEVELS.map(LogLevel.ERROR, Level.ERROR); + LEVELS.map(LogLevel.FATAL, Level.FATAL); + LEVELS.map(LogLevel.OFF, Level.OFF); } private static final Filter FILTER = new AbstractFilter() { @@ -209,25 +197,9 @@ public class Log4J2LoggingSystem extends Slf4JLoggingSystem { getLoggerContext().reconfigure(); } - @Override - public LoggerConfiguration getLoggerConfiguration(String loggerName) { - return toLoggerConfiguration(getLoggerConfig(loggerName)); - } - - @Override - public Collection listLoggerConfigurations() { - List result = new ArrayList(); - for (LoggerConfig loggerConfig : - getLoggerContext().getConfiguration().getLoggers().values()) { - result.add(toLoggerConfiguration(loggerConfig)); - } - Collections.sort(result, COMPARATOR); - return result; - } - @Override public void setLogLevel(String loggerName, LogLevel logLevel) { - Level level = LEVELS.get(logLevel); + Level level = LEVELS.convertSystemToNative(logLevel); LoggerConfig loggerConfig = getLoggerConfig(loggerName); if (loggerConfig == null) { loggerConfig = new LoggerConfig(loggerName, level, true); @@ -239,6 +211,30 @@ public class Log4J2LoggingSystem extends Slf4JLoggingSystem { getLoggerContext().updateLoggers(); } + @Override + public List getLoggerConfigurations() { + List result = new ArrayList(); + Configuration configuration = getLoggerContext().getConfiguration(); + for (LoggerConfig loggerConfig : configuration.getLoggers().values()) { + result.add(convertLoggerConfiguration(loggerConfig)); + } + Collections.sort(result, COMPARATOR); + return result; + } + + @Override + public LoggerConfiguration getLoggerConfiguration(String loggerName) { + return convertLoggerConfiguration(getLoggerConfig(loggerName)); + } + + private LoggerConfiguration convertLoggerConfiguration(LoggerConfig loggerConfig) { + if (loggerConfig == null) { + return null; + } + LogLevel level = LEVELS.convertNativeToSystem(loggerConfig.getLevel()); + return new LoggerConfiguration(loggerConfig.getName(), level, level); + } + @Override public Runnable getShutdownHandler() { return new ShutdownHandler(); @@ -272,12 +268,6 @@ public class Log4J2LoggingSystem extends Slf4JLoggingSystem { loggerContext.setExternalContext(null); } - private static LoggerConfiguration toLoggerConfiguration(LoggerConfig loggerConfig) { - return new LoggerConfiguration(loggerConfig.getName(), - LOG_LEVELS.get(loggerConfig.getLevel()), - LOG_LEVELS.get(loggerConfig.getLevel())); - } - private final class ShutdownHandler implements Runnable { @Override diff --git a/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java b/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java index f32cf80b9b..f58ba8f57e 100644 --- a/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java +++ b/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java @@ -20,11 +20,8 @@ import java.net.URL; import java.security.CodeSource; import java.security.ProtectionDomain; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.LoggerContext; @@ -61,31 +58,21 @@ import org.springframework.util.StringUtils; */ public class LogbackLoggingSystem extends Slf4JLoggingSystem { - private static final LoggerConfigurationComparator COMPARATOR = - new LoggerConfigurationComparator(Logger.ROOT_LOGGER_NAME); + private static final LoggerConfigurationComparator COMPARATOR = new LoggerConfigurationComparator( + Logger.ROOT_LOGGER_NAME); private static final String CONFIGURATION_FILE_PROPERTY = "logback.configurationFile"; - private static final Map LEVELS; - - private static final Map LOG_LEVELS; + private static final LogLevels LEVELS = new LogLevels(); static { - Map levels = new HashMap(); - levels.put(LogLevel.TRACE, Level.TRACE); - levels.put(LogLevel.DEBUG, Level.DEBUG); - levels.put(LogLevel.INFO, Level.INFO); - levels.put(LogLevel.WARN, Level.WARN); - levels.put(LogLevel.ERROR, Level.ERROR); - levels.put(LogLevel.FATAL, Level.ERROR); - levels.put(LogLevel.OFF, Level.OFF); - LEVELS = Collections.unmodifiableMap(levels); - - Map logLevels = new HashMap(); - for (Map.Entry entry : LEVELS.entrySet()) { - logLevels.put(entry.getValue(), entry.getKey()); - } - LOG_LEVELS = Collections.unmodifiableMap(logLevels); + LEVELS.map(LogLevel.TRACE, Level.TRACE); + LEVELS.map(LogLevel.DEBUG, Level.DEBUG); + LEVELS.map(LogLevel.INFO, Level.INFO); + LEVELS.map(LogLevel.WARN, Level.WARN); + LEVELS.map(LogLevel.ERROR, Level.ERROR); + LEVELS.map(LogLevel.FATAL, Level.ERROR); + LEVELS.map(LogLevel.OFF, Level.OFF); } private static final TurboFilter FILTER = new TurboFilter() { @@ -226,23 +213,37 @@ public class LogbackLoggingSystem extends Slf4JLoggingSystem { } @Override - public LoggerConfiguration getLoggerConfiguration(String loggerName) { - return toLoggerConfiguration(getLogger(loggerName)); - } - - @Override - public Collection listLoggerConfigurations() { + public List getLoggerConfigurations() { List result = new ArrayList(); for (ch.qos.logback.classic.Logger logger : getLoggerContext().getLoggerList()) { - result.add(toLoggerConfiguration(logger)); + result.add(getLoggerConfiguration(logger)); } Collections.sort(result, COMPARATOR); return result; } + @Override + public LoggerConfiguration getLoggerConfiguration(String loggerName) { + return getLoggerConfiguration(getLogger(loggerName)); + } + + private LoggerConfiguration getLoggerConfiguration( + ch.qos.logback.classic.Logger logger) { + if (logger == null) { + return null; + } + LogLevel level = LEVELS.convertNativeToSystem(logger.getLevel()); + LogLevel effectiveLevel = LEVELS + .convertNativeToSystem(logger.getEffectiveLevel()); + return new LoggerConfiguration(logger.getName(), level, effectiveLevel); + } + @Override public void setLogLevel(String loggerName, LogLevel level) { - getLogger(loggerName).setLevel(LEVELS.get(level)); + ch.qos.logback.classic.Logger logger = getLogger(loggerName); + if (logger != null) { + logger.setLevel(LEVELS.convertSystemToNative(level)); + } } @Override @@ -252,8 +253,8 @@ public class LogbackLoggingSystem extends Slf4JLoggingSystem { private ch.qos.logback.classic.Logger getLogger(String name) { LoggerContext factory = getLoggerContext(); - return factory - .getLogger(StringUtils.isEmpty(name) ? Logger.ROOT_LOGGER_NAME : name); + name = (StringUtils.isEmpty(name) ? Logger.ROOT_LOGGER_NAME : name); + return factory.getLogger(name); } @@ -296,13 +297,6 @@ public class LogbackLoggingSystem extends Slf4JLoggingSystem { loggerContext.removeObject(LoggingSystem.class.getName()); } - private static LoggerConfiguration toLoggerConfiguration( - ch.qos.logback.classic.Logger logger) { - return new LoggerConfiguration(logger.getName(), - LOG_LEVELS.get(logger.getLevel()), - LOG_LEVELS.get(logger.getEffectiveLevel())); - } - private final class ShutdownHandler implements Runnable { @Override diff --git a/spring-boot/src/test/java/org/springframework/boot/logging/LoggerConfigurationComparatorTests.java b/spring-boot/src/test/java/org/springframework/boot/logging/LoggerConfigurationComparatorTests.java index 1a3cb4f765..8c7188f28c 100644 --- a/spring-boot/src/test/java/org/springframework/boot/logging/LoggerConfigurationComparatorTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/logging/LoggerConfigurationComparatorTests.java @@ -27,8 +27,8 @@ import static org.assertj.core.api.Assertions.assertThat; */ public class LoggerConfigurationComparatorTests { - private final LoggerConfigurationComparator comparator = - new LoggerConfigurationComparator("ROOT"); + private final LoggerConfigurationComparator comparator = new LoggerConfigurationComparator( + "ROOT"); @Test public void rootLoggerFirst() { diff --git a/spring-boot/src/test/java/org/springframework/boot/logging/LoggingApplicationListenerTests.java b/spring-boot/src/test/java/org/springframework/boot/logging/LoggingApplicationListenerTests.java index b6c6ff2465..cbc2647c68 100644 --- a/spring-boot/src/test/java/org/springframework/boot/logging/LoggingApplicationListenerTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/logging/LoggingApplicationListenerTests.java @@ -18,7 +18,7 @@ package org.springframework.boot.logging; import java.io.File; import java.io.IOException; -import java.util.Collection; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.logging.Handler; @@ -518,21 +518,20 @@ public class LoggingApplicationListenerTests { LogFile logFile) { } + @Override + public void setLogLevel(String loggerName, LogLevel level) { + } + + @Override + public List getLoggerConfigurations() { + return null; + } + @Override public LoggerConfiguration getLoggerConfiguration(String loggerName) { return null; } - @Override - public Collection listLoggerConfigurations() { - return null; - } - - @Override - public void setLogLevel(String loggerName, LogLevel level) { - - } - @Override public Runnable getShutdownHandler() { return new Runnable() { @@ -564,12 +563,19 @@ public class LoggingApplicationListenerTests { private boolean cleanedUp = false; public TestCleanupLoggingSystem(ClassLoader classLoader) { - } @Override public void beforeInitialize() { + } + @Override + public void setLogLevel(String loggerName, LogLevel level) { + } + + @Override + public List getLoggerConfigurations() { + return null; } @Override @@ -577,16 +583,6 @@ public class LoggingApplicationListenerTests { return null; } - @Override - public Collection listLoggerConfigurations() { - return null; - } - - @Override - public void setLogLevel(String loggerName, LogLevel level) { - - } - @Override public void cleanUp() { this.cleanedUp = true; diff --git a/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemTests.java b/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemTests.java index 735e5814a4..aa3625b92b 100644 --- a/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemTests.java @@ -49,7 +49,7 @@ public class LoggingSystemTests { @Test(expected = UnsupportedOperationException.class) public void listLoggerConfigurationsIsUnsupported() { - new StubLoggingSystem().listLoggerConfigurations(); + new StubLoggingSystem().getLoggerConfigurations(); } private static final class StubLoggingSystem extends LoggingSystem { diff --git a/spring-boot/src/test/java/org/springframework/boot/logging/java/JavaLoggingSystemTests.java b/spring-boot/src/test/java/org/springframework/boot/logging/java/JavaLoggingSystemTests.java index a624c50b05..12eb04eb9c 100644 --- a/spring-boot/src/test/java/org/springframework/boot/logging/java/JavaLoggingSystemTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/logging/java/JavaLoggingSystemTests.java @@ -19,7 +19,7 @@ package org.springframework.boot.logging.java; import java.io.File; import java.io.FileFilter; import java.io.IOException; -import java.util.Collection; +import java.util.List; import java.util.Locale; import java.util.logging.Level; @@ -148,27 +148,6 @@ public class JavaLoggingSystemTests extends AbstractLoggingSystemTests { null); } - @Test - public void getLoggingConfiguration() throws Exception { - this.loggingSystem.beforeInitialize(); - this.loggingSystem.initialize(null, null, null); - this.loggingSystem.setLogLevel(getClass().getName(), LogLevel.DEBUG); - assertThat(this.loggingSystem.getLoggerConfiguration(getClass().getName())) - .isEqualTo(new LoggerConfiguration(getClass().getName(), - LogLevel.DEBUG, LogLevel.DEBUG)); - } - - @Test - public void listLoggingConfigurations() throws Exception { - this.loggingSystem.beforeInitialize(); - this.loggingSystem.initialize(null, null, null); - this.loggingSystem.setLogLevel(getClass().getName(), LogLevel.DEBUG); - Collection loggerConfigurations = this.loggingSystem - .listLoggerConfigurations(); - assertThat(loggerConfigurations.size()).isGreaterThan(0); - assertThat(loggerConfigurations.iterator().next().getName()).isEmpty(); - } - @Test public void setLevel() throws Exception { this.loggingSystem.beforeInitialize(); @@ -180,4 +159,26 @@ public class JavaLoggingSystemTests extends AbstractLoggingSystemTests { .isEqualTo(1); } + @Test + public void getLoggingConfigurations() throws Exception { + 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()).isEmpty(); + } + + @Test + public void getLoggingConfiguration() throws Exception { + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(null, null, null); + this.loggingSystem.setLogLevel(getClass().getName(), LogLevel.DEBUG); + LoggerConfiguration configuration = this.loggingSystem + .getLoggerConfiguration(getClass().getName()); + assertThat(configuration).isEqualTo(new LoggerConfiguration(getClass().getName(), + LogLevel.DEBUG, LogLevel.DEBUG)); + } + } diff --git a/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java b/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java index 25de8b8f31..1e06c7c891 100644 --- a/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java @@ -21,7 +21,6 @@ import java.beans.PropertyChangeListener; import java.io.File; import java.io.FileReader; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; @@ -123,27 +122,6 @@ public class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests { this.loggingSystem.initialize(null, "classpath:log4j2-nonexistent.xml", null); } - @Test - public void getLoggingConfiguration() throws Exception { - this.loggingSystem.beforeInitialize(); - this.loggingSystem.initialize(null, null, null); - this.loggingSystem.setLogLevel(getClass().getName(), LogLevel.DEBUG); - assertThat(this.loggingSystem.getLoggerConfiguration(getClass().getName())) - .isEqualTo(new LoggerConfiguration(getClass().getName(), - LogLevel.DEBUG, LogLevel.DEBUG)); - } - - @Test - public void listLoggingConfigurations() throws Exception { - this.loggingSystem.beforeInitialize(); - this.loggingSystem.initialize(null, null, null); - this.loggingSystem.setLogLevel(getClass().getName(), LogLevel.DEBUG); - Collection loggerConfigurations = this.loggingSystem - .listLoggerConfigurations(); - assertThat(loggerConfigurations.size()).isGreaterThan(0); - assertThat(loggerConfigurations.iterator().next().getName()).isEmpty(); - } - @Test public void setLevel() throws Exception { this.loggingSystem.beforeInitialize(); @@ -155,6 +133,28 @@ public class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests { .isEqualTo(1); } + @Test + public void getLoggingConfigurations() throws Exception { + 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()).isEmpty(); + } + + @Test + public void getLoggingConfiguration() throws Exception { + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(null, null, null); + this.loggingSystem.setLogLevel(getClass().getName(), LogLevel.DEBUG); + LoggerConfiguration configuration = this.loggingSystem + .getLoggerConfiguration(getClass().getName()); + assertThat(configuration).isEqualTo(new LoggerConfiguration(getClass().getName(), + LogLevel.DEBUG, LogLevel.DEBUG)); + } + @Test public void setLevelOfUnconfiguredLoggerDoesNotAffectRootConfiguration() throws Exception { diff --git a/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java b/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java index 83a8fd60cb..072b9b8210 100644 --- a/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java @@ -18,7 +18,7 @@ package org.springframework.boot.logging.logback; import java.io.File; import java.io.FileReader; -import java.util.Collection; +import java.util.List; import java.util.logging.Handler; import java.util.logging.LogManager; @@ -162,28 +162,6 @@ public class LogbackLoggingSystemTests extends AbstractLoggingSystemTests { "classpath:logback-nonexistent.xml", null); } - @Test - public void getLoggingConfiguration() throws Exception { - this.loggingSystem.beforeInitialize(); - this.loggingSystem.initialize(this.initializationContext, null, null); - this.loggingSystem.setLogLevel(getClass().getName(), LogLevel.DEBUG); - assertThat(this.loggingSystem.getLoggerConfiguration(getClass().getName())) - .isEqualTo(new LoggerConfiguration(getClass().getName(), - LogLevel.DEBUG, LogLevel.DEBUG)); - } - - @Test - public void listLoggingConfigurations() throws Exception { - this.loggingSystem.beforeInitialize(); - this.loggingSystem.initialize(this.initializationContext, null, null); - this.loggingSystem.setLogLevel(getClass().getName(), LogLevel.DEBUG); - Collection loggerConfigurations = this.loggingSystem - .listLoggerConfigurations(); - assertThat(loggerConfigurations.size()).isGreaterThan(0); - assertThat(loggerConfigurations.iterator().next().getName()).isEqualTo( - org.slf4j.Logger.ROOT_LOGGER_NAME); - } - @Test public void setLevel() throws Exception { this.loggingSystem.beforeInitialize(); @@ -195,6 +173,29 @@ public class LogbackLoggingSystemTests extends AbstractLoggingSystemTests { .isEqualTo(1); } + @Test + public void getLoggingConfigurations() throws Exception { + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(this.initializationContext, null, null); + this.loggingSystem.setLogLevel(getClass().getName(), LogLevel.DEBUG); + List configurations = this.loggingSystem + .getLoggerConfigurations(); + assertThat(configurations).isNotEmpty(); + assertThat(configurations.get(0).getName()) + .isEqualTo(org.slf4j.Logger.ROOT_LOGGER_NAME); + } + + @Test + public void getLoggingConfiguration() throws Exception { + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(this.initializationContext, null, null); + this.loggingSystem.setLogLevel(getClass().getName(), LogLevel.DEBUG); + LoggerConfiguration configuration = this.loggingSystem + .getLoggerConfiguration(getClass().getName()); + assertThat(configuration).isEqualTo(new LoggerConfiguration(getClass().getName(), + LogLevel.DEBUG, LogLevel.DEBUG)); + } + @Test public void loggingThatUsesJulIsCaptured() { this.loggingSystem.beforeInitialize();