diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 2c3904f862..1b7a59af5c 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -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 diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/logging/LoggingApplicationListener.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/logging/LoggingApplicationListener.java index 75d9227aae..4fb6e429cd 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/logging/LoggingApplicationListener.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/logging/LoggingApplicationListener.java @@ -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} . *

* 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> STRING_STRING_MAP = Bindable .mapOf(String.class, String.class); + private static final Bindable> 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 LOG_LEVEL_LOGGERS; + private static final Map> DEFAULT_GROUP_LOGGERS; + static { + MultiValueMap 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> 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 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 groups = getGroups(binder); + Map 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 getGroups(Binder binder) { + Map 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) { diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json index edd34a3a3d..ed3127ef84 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -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", + "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", diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java index 276fd8ac68..8a7e683655 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java @@ -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); }