Commit b9047c22 authored by HaiTao Zhang's avatar HaiTao Zhang Committed by Madhura Bhave

Add support for configuring logging groups via endpoint

See gh-17515
parent 8197feac
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package org.springframework.boot.actuate.autoconfigure.logging; package org.springframework.boot.actuate.autoconfigure.logging;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.actuate.logging.LoggersEndpoint; import org.springframework.boot.actuate.logging.LoggersEndpoint;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
...@@ -24,6 +25,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionOutcome; ...@@ -24,6 +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.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;
...@@ -45,8 +47,9 @@ public class LoggersEndpointAutoConfiguration { ...@@ -45,8 +47,9 @@ public class LoggersEndpointAutoConfiguration {
@ConditionalOnBean(LoggingSystem.class) @ConditionalOnBean(LoggingSystem.class)
@Conditional(OnEnabledLoggingSystemCondition.class) @Conditional(OnEnabledLoggingSystemCondition.class)
@ConditionalOnMissingBean @ConditionalOnMissingBean
public LoggersEndpoint loggersEndpoint(LoggingSystem loggingSystem) { public LoggersEndpoint loggersEndpoint(LoggingSystem loggingSystem,
return new LoggersEndpoint(loggingSystem); ObjectProvider<LoggingGroups> loggingGroupsObjectProvider) {
return new LoggersEndpoint(loggingSystem, loggingGroupsObjectProvider.getIfAvailable());
} }
static class OnEnabledLoggingSystemCondition extends SpringBootCondition { static class OnEnabledLoggingSystemCondition extends SpringBootCondition {
......
...@@ -17,14 +17,17 @@ ...@@ -17,14 +17,17 @@
package org.springframework.boot.actuate.autoconfigure.endpoint.web.documentation; package org.springframework.boot.actuate.autoconfigure.endpoint.web.documentation;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.ObjectProvider;
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.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;
...@@ -54,32 +57,60 @@ class LoggersEndpointDocumentationTests extends MockMvcEndpointDocumentationTest ...@@ -54,32 +57,60 @@ 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(
fieldWithPath("configuredLevel").description("Configured level of the logger group"),
fieldWithPath("members").description("Loggers that are part of this group").optional());
@MockBean @MockBean
private LoggingSystem loggingSystem; private LoggingSystem loggingSystem;
@MockBean
private ObjectProvider<LoggingGroups> loggingGroupsObjectProvider;
@MockBean
LoggingGroups loggingGroups;
@Test @Test
void allLoggers() throws Exception { void allLoggers() throws Exception {
given(this.loggingSystem.getSupportedLogLevels()).willReturn(EnumSet.allOf(LogLevel.class)); given(this.loggingSystem.getSupportedLogLevels()).willReturn(EnumSet.allOf(LogLevel.class));
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."),
fieldWithPath("loggers").description("Loggers keyed by name.")) fieldWithPath("loggers").description("Loggers keyed by name."),
.andWithPrefix("loggers.*.", levelFields))); fieldWithPath("groups").description("Logger groups keyed by name"))
.andWithPrefix("loggers.*.", levelFields)
.andWithPrefix("groups.*.", groupLevelFields)));
} }
@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())
.andDo(MockMvcRestDocumentation.document("loggers/single", responseFields(levelFields))); .andDo(MockMvcRestDocumentation.document("loggers/single", responseFields(levelFields)));
} }
@Test
void loggerGroups() throws Exception {
given(this.loggingGroupsObjectProvider.getIfAvailable()).willReturn(this.loggingGroups);
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)));
}
@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))
...@@ -89,8 +120,24 @@ class LoggersEndpointDocumentationTests extends MockMvcEndpointDocumentationTest ...@@ -89,8 +120,24 @@ class LoggersEndpointDocumentationTests extends MockMvcEndpointDocumentationTest
verify(this.loggingSystem).setLogLevel("com.example", LogLevel.DEBUG); verify(this.loggingSystem).setLogLevel("com.example", LogLevel.DEBUG);
} }
@Test
void setLogLevelOfLoggerGroup() throws Exception {
given(this.loggingGroupsObjectProvider.getIfAvailable()).willReturn(this.loggingGroups);
given(this.loggingGroups.isGroup("com.example")).willReturn(true);
this.mockMvc
.perform(post("/actuator/loggers/com.example")
.content("{\"configuredLevel\":\"debug\"}").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent()).andDo(
MockMvcRestDocumentation.document("loggers/setGroup",
requestFields(fieldWithPath("configuredLevel").description(
"Level for the logger group. May be omitted to clear the level of the loggers.")
.optional())));
verify(this.loggingGroups).setLoggerGroupLevel("com.example", LogLevel.DEBUG);
}
@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"));
...@@ -102,8 +149,9 @@ class LoggersEndpointDocumentationTests extends MockMvcEndpointDocumentationTest ...@@ -102,8 +149,9 @@ class LoggersEndpointDocumentationTests extends MockMvcEndpointDocumentationTest
static class TestConfiguration { static class TestConfiguration {
@Bean @Bean
LoggersEndpoint endpoint(LoggingSystem loggingSystem) { LoggersEndpoint endpoint(LoggingSystem loggingSystem,
return new LoggersEndpoint(loggingSystem); ObjectProvider<LoggingGroups> loggingGroupsObjectProvider) {
return new LoggersEndpoint(loggingSystem, loggingGroupsObjectProvider.getIfAvailable());
} }
} }
......
...@@ -19,6 +19,7 @@ package org.springframework.boot.actuate.logging; ...@@ -19,6 +19,7 @@ package org.springframework.boot.actuate.logging;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.NavigableSet; import java.util.NavigableSet;
import java.util.Set; import java.util.Set;
...@@ -30,6 +31,7 @@ import org.springframework.boot.actuate.endpoint.annotation.Selector; ...@@ -30,6 +31,7 @@ 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.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;
...@@ -39,6 +41,7 @@ import org.springframework.util.Assert; ...@@ -39,6 +41,7 @@ import org.springframework.util.Assert;
* *
* @author Ben Hale * @author Ben Hale
* @author Phillip Webb * @author Phillip Webb
* @author HaiTao Zhang
* @since 2.0.0 * @since 2.0.0
*/ */
@Endpoint(id = "loggers") @Endpoint(id = "loggers")
...@@ -46,13 +49,17 @@ public class LoggersEndpoint { ...@@ -46,13 +49,17 @@ public class LoggersEndpoint {
private final LoggingSystem loggingSystem; private final LoggingSystem loggingSystem;
private final LoggingGroups loggingGroups;
/** /**
* 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
*/ */
public LoggersEndpoint(LoggingSystem loggingSystem) { public LoggersEndpoint(LoggingSystem loggingSystem, LoggingGroups loggingGroups) {
Assert.notNull(loggingSystem, "LoggingSystem must not be null"); Assert.notNull(loggingSystem, "LoggingSystem must not be null");
this.loggingSystem = loggingSystem; this.loggingSystem = loggingSystem;
this.loggingGroups = loggingGroups;
} }
@ReadOperation @ReadOperation
...@@ -64,19 +71,32 @@ public class LoggersEndpoint { ...@@ -64,19 +71,32 @@ 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) {
Set<String> groups = this.loggingGroups.getLoggerGroupNames();
result.put("groups", getLoggerGroups(groups));
}
return result; return result;
} }
@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)) {
List<String> members = this.loggingGroups.getLoggerGroup(name);
LogLevel groupConfiguredLevel = this.loggingGroups.getLoggerGroupConfiguredLevel(name);
return new GroupLoggerLevels(groupConfiguredLevel, members);
}
LoggerConfiguration configuration = this.loggingSystem.getLoggerConfiguration(name); LoggerConfiguration configuration = this.loggingSystem.getLoggerConfiguration(name);
return (configuration != null) ? new LoggerLevels(configuration) : null; return (configuration != null) ? new SingleLoggerLevels(configuration) : null;
} }
@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)) {
this.loggingGroups.setLoggerGroupLevel(name, configuredLevel);
return;
}
this.loggingSystem.setLogLevel(name, configuredLevel); this.loggingSystem.setLogLevel(name, configuredLevel);
} }
...@@ -88,11 +108,21 @@ public class LoggersEndpoint { ...@@ -88,11 +108,21 @@ public class LoggersEndpoint {
private Map<String, LoggerLevels> getLoggers(Collection<LoggerConfiguration> configurations) { private Map<String, LoggerLevels> getLoggers(Collection<LoggerConfiguration> configurations) {
Map<String, LoggerLevels> loggers = new LinkedHashMap<>(configurations.size()); Map<String, LoggerLevels> loggers = new LinkedHashMap<>(configurations.size());
for (LoggerConfiguration configuration : configurations) { for (LoggerConfiguration configuration : configurations) {
loggers.put(configuration.getName(), new LoggerLevels(configuration)); loggers.put(configuration.getName(), new SingleLoggerLevels(configuration));
} }
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.
*/ */
...@@ -100,11 +130,8 @@ public class LoggersEndpoint { ...@@ -100,11 +130,8 @@ public class LoggersEndpoint {
private String configuredLevel; private String configuredLevel;
private String effectiveLevel; public LoggerLevels(LogLevel configuredLevel) {
this.configuredLevel = getName(configuredLevel);
public LoggerLevels(LoggerConfiguration configuration) {
this.configuredLevel = getName(configuration.getConfiguredLevel());
this.effectiveLevel = getName(configuration.getEffectiveLevel());
} }
private String getName(LogLevel level) { private String getName(LogLevel level) {
...@@ -113,6 +140,33 @@ public class LoggersEndpoint { ...@@ -113,6 +140,33 @@ public class LoggersEndpoint {
public String getConfiguredLevel() { public String getConfiguredLevel() {
return this.configuredLevel; return this.configuredLevel;
}
}
public static class GroupLoggerLevels extends LoggerLevels {
private List<String> members;
public GroupLoggerLevels(LogLevel configuredLevel, List<String> members) {
super(configuredLevel);
this.members = members;
}
public List<String> getMembers() {
return this.members;
}
}
public static class SingleLoggerLevels extends LoggerLevels {
private String effectiveLevel;
public SingleLoggerLevels(LoggerConfiguration configuration) {
super(configuration.getConfiguredLevel());
this.effectiveLevel = super.getName(configuration.getEffectiveLevel());
} }
public String getEffectiveLevel() { public String getEffectiveLevel() {
......
...@@ -23,9 +23,12 @@ import java.util.Set; ...@@ -23,9 +23,12 @@ import java.util.Set;
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.LoggerLevels; import org.springframework.boot.actuate.logging.LoggersEndpoint.LoggerLevels;
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.LoggingSystem; import org.springframework.boot.logging.LoggingSystem;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
...@@ -38,46 +41,110 @@ import static org.mockito.Mockito.verify; ...@@ -38,46 +41,110 @@ import static org.mockito.Mockito.verify;
* *
* @author Ben Hale * @author Ben Hale
* @author Andy Wilkinson * @author Andy Wilkinson
* @author HaiTao Zhang
*/ */
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);
@Test
@SuppressWarnings("unchecked")
void loggersShouldReturnLoggerConfigurationsWithNoLoggerGroups() {
given(this.loggingSystem.getLoggerConfigurations())
.willReturn(Collections.singletonList(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG)));
given(this.loggingSystem.getSupportedLogLevels()).willReturn(EnumSet.allOf(LogLevel.class));
given(this.loggingGroups.getLoggerGroupNames()).willReturn(null);
Map<String, Object> result = new LoggersEndpoint(this.loggingSystem, this.loggingGroups).loggers();
Map<String, LoggerLevels> loggers = (Map<String, LoggerLevels>) result.get("loggers");
Set<LogLevel> levels = (Set<LogLevel>) result.get("levels");
SingleLoggerLevels rootLevels = (SingleLoggerLevels) loggers.get("ROOT");
assertThat(rootLevels.getConfiguredLevel()).isNull();
assertThat(rootLevels.getEffectiveLevel()).isEqualTo("DEBUG");
assertThat(levels).containsExactly(LogLevel.OFF, LogLevel.FATAL, LogLevel.ERROR, LogLevel.WARN, LogLevel.INFO,
LogLevel.DEBUG, LogLevel.TRACE);
assertThat(result.get("groups")).isNull();
}
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
void loggersShouldReturnLoggerConfigurations() { void loggersShouldReturnLoggerConfigurationsWithLoggerGroups() {
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));
Map<String, Object> result = new LoggersEndpoint(this.loggingSystem).loggers(); given(this.loggingGroups.getLoggerGroup("test")).willReturn(Collections.singletonList("test.member"));
given(this.loggingGroups.getLoggerGroupNames()).willReturn(Collections.singleton("test"));
given(this.loggingGroups.getLoggerGroupConfiguredLevel("test")).willReturn(LogLevel.DEBUG);
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");
LoggerLevels rootLevels = loggers.get("ROOT"); SingleLoggerLevels rootLevels = (SingleLoggerLevels) loggers.get("ROOT");
assertThat(rootLevels.getConfiguredLevel()).isNull(); assertThat(rootLevels.getConfiguredLevel()).isNull();
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(loggerGroups).isNotNull();
assertThat(testLoggerLevel).isNotNull();
assertThat(testLoggerLevel.getConfiguredLevel()).isEqualTo("DEBUG");
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));
LoggerLevels levels = new LoggersEndpoint(this.loggingSystem).loggerLevels("ROOT"); SingleLoggerLevels levels = (SingleLoggerLevels) new LoggersEndpoint(this.loggingSystem, this.loggingGroups)
.loggerLevels("ROOT");
assertThat(levels.getConfiguredLevel()).isNull(); assertThat(levels.getConfiguredLevel()).isNull();
assertThat(levels.getEffectiveLevel()).isEqualTo("DEBUG"); assertThat(levels.getEffectiveLevel()).isEqualTo("DEBUG");
} }
@Test
void groupNameSpecifiedShouldReturnConfiguredLevelAndMembers() {
given(this.loggingGroups.isGroup("test")).willReturn(true);
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");
assertThat(levels.getConfiguredLevel()).isEqualTo("DEBUG");
assertThat(levels.getMembers()).isEqualTo(Collections.singletonList("test.member"));
}
@Test @Test
void configureLogLevelShouldSetLevelOnLoggingSystem() { void configureLogLevelShouldSetLevelOnLoggingSystem() {
new LoggersEndpoint(this.loggingSystem).configureLogLevel("ROOT", LogLevel.DEBUG); given(this.loggingGroups.getLoggerGroup("ROOT")).willReturn(null);
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() {
new LoggersEndpoint(this.loggingSystem).configureLogLevel("ROOT", null); given(this.loggingGroups.getLoggerGroup("ROOT")).willReturn(null);
new LoggersEndpoint(this.loggingSystem, this.loggingGroups).configureLogLevel("ROOT", null);
verify(this.loggingSystem).setLogLevel("ROOT", null); verify(this.loggingSystem).setLogLevel("ROOT", null);
} }
@Test
void configureLogLevelInLoggerGroupShouldSetLevelOnLoggingSystem() {
given(this.loggingGroups.isGroup("test")).willReturn(true);
given(this.loggingGroups.getLoggerGroup("test")).willReturn(Collections.singletonList("test.member"));
new LoggersEndpoint(this.loggingSystem, this.loggingGroups).configureLogLevel("test", LogLevel.DEBUG);
verify(this.loggingGroups).setLoggerGroupLevel("test", LogLevel.DEBUG);
}
@Test
void configureLogLevelWithNullInLoggerGroupShouldSetLevelOnLoggingSystem() {
given(this.loggingGroups.isGroup("test")).willReturn(true);
given(this.loggingGroups.getLoggerGroup("test")).willReturn(Collections.singletonList("test.member"));
new LoggersEndpoint(this.loggingSystem, this.loggingGroups).configureLogLevel("test", null);
verify(this.loggingGroups).setLoggerGroupLevel("test", null);
}
// @Test
// void
} }
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package org.springframework.boot.context.logging; package org.springframework.boot.context.logging;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
...@@ -36,6 +37,7 @@ import org.springframework.boot.context.properties.bind.Binder; ...@@ -36,6 +37,7 @@ import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
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.LoggingGroups;
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;
...@@ -126,6 +128,11 @@ public class LoggingApplicationListener implements GenericApplicationListener { ...@@ -126,6 +128,11 @@ public class LoggingApplicationListener implements GenericApplicationListener {
*/ */
public static final String LOGFILE_BEAN_NAME = "springBootLogFile"; public static final String LOGFILE_BEAN_NAME = "springBootLogFile";
/**
* The name of the{@link LoggingGroups} bean.
*/
public static final String LOGGING_GROUPS_BEAN_NAME = "springBootLoggingGroups";
private static final Map<String, List<String>> DEFAULT_GROUP_LOGGERS; private static final Map<String, List<String>> DEFAULT_GROUP_LOGGERS;
static { static {
MultiValueMap<String, String> loggers = new LinkedMultiValueMap<>(); MultiValueMap<String, String> loggers = new LinkedMultiValueMap<>();
...@@ -166,6 +173,8 @@ public class LoggingApplicationListener implements GenericApplicationListener { ...@@ -166,6 +173,8 @@ public class LoggingApplicationListener implements GenericApplicationListener {
private LoggingSystem loggingSystem; private LoggingSystem loggingSystem;
private LoggingGroups loggingGroups;
private LogFile logFile; private LogFile logFile;
private int order = DEFAULT_ORDER; private int order = DEFAULT_ORDER;
...@@ -235,6 +244,9 @@ public class LoggingApplicationListener implements GenericApplicationListener { ...@@ -235,6 +244,9 @@ public class LoggingApplicationListener implements GenericApplicationListener {
if (this.logFile != null && !beanFactory.containsBean(LOGFILE_BEAN_NAME)) { if (this.logFile != null && !beanFactory.containsBean(LOGFILE_BEAN_NAME)) {
beanFactory.registerSingleton(LOGFILE_BEAN_NAME, this.logFile); beanFactory.registerSingleton(LOGFILE_BEAN_NAME, this.logFile);
} }
if (this.loggingGroups != null && !beanFactory.containsBean(LOGGING_GROUPS_BEAN_NAME)) {
beanFactory.registerSingleton(LOGGING_GROUPS_BEAN_NAME, this.loggingGroups);
}
} }
private void onContextClosedEvent() { private void onContextClosedEvent() {
...@@ -257,6 +269,7 @@ public class LoggingApplicationListener implements GenericApplicationListener { ...@@ -257,6 +269,7 @@ public class LoggingApplicationListener implements GenericApplicationListener {
*/ */
protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) { protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {
new LoggingSystemProperties(environment).apply(); new LoggingSystemProperties(environment).apply();
this.loggingGroups = new LoggingGroups(this.loggingSystem);
this.logFile = LogFile.get(environment); this.logFile = LogFile.get(environment);
if (this.logFile != null) { if (this.logFile != null) {
this.logFile.applyToSystemProperties(); this.logFile.applyToSystemProperties();
...@@ -325,7 +338,8 @@ public class LoggingApplicationListener implements GenericApplicationListener { ...@@ -325,7 +338,8 @@ public class LoggingApplicationListener implements GenericApplicationListener {
system.setLogLevel(logger, level); system.setLogLevel(logger, level);
return; return;
} }
groupLoggers.forEach((groupLogger) -> system.setLogLevel(groupLogger, level)); this.loggingGroups.setLoggerGroup(logger, groupLoggers);
this.loggingGroups.setLoggerGroupLevel(logger, level);
} }
protected void setLogLevels(LoggingSystem system, Environment environment) { protected void setLogLevels(LoggingSystem system, Environment environment) {
...@@ -342,7 +356,7 @@ public class LoggingApplicationListener implements GenericApplicationListener { ...@@ -342,7 +356,7 @@ public class LoggingApplicationListener implements GenericApplicationListener {
setLogLevel(system, name, level); setLogLevel(system, name, level);
} }
else { else {
setLogLevel(system, groupedNames, level); setLogLevel(groupedNames, level, name);
} }
}); });
} }
...@@ -353,9 +367,13 @@ public class LoggingApplicationListener implements GenericApplicationListener { ...@@ -353,9 +367,13 @@ public class LoggingApplicationListener implements GenericApplicationListener {
return groups; return groups;
} }
private void setLogLevel(LoggingSystem system, String[] names, LogLevel level) { private void setLogLevel(String[] names, LogLevel level, String groupName) {
for (String name : names) { try {
setLogLevel(system, name, level); this.loggingGroups.setLoggerGroup(groupName, Arrays.asList(names));
this.loggingGroups.setLoggerGroupLevel(groupName, level);
}
catch (RuntimeException ex) {
this.logger.error("Cannot set level '" + level + "' for '" + groupName + "'");
} }
} }
......
/*
* 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);
}
}
/*
* 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 org.junit.jupiter.api.Test;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link LoggingGroups}
*
* @author HaiTao Zhang
*/
public class LoggingGroupsTests {
private LoggingSystem loggingSystem = mock(LoggingSystem.class);
@Test
void setLoggerGroupWithTheConfiguredLevelToAllMembers() {
LoggingGroups loggingGroups = new LoggingGroups(this.loggingSystem);
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);
}
}
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