Commit b32887b8 authored by Phillip Webb's avatar Phillip Webb

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
parent 3ed9730b
......@@ -1731,6 +1731,46 @@ The following example shows potential logging settings in `application.propertie
[[boot-features-custom-log-groups]]
=== Log Groups
It's often useful to be able to group related loggers together so that they can all be
configured at the same time. For example, you might commonly change the logging levels for
_all_ Tomcat related loggers, but you can't easily remember to top level packages.
To help with this, Spring Boot allows you do define logging groups in your Spring
`Environment`. For example, here's how you could define a "`tomcat`" group by adding
it to your `appplication.properties`:
[source,properties,indent=0,subs="verbatim,quotes,attributes"]
----
logging.group.tomcat=org.apache.catalina, org.apache.coyote, org.apache.tomcat
----
Once defined, you can change the level for all the loggers in the group with a single
line:
[source,properties,indent=0,subs="verbatim,quotes,attributes"]
----
logging.level.tomcat=TRACE
----
Spring Boot includes the following pre-defined logging groups that can be used
out-of-the-box:
[cols="1,4"]
|===
|Name |Loggers
|web
|`org.springframework.core.codec`, `org.springframework.http`, `org.springframework.web`
|sql
|`org.springframework.jdbc.core`, `org.hibernate.SQL`
|===
[[boot-features-custom-log-configuration]]
=== Custom Log Configuration
The various logging systems can be activated by including the appropriate libraries on
......
......@@ -17,6 +17,7 @@
package org.springframework.boot.context.logging;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
......@@ -49,6 +50,7 @@ import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
......@@ -56,7 +58,8 @@ import org.springframework.util.StringUtils;
* An {@link ApplicationListener} that configures the {@link LoggingSystem}. If the
* environment contains a {@code logging.config} property it will be used to bootstrap the
* logging system, otherwise a default configuration is used. Regardless, logging levels
* will be customized if the environment contains {@code logging.level.*} entries.
* will be customized if the environment contains {@code logging.level.*} entries and
* logging groups can be defined with {@code logging.group} .
* <p>
* Debug and trace logging for Spring, Tomcat, Jetty and Hibernate will be enabled when
* the environment contains {@code debug} or {@code trace} properties that aren't set to
......@@ -88,6 +91,9 @@ public class LoggingApplicationListener implements GenericApplicationListener {
private static final Bindable<Map<String, String>> STRING_STRING_MAP = Bindable
.mapOf(String.class, String.class);
private static final Bindable<Map<String, String[]>> STRING_STRINGS_MAP = Bindable
.mapOf(String.class, String[].class);
/**
* The default order for the LoggingApplicationListener.
*/
......@@ -111,19 +117,30 @@ public class LoggingApplicationListener implements GenericApplicationListener {
*/
public static final String LOGGING_SYSTEM_BEAN_NAME = "springBootLoggingSystem";
private static final MultiValueMap<LogLevel, String> LOG_LEVEL_LOGGERS;
private static final Map<String, List<String>> DEFAULT_GROUP_LOGGERS;
static {
MultiValueMap<String, String> loggers = new LinkedMultiValueMap<>();
loggers.add("web", "org.springframework.core.codec");
loggers.add("web", "org.springframework.http");
loggers.add("web", "org.springframework.web");
loggers.add("sql", "org.springframework.jdbc.core");
loggers.add("sql", "org.hibernate.SQL");
DEFAULT_GROUP_LOGGERS = Collections.unmodifiableMap(loggers);
}
private static AtomicBoolean shutdownHookRegistered = new AtomicBoolean(false);
private static final Map<LogLevel, List<String>> LOG_LEVEL_LOGGERS;
static {
LOG_LEVEL_LOGGERS = new LinkedMultiValueMap<>();
LOG_LEVEL_LOGGERS.add(LogLevel.DEBUG, "org.springframework.boot");
LOG_LEVEL_LOGGERS.add(LogLevel.DEBUG, "org.hibernate.SQL");
LOG_LEVEL_LOGGERS.add(LogLevel.TRACE, "org.springframework");
LOG_LEVEL_LOGGERS.add(LogLevel.TRACE, "org.apache.tomcat");
LOG_LEVEL_LOGGERS.add(LogLevel.TRACE, "org.apache.catalina");
LOG_LEVEL_LOGGERS.add(LogLevel.TRACE, "org.eclipse.jetty");
LOG_LEVEL_LOGGERS.add(LogLevel.TRACE, "org.hibernate.tool.hbm2ddl");
MultiValueMap<LogLevel, String> loggers = new LinkedMultiValueMap<>();
loggers.add(LogLevel.DEBUG, "sql");
loggers.add(LogLevel.DEBUG, "web");
loggers.add(LogLevel.DEBUG, "org.springframework.boot");
loggers.add(LogLevel.TRACE, "org.springframework");
loggers.add(LogLevel.TRACE, "org.apache.tomcat");
loggers.add(LogLevel.TRACE, "org.apache.catalina");
loggers.add(LogLevel.TRACE, "org.eclipse.jetty");
loggers.add(LogLevel.TRACE, "org.hibernate.tool.hbm2ddl");
LOG_LEVEL_LOGGERS = Collections.unmodifiableMap(loggers);
}
private static final Class<?>[] EVENT_TYPES = { ApplicationStartingEvent.class,
......@@ -133,6 +150,8 @@ public class LoggingApplicationListener implements GenericApplicationListener {
private static final Class<?>[] SOURCE_TYPES = { SpringApplication.class,
ApplicationContext.class };
private static AtomicBoolean shutdownHookRegistered = new AtomicBoolean(false);
private final Log logger = LogFactory.getLog(getClass());
private LoggingSystem loggingSystem;
......@@ -304,8 +323,32 @@ public class LoggingApplicationListener implements GenericApplicationListener {
return;
}
Binder binder = Binder.get(environment);
binder.bind("logging.level", STRING_STRING_MAP).orElseGet(Collections::emptyMap)
.forEach((name, level) -> setLogLevel(system, name, level));
Map<String, String[]> groups = getGroups(binder);
Map<String, String> levels = binder.bind("logging.level", STRING_STRING_MAP)
.orElseGet(Collections::emptyMap);
levels.forEach((name, level) -> {
String[] groupedNames = groups.get(name);
if (ObjectUtils.isEmpty(groupedNames)) {
setLogLevel(system, name, level);
}
else {
setLogLevel(system, groupedNames, level);
}
});
}
private Map<String, String[]> getGroups(Binder binder) {
Map<String, String[]> groups = new LinkedHashMap<>();
DEFAULT_GROUP_LOGGERS.forEach(
(name, loggers) -> groups.put(name, StringUtils.toStringArray(loggers)));
binder.bind("logging.group", STRING_STRINGS_MAP.withExistingValue(groups));
return groups;
}
private void setLogLevel(LoggingSystem system, String[] names, String level) {
for (String name : names) {
setLogLevel(system, name, level);
}
}
private void setLogLevel(LoggingSystem system, String name, String level) {
......@@ -360,8 +403,9 @@ public class LoggingApplicationListener implements GenericApplicationListener {
}
/**
* Sets if initialization arguments should be parsed for {@literal --debug} and
* {@literal --trace} options. Defaults to {@code true}.
* Sets if initialization arguments should be parsed for {@literal debug} and
* {@literal trace} properties (usually defined from {@literal --debug} or
* {@literal --trace} command line args. Defaults to {@code true}.
* @param parseArgs if arguments should be parsed
*/
public void setParseArgs(boolean parseArgs) {
......
......@@ -93,6 +93,12 @@
"description": "Log levels severity mapping. For instance, `logging.level.org.springframework=DEBUG`.",
"sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener"
},
{
"name": "logging.group",
"type": "java.util.Map<java.lang.String,java.lang.String>",
"description": "Log groups to quickly change multipe loggers at the same time. For instance, `logging.level.db=org.hibernate,org.springframework.jdbc`.",
"sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener"
},
{
"name": "logging.path",
"type": "java.lang.String",
......
......@@ -92,7 +92,9 @@ public class LoggingApplicationListenerTests {
private final LoggingApplicationListener initializer = new LoggingApplicationListener();
private final Log logger = new SLF4JLogFactory().getInstance(getClass());
private final SLF4JLogFactory logFactory = new SLF4JLogFactory();
private final Log logger = this.logFactory.getInstance(getClass());
private final SpringApplication springApplication = new SpringApplication();
......@@ -558,6 +560,34 @@ public class LoggingApplicationListenerTests {
assertThat(this.outputCapture.toString()).contains("testatdebug");
}
@Test
public void loggingGroupsDefaultsAreApplied() {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
"logging.level.web=TRACE");
this.initializer.initialize(this.context.getEnvironment(),
this.context.getClassLoader());
assertTraceEnabled("org.springframework.core", false);
assertTraceEnabled("org.springframework.core.codec", true);
assertTraceEnabled("org.springframework.http", true);
assertTraceEnabled("org.springframework.web", true);
}
@Test
public void loggingGroupsCanBeDefined() {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
"logging.group.foo=com.foo.bar,com.foo.baz", "logging.level.foo=TRACE");
this.initializer.initialize(this.context.getEnvironment(),
this.context.getClassLoader());
assertTraceEnabled("com.foo", false);
assertTraceEnabled("com.foo.bar", true);
assertTraceEnabled("com.foo.baz", true);
}
private void assertTraceEnabled(String name, boolean expected) {
assertThat(this.logFactory.getInstance(name).isTraceEnabled())
.isEqualTo(expected);
}
private void multicastEvent(ApplicationEvent event) {
multicastEvent(this.initializer, event);
}
......
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