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
This commit is contained in:
Ben Hale 2016-09-22 16:52:22 -07:00 committed by Phillip Webb
parent ae4dd0d17e
commit 06cb4fcca5
22 changed files with 1021 additions and 10 deletions

View File

@ -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.<InfoContributor>emptyList() : this.infoContributors);
}
@Bean
@ConditionalOnBean(LoggingSystem.class)
@ConditionalOnMissingBean
public LoggersEndpoint loggersEndpoint(LoggingSystem loggingSystem) {
return new LoggersEndpoint(loggingSystem);
}
@Bean
@ConditionalOnMissingBean
public MetricsEndpoint metricsEndpoint() {

View File

@ -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")

View File

@ -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<Map<String, Map<String, String>>> {
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<String, Map<String, String>> invoke() {
Collection<LoggerConfiguration> loggerConfigurations = this.loggingSystem
.listLoggerConfigurations();
if (loggerConfigurations == null) {
return Collections.emptyMap();
}
Map<String, Map<String, String>> result = new LinkedHashMap<String,
Map<String, String>>(loggerConfigurations.size());
for (LoggerConfiguration loggerConfiguration : loggerConfigurations) {
result.put(loggerConfiguration.getName(), result(loggerConfiguration));
}
return result;
}
public Map<String, String> 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<String, String> result(LoggerConfiguration loggerConfiguration) {
if (loggerConfiguration == null) {
return Collections.emptyMap();
}
Map<String, String> result = new LinkedHashMap<String, String>(3);
LogLevel configuredLevel = loggerConfiguration.getConfiguredLevel();
result.put("configuredLevel",
configuredLevel != null ? configuredLevel.name() : null);
result.put("effectiveLevel", loggerConfiguration.getEffectiveLevel().name());
return result;
}
}

View File

@ -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<String, String> 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;
}
}

View File

@ -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<String, Map<String, String>> 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 {

View File

@ -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<? extends MvcEndpoint> 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 {

View File

@ -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());

View File

@ -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<LoggersEndpoint> {
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<String, String> 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<String, String> 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);
}
}
}

View File

@ -119,7 +119,7 @@ public class HalBrowserMvcEndpointVanillaIntegrationTests {
@Test
public void endpointsEachHaveSelf() throws Exception {
Set<String> collections = new HashSet<String>(
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)) {

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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<LoggerConfiguration> {
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());
}
}

View File

@ -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<LoggerConfiguration> 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<LoggerConfiguration> listLoggerConfigurations() {
return Collections.emptyList();
}
@Override
public void setLogLevel(String loggerName, LogLevel level) {

View File

@ -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<LogLevel, Level> LEVELS;
private static final Map<Level, LogLevel> LOG_LEVELS;
static {
Map<LogLevel, Level> levels = new HashMap<LogLevel, Level>();
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<Level, LogLevel> logLevels = new HashMap<Level, LogLevel>();
for (Map.Entry<LogLevel, Level> 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<LoggerConfiguration> listLoggerConfigurations() {
List<LoggerConfiguration> result = new ArrayList<LoggerConfiguration>();
for (Enumeration<String> 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

View File

@ -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<LogLevel, Level> LEVELS;
private static final Map<Level, LogLevel> LOG_LEVELS;
static {
Map<LogLevel, Level> levels = new HashMap<LogLevel, Level>();
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<Level, LogLevel> logLevels = new HashMap<Level, LogLevel>();
for (Map.Entry<LogLevel, Level> 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<LoggerConfiguration> listLoggerConfigurations() {
List<LoggerConfiguration> result = new ArrayList<LoggerConfiguration>();
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

View File

@ -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<LogLevel, Level> LEVELS;
private static final Map<Level, LogLevel> LOG_LEVELS;
static {
Map<LogLevel, Level> levels = new HashMap<LogLevel, Level>();
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<Level, LogLevel> logLevels = new HashMap<Level, LogLevel>();
for (Map.Entry<LogLevel, Level> 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<LoggerConfiguration> listLoggerConfigurations() {
List<LoggerConfiguration> result = new ArrayList<LoggerConfiguration>();
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

View File

@ -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);
}
}

View File

@ -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<LoggerConfiguration> 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<LoggerConfiguration> listLoggerConfigurations() {
return null;
}
@Override
public void setLogLevel(String loggerName, LogLevel level) {

View File

@ -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
}
}
}

View File

@ -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<LoggerConfiguration> loggerConfigurations = this.loggingSystem
.listLoggerConfigurations();
assertThat(loggerConfigurations.size()).isGreaterThan(0);
assertThat(loggerConfigurations.iterator().next().getName()).isEmpty();
}
@Test
public void setLevel() throws Exception {
this.loggingSystem.beforeInitialize();

View File

@ -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<LoggerConfiguration> loggerConfigurations = this.loggingSystem
.listLoggerConfigurations();
assertThat(loggerConfigurations.size()).isGreaterThan(0);
assertThat(loggerConfigurations.iterator().next().getName()).isEmpty();
}
@Test
public void setLevel() throws Exception {
this.loggingSystem.beforeInitialize();

View File

@ -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<LoggerConfiguration> 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();