Commit 61b86ff2 authored by Madhura Bhave's avatar Madhura Bhave

Polish "Add support for configuring logging groups"

See gh-17515
Co-authored-by: 's avatarPhillip Webb <pwebb@pivotal.io>
parent b9047c22
...@@ -57,6 +57,21 @@ include::{snippets}loggers/single/response-fields.adoc[] ...@@ -57,6 +57,21 @@ include::{snippets}loggers/single/response-fields.adoc[]
[[loggers-group]]
== Retrieving a Single Group
To retrieve a single group, make a `GET` request to `/actuator/loggers/{group.name}`,
as shown in the following curl-based example:
include::{snippets}loggers/group/curl-request.adoc[]
The preceding example retrieves information about the logger group named `test`. The
resulting response is similar to the following:
include::{snippets}loggers/group/http-response.adoc[]
[[loggers-setting-level]] [[loggers-setting-level]]
== Setting a Log Level == Setting a Log Level
...@@ -81,6 +96,30 @@ include::{snippets}loggers/set/request-fields.adoc[] ...@@ -81,6 +96,30 @@ include::{snippets}loggers/set/request-fields.adoc[]
[[loggers-setting-level]]
== Setting a Log Level for a Group
To set the level of a logger, make a `POST` request to
`/actuator/loggers/{group.name}` with a JSON body that specifies the configured level
for the logger group, as shown in the following curl-based example:
include::{snippets}loggers/setGroup/curl-request.adoc[]
The preceding example sets the `configuredLevel` of the `test` logger group to `DEBUG`.
[[loggers-setting-level-request-structure]]
=== Request Structure
The request specifies the desired level of the logger group. The following table describes the
structure of the request:
[cols="3,1,3"]
include::{snippets}loggers/set/request-fields.adoc[]
[[loggers-clearing-level]] [[loggers-clearing-level]]
== Clearing a Log Level == Clearing a Log Level
......
...@@ -25,7 +25,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionOutcome; ...@@ -25,7 +25,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.logging.LoggingGroups; import org.springframework.boot.logging.LoggerGroups;
import org.springframework.boot.logging.LoggingSystem; import org.springframework.boot.logging.LoggingSystem;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.ConditionContext;
...@@ -48,8 +48,8 @@ public class LoggersEndpointAutoConfiguration { ...@@ -48,8 +48,8 @@ public class LoggersEndpointAutoConfiguration {
@Conditional(OnEnabledLoggingSystemCondition.class) @Conditional(OnEnabledLoggingSystemCondition.class)
@ConditionalOnMissingBean @ConditionalOnMissingBean
public LoggersEndpoint loggersEndpoint(LoggingSystem loggingSystem, public LoggersEndpoint loggersEndpoint(LoggingSystem loggingSystem,
ObjectProvider<LoggingGroups> loggingGroupsObjectProvider) { ObjectProvider<LoggerGroups> springBootLoggerGroups) {
return new LoggersEndpoint(loggingSystem, loggingGroupsObjectProvider.getIfAvailable()); return new LoggersEndpoint(loggingSystem, springBootLoggerGroups.getIfAvailable(LoggerGroups::new));
} }
static class OnEnabledLoggingSystemCondition extends SpringBootCondition { static class OnEnabledLoggingSystemCondition extends SpringBootCondition {
......
...@@ -20,14 +20,15 @@ import java.util.Arrays; ...@@ -20,14 +20,15 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.logging.LoggersEndpoint; import org.springframework.boot.actuate.logging.LoggersEndpoint;
import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggerConfiguration; import org.springframework.boot.logging.LoggerConfiguration;
import org.springframework.boot.logging.LoggingGroups; import org.springframework.boot.logging.LoggerGroups;
import org.springframework.boot.logging.LoggingSystem; import org.springframework.boot.logging.LoggingSystem;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
...@@ -57,18 +58,20 @@ class LoggersEndpointDocumentationTests extends MockMvcEndpointDocumentationTest ...@@ -57,18 +58,20 @@ class LoggersEndpointDocumentationTests extends MockMvcEndpointDocumentationTest
fieldWithPath("configuredLevel").description("Configured level of the logger, if any.").optional(), fieldWithPath("configuredLevel").description("Configured level of the logger, if any.").optional(),
fieldWithPath("effectiveLevel").description("Effective level of the logger.")); fieldWithPath("effectiveLevel").description("Effective level of the logger."));
private static final List<FieldDescriptor> groupLevelFields = Arrays.asList( private static final List<FieldDescriptor> groupLevelFields;
fieldWithPath("configuredLevel").description("Configured level of the logger group"),
static {
groupLevelFields = Arrays.asList(
fieldWithPath("configuredLevel").description("Configured level of the logger group")
.type(LogLevel.class).optional(),
fieldWithPath("members").description("Loggers that are part of this group").optional()); fieldWithPath("members").description("Loggers that are part of this group").optional());
}
@MockBean @MockBean
private LoggingSystem loggingSystem; private LoggingSystem loggingSystem;
@MockBean @Autowired
private ObjectProvider<LoggingGroups> loggingGroupsObjectProvider; private LoggerGroups loggerGroups;
@MockBean
LoggingGroups loggingGroups;
@Test @Test
void allLoggers() throws Exception { void allLoggers() throws Exception {
...@@ -76,10 +79,6 @@ class LoggersEndpointDocumentationTests extends MockMvcEndpointDocumentationTest ...@@ -76,10 +79,6 @@ class LoggersEndpointDocumentationTests extends MockMvcEndpointDocumentationTest
given(this.loggingSystem.getLoggerConfigurations()) given(this.loggingSystem.getLoggerConfigurations())
.willReturn(Arrays.asList(new LoggerConfiguration("ROOT", LogLevel.INFO, LogLevel.INFO), .willReturn(Arrays.asList(new LoggerConfiguration("ROOT", LogLevel.INFO, LogLevel.INFO),
new LoggerConfiguration("com.example", LogLevel.DEBUG, LogLevel.DEBUG))); new LoggerConfiguration("com.example", LogLevel.DEBUG, LogLevel.DEBUG)));
given(this.loggingGroupsObjectProvider.getIfAvailable()).willReturn(this.loggingGroups);
given(this.loggingGroups.getLoggerGroupNames()).willReturn(Collections.singleton("test"));
given(this.loggingGroups.getLoggerGroup("test")).willReturn(Arrays.asList("test.member"));
given(this.loggingGroups.getLoggerGroupConfiguredLevel("test")).willReturn(LogLevel.INFO);
this.mockMvc.perform(get("/actuator/loggers")).andExpect(status().isOk()) this.mockMvc.perform(get("/actuator/loggers")).andExpect(status().isOk())
.andDo(MockMvcRestDocumentation.document("loggers/all", .andDo(MockMvcRestDocumentation.document("loggers/all",
responseFields(fieldWithPath("levels").description("Levels support by the logging system."), responseFields(fieldWithPath("levels").description("Levels support by the logging system."),
...@@ -91,7 +90,6 @@ class LoggersEndpointDocumentationTests extends MockMvcEndpointDocumentationTest ...@@ -91,7 +90,6 @@ class LoggersEndpointDocumentationTests extends MockMvcEndpointDocumentationTest
@Test @Test
void logger() throws Exception { void logger() throws Exception {
given(this.loggingGroupsObjectProvider.getIfAvailable()).willReturn(this.loggingGroups);
given(this.loggingSystem.getLoggerConfiguration("com.example")) given(this.loggingSystem.getLoggerConfiguration("com.example"))
.willReturn(new LoggerConfiguration("com.example", LogLevel.INFO, LogLevel.INFO)); .willReturn(new LoggerConfiguration("com.example", LogLevel.INFO, LogLevel.INFO));
this.mockMvc.perform(get("/actuator/loggers/com.example")).andExpect(status().isOk()) this.mockMvc.perform(get("/actuator/loggers/com.example")).andExpect(status().isOk())
...@@ -100,17 +98,12 @@ class LoggersEndpointDocumentationTests extends MockMvcEndpointDocumentationTest ...@@ -100,17 +98,12 @@ class LoggersEndpointDocumentationTests extends MockMvcEndpointDocumentationTest
@Test @Test
void loggerGroups() throws Exception { void loggerGroups() throws Exception {
given(this.loggingGroupsObjectProvider.getIfAvailable()).willReturn(this.loggingGroups); this.mockMvc.perform(get("/actuator/loggers/test")).andExpect(status().isOk())
given(this.loggingGroups.isGroup("com.example")).willReturn(true);
given(this.loggingGroups.getLoggerGroup("com.example")).willReturn(Arrays.asList("com.member", "com.member2"));
given(this.loggingGroups.getLoggerGroupConfiguredLevel("com.example")).willReturn(LogLevel.INFO);
this.mockMvc.perform(get("/actuator/loggers/com.example")).andExpect(status().isOk())
.andDo(MockMvcRestDocumentation.document("loggers/group", responseFields(groupLevelFields))); .andDo(MockMvcRestDocumentation.document("loggers/group", responseFields(groupLevelFields)));
} }
@Test @Test
void setLogLevel() throws Exception { void setLogLevel() throws Exception {
given(this.loggingGroupsObjectProvider.getIfAvailable()).willReturn(this.loggingGroups);
this.mockMvc this.mockMvc
.perform(post("/actuator/loggers/com.example").content("{\"configuredLevel\":\"debug\"}") .perform(post("/actuator/loggers/com.example").content("{\"configuredLevel\":\"debug\"}")
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
...@@ -122,22 +115,26 @@ class LoggersEndpointDocumentationTests extends MockMvcEndpointDocumentationTest ...@@ -122,22 +115,26 @@ class LoggersEndpointDocumentationTests extends MockMvcEndpointDocumentationTest
@Test @Test
void setLogLevelOfLoggerGroup() throws Exception { void setLogLevelOfLoggerGroup() throws Exception {
given(this.loggingGroupsObjectProvider.getIfAvailable()).willReturn(this.loggingGroups);
given(this.loggingGroups.isGroup("com.example")).willReturn(true);
this.mockMvc this.mockMvc
.perform(post("/actuator/loggers/com.example") .perform(post("/actuator/loggers/test")
.content("{\"configuredLevel\":\"debug\"}").contentType(MediaType.APPLICATION_JSON)) .content("{\"configuredLevel\":\"debug\"}").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent()).andDo( .andExpect(status().isNoContent()).andDo(
MockMvcRestDocumentation.document("loggers/setGroup", MockMvcRestDocumentation.document("loggers/setGroup",
requestFields(fieldWithPath("configuredLevel").description( requestFields(fieldWithPath("configuredLevel").description(
"Level for the logger group. May be omitted to clear the level of the loggers.") "Level for the logger group. May be omitted to clear the level of the loggers.")
.optional()))); .optional())));
verify(this.loggingGroups).setLoggerGroupLevel("com.example", LogLevel.DEBUG); verify(this.loggingSystem).setLogLevel("test.member1", LogLevel.DEBUG);
verify(this.loggingSystem).setLogLevel("test.member2", LogLevel.DEBUG);
resetLogger();
}
private void resetLogger() {
this.loggerGroups.get("test").configureLogLevel(null, (a, b) -> {
});
} }
@Test @Test
void clearLogLevel() throws Exception { void clearLogLevel() throws Exception {
given(this.loggingGroupsObjectProvider.getIfAvailable()).willReturn(this.loggingGroups);
this.mockMvc this.mockMvc
.perform(post("/actuator/loggers/com.example").content("{}").contentType(MediaType.APPLICATION_JSON)) .perform(post("/actuator/loggers/com.example").content("{}").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent()).andDo(MockMvcRestDocumentation.document("loggers/clear")); .andExpect(status().isNoContent()).andDo(MockMvcRestDocumentation.document("loggers/clear"));
...@@ -149,9 +146,13 @@ class LoggersEndpointDocumentationTests extends MockMvcEndpointDocumentationTest ...@@ -149,9 +146,13 @@ class LoggersEndpointDocumentationTests extends MockMvcEndpointDocumentationTest
static class TestConfiguration { static class TestConfiguration {
@Bean @Bean
LoggersEndpoint endpoint(LoggingSystem loggingSystem, LoggersEndpoint endpoint(LoggingSystem loggingSystem, LoggerGroups groups) {
ObjectProvider<LoggingGroups> loggingGroupsObjectProvider) { groups.putAll(getLoggerGroups());
return new LoggersEndpoint(loggingSystem, loggingGroupsObjectProvider.getIfAvailable()); return new LoggersEndpoint(loggingSystem, groups);
}
private Map<String, List<String>> getLoggerGroups() {
return Collections.singletonMap("test", Arrays.asList("test.member1", "test.member2"));
} }
} }
......
...@@ -31,7 +31,8 @@ import org.springframework.boot.actuate.endpoint.annotation.Selector; ...@@ -31,7 +31,8 @@ import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggerConfiguration; import org.springframework.boot.logging.LoggerConfiguration;
import org.springframework.boot.logging.LoggingGroups; import org.springframework.boot.logging.LoggerGroup;
import org.springframework.boot.logging.LoggerGroups;
import org.springframework.boot.logging.LoggingSystem; import org.springframework.boot.logging.LoggingSystem;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
...@@ -49,17 +50,18 @@ public class LoggersEndpoint { ...@@ -49,17 +50,18 @@ public class LoggersEndpoint {
private final LoggingSystem loggingSystem; private final LoggingSystem loggingSystem;
private final LoggingGroups loggingGroups; private final LoggerGroups loggerGroups;
/** /**
* Create a new {@link LoggersEndpoint} instance. * Create a new {@link LoggersEndpoint} instance.
* @param loggingSystem the logging system to expose * @param loggingSystem the logging system to expose
* @param loggingGroups the logging group to expose if it exists * @param loggerGroups the logger group to expose
*/ */
public LoggersEndpoint(LoggingSystem loggingSystem, LoggingGroups loggingGroups) { public LoggersEndpoint(LoggingSystem loggingSystem, LoggerGroups loggerGroups) {
Assert.notNull(loggingSystem, "LoggingSystem must not be null"); Assert.notNull(loggingSystem, "LoggingSystem must not be null");
Assert.notNull(loggerGroups, "LoggerGroups must not be null");
this.loggingSystem = loggingSystem; this.loggingSystem = loggingSystem;
this.loggingGroups = loggingGroups; this.loggerGroups = loggerGroups;
} }
@ReadOperation @ReadOperation
...@@ -71,20 +73,23 @@ public class LoggersEndpoint { ...@@ -71,20 +73,23 @@ public class LoggersEndpoint {
Map<String, Object> result = new LinkedHashMap<>(); Map<String, Object> result = new LinkedHashMap<>();
result.put("levels", getLevels()); result.put("levels", getLevels());
result.put("loggers", getLoggers(configurations)); result.put("loggers", getLoggers(configurations));
if (this.loggingGroups != null && this.loggingGroups.getLoggerGroupNames() != null) { result.put("groups", getGroups());
Set<String> groups = this.loggingGroups.getLoggerGroupNames();
result.put("groups", getLoggerGroups(groups));
}
return result; return result;
} }
private Map<String, LoggerLevels> getGroups() {
Map<String, LoggerLevels> groups = new LinkedHashMap<>();
this.loggerGroups.forEach((group) -> groups.put(group.getName(),
new GroupLoggerLevels(group.getConfiguredLevel(), group.getMembers())));
return groups;
}
@ReadOperation @ReadOperation
public LoggerLevels loggerLevels(@Selector String name) { public LoggerLevels loggerLevels(@Selector String name) {
Assert.notNull(name, "Name must not be null"); Assert.notNull(name, "Name must not be null");
if (this.loggingGroups != null && this.loggingGroups.isGroup(name)) { LoggerGroup group = this.loggerGroups.get(name);
List<String> members = this.loggingGroups.getLoggerGroup(name); if (group != null) {
LogLevel groupConfiguredLevel = this.loggingGroups.getLoggerGroupConfiguredLevel(name); return new GroupLoggerLevels(group.getConfiguredLevel(), group.getMembers());
return new GroupLoggerLevels(groupConfiguredLevel, members);
} }
LoggerConfiguration configuration = this.loggingSystem.getLoggerConfiguration(name); LoggerConfiguration configuration = this.loggingSystem.getLoggerConfiguration(name);
return (configuration != null) ? new SingleLoggerLevels(configuration) : null; return (configuration != null) ? new SingleLoggerLevels(configuration) : null;
...@@ -93,8 +98,9 @@ public class LoggersEndpoint { ...@@ -93,8 +98,9 @@ public class LoggersEndpoint {
@WriteOperation @WriteOperation
public void configureLogLevel(@Selector String name, @Nullable LogLevel configuredLevel) { public void configureLogLevel(@Selector String name, @Nullable LogLevel configuredLevel) {
Assert.notNull(name, "Name must not be empty"); Assert.notNull(name, "Name must not be empty");
if (this.loggingGroups != null && this.loggingGroups.isGroup(name)) { LoggerGroup group = this.loggerGroups.get(name);
this.loggingGroups.setLoggerGroupLevel(name, configuredLevel); if (group != null && group.hasMembers()) {
group.configureLogLevel(configuredLevel, this.loggingSystem::setLogLevel);
return; return;
} }
this.loggingSystem.setLogLevel(name, configuredLevel); this.loggingSystem.setLogLevel(name, configuredLevel);
...@@ -113,16 +119,6 @@ public class LoggersEndpoint { ...@@ -113,16 +119,6 @@ public class LoggersEndpoint {
return loggers; return loggers;
} }
private Map<String, LoggerLevels> getLoggerGroups(Set<String> groups) {
Map<String, LoggerLevels> loggerGroups = new LinkedHashMap<>(groups.size());
for (String name : groups) {
List<String> members = this.loggingGroups.getLoggerGroup(name);
LogLevel groupConfiguredLevel = this.loggingGroups.getLoggerGroupConfiguredLevel(name);
loggerGroups.put(name, new GroupLoggerLevels(groupConfiguredLevel, members));
}
return loggerGroups;
}
/** /**
* Levels configured for a given logger exposed in a JSON friendly way. * Levels configured for a given logger exposed in a JSON friendly way.
*/ */
...@@ -134,13 +130,12 @@ public class LoggersEndpoint { ...@@ -134,13 +130,12 @@ public class LoggersEndpoint {
this.configuredLevel = getName(configuredLevel); this.configuredLevel = getName(configuredLevel);
} }
private String getName(LogLevel level) { protected final String getName(LogLevel level) {
return (level != null) ? level.name() : null; return (level != null) ? level.name() : null;
} }
public String getConfiguredLevel() { public String getConfiguredLevel() {
return this.configuredLevel; return this.configuredLevel;
} }
} }
...@@ -166,7 +161,7 @@ public class LoggersEndpoint { ...@@ -166,7 +161,7 @@ public class LoggersEndpoint {
public SingleLoggerLevels(LoggerConfiguration configuration) { public SingleLoggerLevels(LoggerConfiguration configuration) {
super(configuration.getConfiguredLevel()); super(configuration.getConfiguredLevel());
this.effectiveLevel = super.getName(configuration.getEffectiveLevel()); this.effectiveLevel = getName(configuration.getEffectiveLevel());
} }
public String getEffectiveLevel() { public String getEffectiveLevel() {
......
...@@ -18,9 +18,11 @@ package org.springframework.boot.actuate.logging; ...@@ -18,9 +18,11 @@ package org.springframework.boot.actuate.logging;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.logging.LoggersEndpoint.GroupLoggerLevels; import org.springframework.boot.actuate.logging.LoggersEndpoint.GroupLoggerLevels;
...@@ -28,7 +30,7 @@ import org.springframework.boot.actuate.logging.LoggersEndpoint.LoggerLevels; ...@@ -28,7 +30,7 @@ import org.springframework.boot.actuate.logging.LoggersEndpoint.LoggerLevels;
import org.springframework.boot.actuate.logging.LoggersEndpoint.SingleLoggerLevels; import org.springframework.boot.actuate.logging.LoggersEndpoint.SingleLoggerLevels;
import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggerConfiguration; import org.springframework.boot.logging.LoggerConfiguration;
import org.springframework.boot.logging.LoggingGroups; import org.springframework.boot.logging.LoggerGroups;
import org.springframework.boot.logging.LoggingSystem; import org.springframework.boot.logging.LoggingSystem;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
...@@ -42,12 +44,21 @@ import static org.mockito.Mockito.verify; ...@@ -42,12 +44,21 @@ import static org.mockito.Mockito.verify;
* @author Ben Hale * @author Ben Hale
* @author Andy Wilkinson * @author Andy Wilkinson
* @author HaiTao Zhang * @author HaiTao Zhang
* @author Madhura Bhave
*/ */
class LoggersEndpointTests { class LoggersEndpointTests {
private final LoggingSystem loggingSystem = mock(LoggingSystem.class); private final LoggingSystem loggingSystem = mock(LoggingSystem.class);
private final LoggingGroups loggingGroups = mock(LoggingGroups.class); private LoggerGroups loggerGroups;
@BeforeEach
void setup() {
Map<String, List<String>> groups = Collections.singletonMap("test", Collections.singletonList("test.member"));
this.loggerGroups = new LoggerGroups(groups);
this.loggerGroups.get("test").configureLogLevel(LogLevel.DEBUG, (a, b) -> {
});
}
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
...@@ -55,8 +66,7 @@ class LoggersEndpointTests { ...@@ -55,8 +66,7 @@ class LoggersEndpointTests {
given(this.loggingSystem.getLoggerConfigurations()) given(this.loggingSystem.getLoggerConfigurations())
.willReturn(Collections.singletonList(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG))); .willReturn(Collections.singletonList(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG)));
given(this.loggingSystem.getSupportedLogLevels()).willReturn(EnumSet.allOf(LogLevel.class)); given(this.loggingSystem.getSupportedLogLevels()).willReturn(EnumSet.allOf(LogLevel.class));
given(this.loggingGroups.getLoggerGroupNames()).willReturn(null); Map<String, Object> result = new LoggersEndpoint(this.loggingSystem, new LoggerGroups()).loggers();
Map<String, Object> result = new LoggersEndpoint(this.loggingSystem, this.loggingGroups).loggers();
Map<String, LoggerLevels> loggers = (Map<String, LoggerLevels>) result.get("loggers"); Map<String, LoggerLevels> loggers = (Map<String, LoggerLevels>) result.get("loggers");
Set<LogLevel> levels = (Set<LogLevel>) result.get("levels"); Set<LogLevel> levels = (Set<LogLevel>) result.get("levels");
SingleLoggerLevels rootLevels = (SingleLoggerLevels) loggers.get("ROOT"); SingleLoggerLevels rootLevels = (SingleLoggerLevels) loggers.get("ROOT");
...@@ -64,7 +74,8 @@ class LoggersEndpointTests { ...@@ -64,7 +74,8 @@ class LoggersEndpointTests {
assertThat(rootLevels.getEffectiveLevel()).isEqualTo("DEBUG"); assertThat(rootLevels.getEffectiveLevel()).isEqualTo("DEBUG");
assertThat(levels).containsExactly(LogLevel.OFF, LogLevel.FATAL, LogLevel.ERROR, LogLevel.WARN, LogLevel.INFO, assertThat(levels).containsExactly(LogLevel.OFF, LogLevel.FATAL, LogLevel.ERROR, LogLevel.WARN, LogLevel.INFO,
LogLevel.DEBUG, LogLevel.TRACE); LogLevel.DEBUG, LogLevel.TRACE);
assertThat(result.get("groups")).isNull(); Map<String, LoggerGroups> groups = (Map<String, LoggerGroups>) result.get("groups");
assertThat(groups).isEmpty();
} }
@Test @Test
...@@ -73,12 +84,9 @@ class LoggersEndpointTests { ...@@ -73,12 +84,9 @@ class LoggersEndpointTests {
given(this.loggingSystem.getLoggerConfigurations()) given(this.loggingSystem.getLoggerConfigurations())
.willReturn(Collections.singletonList(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG))); .willReturn(Collections.singletonList(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG)));
given(this.loggingSystem.getSupportedLogLevels()).willReturn(EnumSet.allOf(LogLevel.class)); given(this.loggingSystem.getSupportedLogLevels()).willReturn(EnumSet.allOf(LogLevel.class));
given(this.loggingGroups.getLoggerGroup("test")).willReturn(Collections.singletonList("test.member")); Map<String, Object> result = new LoggersEndpoint(this.loggingSystem, this.loggerGroups).loggers();
given(this.loggingGroups.getLoggerGroupNames()).willReturn(Collections.singleton("test")); Map<String, GroupLoggerLevels> loggerGroups = (Map<String, GroupLoggerLevels>) result.get("groups");
given(this.loggingGroups.getLoggerGroupConfiguredLevel("test")).willReturn(LogLevel.DEBUG); GroupLoggerLevels groupLevel = loggerGroups.get("test");
Map<String, Object> result = new LoggersEndpoint(this.loggingSystem, this.loggingGroups).loggers();
Map<String, LoggerLevels> loggerGroups = (Map<String, LoggerLevels>) result.get("groups");
GroupLoggerLevels testLoggerLevel = (GroupLoggerLevels) loggerGroups.get("test");
Map<String, LoggerLevels> loggers = (Map<String, LoggerLevels>) result.get("loggers"); Map<String, LoggerLevels> loggers = (Map<String, LoggerLevels>) result.get("loggers");
Set<LogLevel> levels = (Set<LogLevel>) result.get("levels"); Set<LogLevel> levels = (Set<LogLevel>) result.get("levels");
SingleLoggerLevels rootLevels = (SingleLoggerLevels) loggers.get("ROOT"); SingleLoggerLevels rootLevels = (SingleLoggerLevels) loggers.get("ROOT");
...@@ -87,17 +95,15 @@ class LoggersEndpointTests { ...@@ -87,17 +95,15 @@ class LoggersEndpointTests {
assertThat(levels).containsExactly(LogLevel.OFF, LogLevel.FATAL, LogLevel.ERROR, LogLevel.WARN, LogLevel.INFO, assertThat(levels).containsExactly(LogLevel.OFF, LogLevel.FATAL, LogLevel.ERROR, LogLevel.WARN, LogLevel.INFO,
LogLevel.DEBUG, LogLevel.TRACE); LogLevel.DEBUG, LogLevel.TRACE);
assertThat(loggerGroups).isNotNull(); assertThat(loggerGroups).isNotNull();
assertThat(testLoggerLevel).isNotNull(); assertThat(groupLevel.getConfiguredLevel()).isEqualTo("DEBUG");
assertThat(testLoggerLevel.getConfiguredLevel()).isEqualTo("DEBUG"); assertThat(groupLevel.getMembers()).containsExactly("test.member");
assertThat(testLoggerLevel.getMembers()).isEqualTo(Collections.singletonList("test.member"));
} }
@Test @Test
void loggerLevelsWhenNameSpecifiedShouldReturnLevels() { void loggerLevelsWhenNameSpecifiedShouldReturnLevels() {
given(this.loggingGroups.isGroup("ROOT")).willReturn(false);
given(this.loggingSystem.getLoggerConfiguration("ROOT")) given(this.loggingSystem.getLoggerConfiguration("ROOT"))
.willReturn(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG)); .willReturn(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG));
SingleLoggerLevels levels = (SingleLoggerLevels) new LoggersEndpoint(this.loggingSystem, this.loggingGroups) SingleLoggerLevels levels = (SingleLoggerLevels) new LoggersEndpoint(this.loggingSystem, this.loggerGroups)
.loggerLevels("ROOT"); .loggerLevels("ROOT");
assertThat(levels.getConfiguredLevel()).isNull(); assertThat(levels.getConfiguredLevel()).isNull();
assertThat(levels.getEffectiveLevel()).isEqualTo("DEBUG"); assertThat(levels.getEffectiveLevel()).isEqualTo("DEBUG");
...@@ -105,10 +111,7 @@ class LoggersEndpointTests { ...@@ -105,10 +111,7 @@ class LoggersEndpointTests {
@Test @Test
void groupNameSpecifiedShouldReturnConfiguredLevelAndMembers() { void groupNameSpecifiedShouldReturnConfiguredLevelAndMembers() {
given(this.loggingGroups.isGroup("test")).willReturn(true); GroupLoggerLevels levels = (GroupLoggerLevels) new LoggersEndpoint(this.loggingSystem, this.loggerGroups)
given(this.loggingGroups.getLoggerGroup("test")).willReturn(Collections.singletonList("test.member"));
given(this.loggingGroups.getLoggerGroupConfiguredLevel("test")).willReturn(LogLevel.DEBUG);
GroupLoggerLevels levels = (GroupLoggerLevels) new LoggersEndpoint(this.loggingSystem, this.loggingGroups)
.loggerLevels("test"); .loggerLevels("test");
assertThat(levels.getConfiguredLevel()).isEqualTo("DEBUG"); assertThat(levels.getConfiguredLevel()).isEqualTo("DEBUG");
assertThat(levels.getMembers()).isEqualTo(Collections.singletonList("test.member")); assertThat(levels.getMembers()).isEqualTo(Collections.singletonList("test.member"));
...@@ -116,35 +119,26 @@ class LoggersEndpointTests { ...@@ -116,35 +119,26 @@ class LoggersEndpointTests {
@Test @Test
void configureLogLevelShouldSetLevelOnLoggingSystem() { void configureLogLevelShouldSetLevelOnLoggingSystem() {
given(this.loggingGroups.getLoggerGroup("ROOT")).willReturn(null); new LoggersEndpoint(this.loggingSystem, this.loggerGroups).configureLogLevel("ROOT", LogLevel.DEBUG);
new LoggersEndpoint(this.loggingSystem, this.loggingGroups).configureLogLevel("ROOT", LogLevel.DEBUG);
verify(this.loggingSystem).setLogLevel("ROOT", LogLevel.DEBUG); verify(this.loggingSystem).setLogLevel("ROOT", LogLevel.DEBUG);
} }
@Test @Test
void configureLogLevelWithNullSetsLevelOnLoggingSystemToNull() { void configureLogLevelWithNullSetsLevelOnLoggingSystemToNull() {
given(this.loggingGroups.getLoggerGroup("ROOT")).willReturn(null); new LoggersEndpoint(this.loggingSystem, this.loggerGroups).configureLogLevel("ROOT", null);
new LoggersEndpoint(this.loggingSystem, this.loggingGroups).configureLogLevel("ROOT", null);
verify(this.loggingSystem).setLogLevel("ROOT", null); verify(this.loggingSystem).setLogLevel("ROOT", null);
} }
@Test @Test
void configureLogLevelInLoggerGroupShouldSetLevelOnLoggingSystem() { void configureLogLevelInLoggerGroupShouldSetLevelOnLoggingSystem() {
given(this.loggingGroups.isGroup("test")).willReturn(true); new LoggersEndpoint(this.loggingSystem, this.loggerGroups).configureLogLevel("test", LogLevel.DEBUG);
given(this.loggingGroups.getLoggerGroup("test")).willReturn(Collections.singletonList("test.member")); verify(this.loggingSystem).setLogLevel("test.member", LogLevel.DEBUG);
new LoggersEndpoint(this.loggingSystem, this.loggingGroups).configureLogLevel("test", LogLevel.DEBUG);
verify(this.loggingGroups).setLoggerGroupLevel("test", LogLevel.DEBUG);
} }
@Test @Test
void configureLogLevelWithNullInLoggerGroupShouldSetLevelOnLoggingSystem() { void configureLogLevelWithNullInLoggerGroupShouldSetLevelOnLoggingSystem() {
given(this.loggingGroups.isGroup("test")).willReturn(true); new LoggersEndpoint(this.loggingSystem, this.loggerGroups).configureLogLevel("test", null);
given(this.loggingGroups.getLoggerGroup("test")).willReturn(Collections.singletonList("test.member")); verify(this.loggingSystem).setLogLevel("test.member", null);
new LoggersEndpoint(this.loggingSystem, this.loggingGroups).configureLogLevel("test", null);
verify(this.loggingGroups).setLoggerGroupLevel("test", null);
} }
// @Test
// void
} }
...@@ -16,29 +16,50 @@ ...@@ -16,29 +16,50 @@
package org.springframework.boot.logging; package org.springframework.boot.logging;
import java.util.Arrays; import java.util.ArrayList;
import java.util.Collections;
import org.junit.jupiter.api.Test; import java.util.List;
import java.util.function.BiConsumer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/** /**
* Tests for {@link LoggingGroups} * A single logger group.
* *
* @author HaiTao Zhang * @author Madhura Bhave
* @author Phillip Webb
* @since 2.2.0
*/ */
public class LoggingGroupsTests { public final class LoggerGroup {
private final String name;
private final List<String> members;
private LogLevel configuredLevel;
LoggerGroup(String name, List<String> members) {
this.name = name;
this.members = Collections.unmodifiableList(new ArrayList<>(members));
}
private LoggingSystem loggingSystem = mock(LoggingSystem.class); public String getName() {
return this.name;
}
public List<String> getMembers() {
return this.members;
}
public boolean hasMembers() {
return !this.members.isEmpty();
}
public LogLevel getConfiguredLevel() {
return this.configuredLevel;
}
@Test public void configureLogLevel(LogLevel level, BiConsumer<String, LogLevel> configurer) {
void setLoggerGroupWithTheConfiguredLevelToAllMembers() { this.configuredLevel = level;
LoggingGroups loggingGroups = new LoggingGroups(this.loggingSystem); this.members.forEach((name) -> configurer.accept(name, level));
loggingGroups.setLoggerGroup("test", Arrays.asList("test.member", "test.member2"));
loggingGroups.setLoggerGroupLevel("test", LogLevel.DEBUG);
verify(this.loggingSystem).setLogLevel("test.member2", LogLevel.DEBUG);
verify(this.loggingSystem).setLogLevel("test.member", LogLevel.DEBUG);
} }
} }
/*
* Copyright 2012-2019 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
*
* https://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.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Logger groups configured via the Spring Environment.
*
* @author HaiTao Zhang
* @author Phillip Webb
* @since 2.2.0 #see {@link LoggerGroup}
*/
public final class LoggerGroups implements Iterable<LoggerGroup> {
private final Map<String, LoggerGroup> groups = new ConcurrentHashMap<>();
public LoggerGroups() {
}
public LoggerGroups(Map<String, List<String>> namesAndMembers) {
putAll(namesAndMembers);
}
public void putAll(Map<String, List<String>> namesAndMembers) {
namesAndMembers.forEach(this::put);
}
private void put(String name, List<String> members) {
put(new LoggerGroup(name, members));
}
private void put(LoggerGroup loggerGroup) {
this.groups.put(loggerGroup.getName(), loggerGroup);
}
public LoggerGroup get(String name) {
return this.groups.get(name);
}
@Override
public Iterator<LoggerGroup> iterator() {
return this.groups.values().iterator();
}
}
/*
* Copyright 2012-2019 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
*
* https://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.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.util.Assert;
/**
* Manage logger groups.
*
* @author HaiTao Zhang
* @since 2.2.0
*/
public class LoggingGroups {
private Map<String, LogLevel> loggerGroupConfigurations;
private Map<String, List<String>> loggerGroups;
private LoggingSystem loggingSystem;
public LoggingGroups(LoggingSystem loggingSystem) {
this.loggerGroupConfigurations = new ConcurrentHashMap<>();
this.loggerGroups = new ConcurrentHashMap<>();
this.loggingSystem = loggingSystem;
}
/**
* Associate a name to a list of logger's name to create a logger group.
* @param groupName name of the logger group
* @param members list of the members names
*/
public void setLoggerGroup(String groupName, List<String> members) {
Assert.notNull(groupName, "Group name can not be null");
Assert.notNull(members, "Members can not be null");
this.loggerGroups.put(groupName, members);
}
/**
* Set the logging level for a given logger group.
* @param groupName the name of the group to set
* @param level the log level ({@code null}) can be used to remove any custom level
* for the logger group and use the default configuration instead.
*/
public void setLoggerGroupLevel(String groupName, LogLevel level) {
Assert.notNull(groupName, "Group name can not be null");
List<String> members = this.loggerGroups.get(groupName);
members.forEach((member) -> this.loggingSystem
.setLogLevel(member.equalsIgnoreCase(LoggingSystem.ROOT_LOGGER_NAME) ? null : member, level));
this.loggerGroupConfigurations.put(groupName, level);
}
/**
* Checks whether a groupName is associated to a logger group.
* @param groupName name of the logger group
* @return a boolean stating true when groupName is associated with a group of loggers
*/
public boolean isGroup(String groupName) {
Assert.notNull(groupName, "Group name can not be null");
return this.loggerGroups.containsKey(groupName);
}
/**
* Get the all registered logger groups.
* @return a Set of the names of the logger groups
*/
public Set<String> getLoggerGroupNames() {
synchronized (this) {
return this.loggerGroups.isEmpty() ? null : Collections.unmodifiableSet(this.loggerGroups.keySet());
}
}
/**
* Get a logger group's members.
* @param groupName name of the logger group
* @return list of the members names associated with this group
*/
public List<String> getLoggerGroup(String groupName) {
Assert.notNull(groupName, "Group name can not be null");
return Collections.unmodifiableList(this.loggerGroups.get(groupName));
}
/**
* Get a logger group's configured level.
* @param groupName name of the logger group
* @return the logger groups configured level
*/
public LogLevel getLoggerGroupConfiguredLevel(String groupName) {
Assert.notNull(groupName, "Group name can not be null");
return this.loggerGroupConfigurations.get(groupName);
}
}
...@@ -50,6 +50,7 @@ import org.springframework.boot.logging.AbstractLoggingSystem; ...@@ -50,6 +50,7 @@ import org.springframework.boot.logging.AbstractLoggingSystem;
import org.springframework.boot.logging.LogFile; import org.springframework.boot.logging.LogFile;
import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggerConfiguration; import org.springframework.boot.logging.LoggerConfiguration;
import org.springframework.boot.logging.LoggerGroups;
import org.springframework.boot.logging.LoggingInitializationContext; import org.springframework.boot.logging.LoggingInitializationContext;
import org.springframework.boot.logging.LoggingSystem; import org.springframework.boot.logging.LoggingSystem;
import org.springframework.boot.logging.LoggingSystemProperties; import org.springframework.boot.logging.LoggingSystemProperties;
...@@ -284,6 +285,8 @@ class LoggingApplicationListenerTests { ...@@ -284,6 +285,8 @@ class LoggingApplicationListenerTests {
this.loggerContext.getLogger("org.hibernate.SQL").debug("testdebugsqlgroup"); this.loggerContext.getLogger("org.hibernate.SQL").debug("testdebugsqlgroup");
assertThat(this.output).contains("testdebugwebgroup"); assertThat(this.output).contains("testdebugwebgroup");
assertThat(this.output).contains("testdebugsqlgroup"); assertThat(this.output).contains("testdebugsqlgroup");
LoggerGroups loggerGroups = (LoggerGroups) ReflectionTestUtils.getField(this.initializer, "loggerGroups");
assertThat(loggerGroups.get("web").getConfiguredLevel()).isEqualTo(LogLevel.DEBUG);
} }
@Test @Test
......
/*
* Copyright 2012-2019 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
*
* https://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.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link LoggerGroups}
*
* @author HaiTao Zhang
* @author Madhura Bhave
*/
class LoggerGroupsTests {
private LoggingSystem loggingSystem = mock(LoggingSystem.class);
@Test
void putAllShouldAddLoggerGroups() {
Map<String, List<String>> groups = Collections.singletonMap("test",
Arrays.asList("test.member", "test.member2"));
LoggerGroups loggerGroups = new LoggerGroups();
loggerGroups.putAll(groups);
LoggerGroup group = loggerGroups.get("test");
assertThat(group.getMembers()).containsExactly("test.member", "test.member2");
}
@Test
void iteratorShouldReturnLoggerGroups() {
LoggerGroups groups = createLoggerGroups();
assertThat(groups).hasSize(3);
assertThat(groups).extracting("name").containsExactlyInAnyOrder("test0", "test1", "test2");
}
private LoggerGroups createLoggerGroups() {
Map<String, List<String>> groups = new LinkedHashMap<>();
groups.put("test0", Arrays.asList("test0.member", "test0.member2"));
groups.put("test1", Arrays.asList("test1.member", "test1.member2"));
groups.put("test2", Arrays.asList("test2.member", "test2.member2"));
return new LoggerGroups(groups);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment