Skip to content

Commit b32887b

Browse files
committed
Add support for logging groups
Provide a way for users to quickly group related loggers together for easier configuration. The `loggers.group` property can be used to define a group that can then be configured in the usual `loggers.level.<group>` way. Additionally, provide pre-defined groups for `web` and `sql. Closes gh-14421
1 parent 3ed9730 commit b32887b

File tree

4 files changed

+136
-16
lines changed

4 files changed

+136
-16
lines changed

spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1731,6 +1731,46 @@ The following example shows potential logging settings in `application.propertie
17311731

17321732

17331733

1734+
[[boot-features-custom-log-groups]]
1735+
=== Log Groups
1736+
It's often useful to be able to group related loggers together so that they can all be
1737+
configured at the same time. For example, you might commonly change the logging levels for
1738+
_all_ Tomcat related loggers, but you can't easily remember to top level packages.
1739+
1740+
To help with this, Spring Boot allows you do define logging groups in your Spring
1741+
`Environment`. For example, here's how you could define a "`tomcat`" group by adding
1742+
it to your `appplication.properties`:
1743+
1744+
[source,properties,indent=0,subs="verbatim,quotes,attributes"]
1745+
----
1746+
logging.group.tomcat=org.apache.catalina, org.apache.coyote, org.apache.tomcat
1747+
----
1748+
1749+
Once defined, you can change the level for all the loggers in the group with a single
1750+
line:
1751+
1752+
[source,properties,indent=0,subs="verbatim,quotes,attributes"]
1753+
----
1754+
logging.level.tomcat=TRACE
1755+
----
1756+
1757+
Spring Boot includes the following pre-defined logging groups that can be used
1758+
out-of-the-box:
1759+
1760+
[cols="1,4"]
1761+
|===
1762+
|Name |Loggers
1763+
1764+
|web
1765+
|`org.springframework.core.codec`, `org.springframework.http`, `org.springframework.web`
1766+
1767+
|sql
1768+
|`org.springframework.jdbc.core`, `org.hibernate.SQL`
1769+
1770+
|===
1771+
1772+
1773+
17341774
[[boot-features-custom-log-configuration]]
17351775
=== Custom Log Configuration
17361776
The various logging systems can be activated by including the appropriate libraries on

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/logging/LoggingApplicationListener.java

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.context.logging;
1818

1919
import java.util.Collections;
20+
import java.util.LinkedHashMap;
2021
import java.util.List;
2122
import java.util.Locale;
2223
import java.util.Map;
@@ -49,14 +50,16 @@
4950
import org.springframework.core.env.Environment;
5051
import org.springframework.util.LinkedMultiValueMap;
5152
import org.springframework.util.MultiValueMap;
53+
import org.springframework.util.ObjectUtils;
5254
import org.springframework.util.ResourceUtils;
5355
import org.springframework.util.StringUtils;
5456

5557
/**
5658
* An {@link ApplicationListener} that configures the {@link LoggingSystem}. If the
5759
* environment contains a {@code logging.config} property it will be used to bootstrap the
5860
* logging system, otherwise a default configuration is used. Regardless, logging levels
59-
* will be customized if the environment contains {@code logging.level.*} entries.
61+
* will be customized if the environment contains {@code logging.level.*} entries and
62+
* logging groups can be defined with {@code logging.group} .
6063
* <p>
6164
* Debug and trace logging for Spring, Tomcat, Jetty and Hibernate will be enabled when
6265
* the environment contains {@code debug} or {@code trace} properties that aren't set to
@@ -88,6 +91,9 @@ public class LoggingApplicationListener implements GenericApplicationListener {
8891
private static final Bindable<Map<String, String>> STRING_STRING_MAP = Bindable
8992
.mapOf(String.class, String.class);
9093

94+
private static final Bindable<Map<String, String[]>> STRING_STRINGS_MAP = Bindable
95+
.mapOf(String.class, String[].class);
96+
9197
/**
9298
* The default order for the LoggingApplicationListener.
9399
*/
@@ -111,19 +117,30 @@ public class LoggingApplicationListener implements GenericApplicationListener {
111117
*/
112118
public static final String LOGGING_SYSTEM_BEAN_NAME = "springBootLoggingSystem";
113119

114-
private static final MultiValueMap<LogLevel, String> LOG_LEVEL_LOGGERS;
120+
private static final Map<String, List<String>> DEFAULT_GROUP_LOGGERS;
121+
static {
122+
MultiValueMap<String, String> loggers = new LinkedMultiValueMap<>();
123+
loggers.add("web", "org.springframework.core.codec");
124+
loggers.add("web", "org.springframework.http");
125+
loggers.add("web", "org.springframework.web");
126+
loggers.add("sql", "org.springframework.jdbc.core");
127+
loggers.add("sql", "org.hibernate.SQL");
128+
DEFAULT_GROUP_LOGGERS = Collections.unmodifiableMap(loggers);
129+
}
115130

116-
private static AtomicBoolean shutdownHookRegistered = new AtomicBoolean(false);
131+
private static final Map<LogLevel, List<String>> LOG_LEVEL_LOGGERS;
117132

118133
static {
119-
LOG_LEVEL_LOGGERS = new LinkedMultiValueMap<>();
120-
LOG_LEVEL_LOGGERS.add(LogLevel.DEBUG, "org.springframework.boot");
121-
LOG_LEVEL_LOGGERS.add(LogLevel.DEBUG, "org.hibernate.SQL");
122-
LOG_LEVEL_LOGGERS.add(LogLevel.TRACE, "org.springframework");
123-
LOG_LEVEL_LOGGERS.add(LogLevel.TRACE, "org.apache.tomcat");
124-
LOG_LEVEL_LOGGERS.add(LogLevel.TRACE, "org.apache.catalina");
125-
LOG_LEVEL_LOGGERS.add(LogLevel.TRACE, "org.eclipse.jetty");
126-
LOG_LEVEL_LOGGERS.add(LogLevel.TRACE, "org.hibernate.tool.hbm2ddl");
134+
MultiValueMap<LogLevel, String> loggers = new LinkedMultiValueMap<>();
135+
loggers.add(LogLevel.DEBUG, "sql");
136+
loggers.add(LogLevel.DEBUG, "web");
137+
loggers.add(LogLevel.DEBUG, "org.springframework.boot");
138+
loggers.add(LogLevel.TRACE, "org.springframework");
139+
loggers.add(LogLevel.TRACE, "org.apache.tomcat");
140+
loggers.add(LogLevel.TRACE, "org.apache.catalina");
141+
loggers.add(LogLevel.TRACE, "org.eclipse.jetty");
142+
loggers.add(LogLevel.TRACE, "org.hibernate.tool.hbm2ddl");
143+
LOG_LEVEL_LOGGERS = Collections.unmodifiableMap(loggers);
127144
}
128145

129146
private static final Class<?>[] EVENT_TYPES = { ApplicationStartingEvent.class,
@@ -133,6 +150,8 @@ public class LoggingApplicationListener implements GenericApplicationListener {
133150
private static final Class<?>[] SOURCE_TYPES = { SpringApplication.class,
134151
ApplicationContext.class };
135152

153+
private static AtomicBoolean shutdownHookRegistered = new AtomicBoolean(false);
154+
136155
private final Log logger = LogFactory.getLog(getClass());
137156

138157
private LoggingSystem loggingSystem;
@@ -304,8 +323,32 @@ protected void setLogLevels(LoggingSystem system, Environment environment) {
304323
return;
305324
}
306325
Binder binder = Binder.get(environment);
307-
binder.bind("logging.level", STRING_STRING_MAP).orElseGet(Collections::emptyMap)
308-
.forEach((name, level) -> setLogLevel(system, name, level));
326+
Map<String, String[]> groups = getGroups(binder);
327+
Map<String, String> levels = binder.bind("logging.level", STRING_STRING_MAP)
328+
.orElseGet(Collections::emptyMap);
329+
levels.forEach((name, level) -> {
330+
String[] groupedNames = groups.get(name);
331+
if (ObjectUtils.isEmpty(groupedNames)) {
332+
setLogLevel(system, name, level);
333+
}
334+
else {
335+
setLogLevel(system, groupedNames, level);
336+
}
337+
});
338+
}
339+
340+
private Map<String, String[]> getGroups(Binder binder) {
341+
Map<String, String[]> groups = new LinkedHashMap<>();
342+
DEFAULT_GROUP_LOGGERS.forEach(
343+
(name, loggers) -> groups.put(name, StringUtils.toStringArray(loggers)));
344+
binder.bind("logging.group", STRING_STRINGS_MAP.withExistingValue(groups));
345+
return groups;
346+
}
347+
348+
private void setLogLevel(LoggingSystem system, String[] names, String level) {
349+
for (String name : names) {
350+
setLogLevel(system, name, level);
351+
}
309352
}
310353

311354
private void setLogLevel(LoggingSystem system, String name, String level) {
@@ -360,8 +403,9 @@ public void setSpringBootLogging(LogLevel springBootLogging) {
360403
}
361404

362405
/**
363-
* Sets if initialization arguments should be parsed for {@literal --debug} and
364-
* {@literal --trace} options. Defaults to {@code true}.
406+
* Sets if initialization arguments should be parsed for {@literal debug} and
407+
* {@literal trace} properties (usually defined from {@literal --debug} or
408+
* {@literal --trace} command line args. Defaults to {@code true}.
365409
* @param parseArgs if arguments should be parsed
366410
*/
367411
public void setParseArgs(boolean parseArgs) {

spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@
9393
"description": "Log levels severity mapping. For instance, `logging.level.org.springframework=DEBUG`.",
9494
"sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener"
9595
},
96+
{
97+
"name": "logging.group",
98+
"type": "java.util.Map<java.lang.String,java.lang.String>",
99+
"description": "Log groups to quickly change multipe loggers at the same time. For instance, `logging.level.db=org.hibernate,org.springframework.jdbc`.",
100+
"sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener"
101+
},
96102
{
97103
"name": "logging.path",
98104
"type": "java.lang.String",

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ public class LoggingApplicationListenerTests {
9292

9393
private final LoggingApplicationListener initializer = new LoggingApplicationListener();
9494

95-
private final Log logger = new SLF4JLogFactory().getInstance(getClass());
95+
private final SLF4JLogFactory logFactory = new SLF4JLogFactory();
96+
97+
private final Log logger = this.logFactory.getInstance(getClass());
9698

9799
private final SpringApplication springApplication = new SpringApplication();
98100

@@ -558,6 +560,34 @@ public void lowPriorityPropertySourceShouldNotOverrideRootLoggerConfig() {
558560
assertThat(this.outputCapture.toString()).contains("testatdebug");
559561
}
560562

563+
@Test
564+
public void loggingGroupsDefaultsAreApplied() {
565+
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
566+
"logging.level.web=TRACE");
567+
this.initializer.initialize(this.context.getEnvironment(),
568+
this.context.getClassLoader());
569+
assertTraceEnabled("org.springframework.core", false);
570+
assertTraceEnabled("org.springframework.core.codec", true);
571+
assertTraceEnabled("org.springframework.http", true);
572+
assertTraceEnabled("org.springframework.web", true);
573+
}
574+
575+
@Test
576+
public void loggingGroupsCanBeDefined() {
577+
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
578+
"logging.group.foo=com.foo.bar,com.foo.baz", "logging.level.foo=TRACE");
579+
this.initializer.initialize(this.context.getEnvironment(),
580+
this.context.getClassLoader());
581+
assertTraceEnabled("com.foo", false);
582+
assertTraceEnabled("com.foo.bar", true);
583+
assertTraceEnabled("com.foo.baz", true);
584+
}
585+
586+
private void assertTraceEnabled(String name, boolean expected) {
587+
assertThat(this.logFactory.getInstance(name).isTraceEnabled())
588+
.isEqualTo(expected);
589+
}
590+
561591
private void multicastEvent(ApplicationEvent event) {
562592
multicastEvent(this.initializer, event);
563593
}

0 commit comments

Comments
 (0)