diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/LoggersEndpointMBean.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/LoggersEndpointMBean.java index e5f324d8d3..9a5fc17082 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/LoggersEndpointMBean.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/LoggersEndpointMBean.java @@ -24,12 +24,15 @@ import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; import org.springframework.boot.logging.LogLevel; import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.jmx.export.annotation.ManagedOperation; -import org.springframework.util.Assert; +import org.springframework.jmx.export.annotation.ManagedOperationParameter; +import org.springframework.jmx.export.annotation.ManagedOperationParameters; +import org.springframework.util.StringUtils; /** * Adapter to expose {@link LoggersEndpoint} as an {@link MvcEndpoint}. * * @author Vedran Pavic + * @author Stephane Nicoll * @since 1.5.0 */ public class LoggersEndpointMBean extends EndpointMBean { @@ -45,15 +48,25 @@ public class LoggersEndpointMBean extends EndpointMBean { } @ManagedOperation(description = "Get log level for a given logger") + @ManagedOperationParameters({ + @ManagedOperationParameter(name = "loggerName", description = "Name of the logger") }) public Object getLogger(String loggerName) { return convert(getEndpoint().invoke(loggerName)); } @ManagedOperation(description = "Set log level for a given logger") + @ManagedOperationParameters({ + @ManagedOperationParameter(name = "loggerName", description = "Name of the logger"), + @ManagedOperationParameter(name = "logLevel", description = "New log level (can be null or empty String to remove the custom level)") }) public void setLogLevel(String loggerName, String logLevel) { - Assert.notNull(logLevel, "LogLevel must not be null"); - LogLevel level = LogLevel.valueOf(logLevel.toUpperCase()); - getEndpoint().setLogLevel(loggerName, level); + getEndpoint().setLogLevel(loggerName, determineLogLevel(logLevel)); + } + + private LogLevel determineLogLevel(String logLevel) { + if (StringUtils.hasText(logLevel)) { + return LogLevel.valueOf(logLevel.toUpperCase()); + } + return null; } @Override diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LoggersEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LoggersEndpointTests.java index 819fa14594..c93ae95d48 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LoggersEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LoggersEndpointTests.java @@ -80,6 +80,12 @@ public class LoggersEndpointTests extends AbstractEndpointTests verify(getLoggingSystem()).setLogLevel("ROOT", LogLevel.DEBUG); } + @Test + public void setLogLevelToNull() { + getEndpointBean().setLogLevel("ROOT", null); + verify(getLoggingSystem()).setLogLevel("ROOT", null); + } + private LoggingSystem getLoggingSystem() { return this.context.getBean(LoggingSystem.class); } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpointTests.java index d849734f9b..aa26d63ae8 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpointTests.java @@ -64,6 +64,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. * @author Ben Hale * @author Phillip Webb * @author EddĂș MelĂ©ndez + * @author Stephane Nicoll */ @RunWith(SpringRunner.class) @SpringBootTest @@ -181,6 +182,22 @@ public class LoggersMvcEndpointTests { verifyZeroInteractions(this.loggingSystem); } + @Test + public void setLoggerWithNullLogLevel() throws Exception { + this.mvc.perform(post(PATH + "/ROOT").contentType(MediaType.APPLICATION_JSON) + .content("{\"configuredLevel\": null}")) + .andExpect(status().isNoContent()); + verify(this.loggingSystem).setLogLevel("ROOT", null); + } + + @Test + public void setLoggerWithNoLogLevel() throws Exception { + this.mvc.perform(post(PATH + "/ROOT").contentType(MediaType.APPLICATION_JSON) + .content("{}")) + .andExpect(status().isNoContent()); + verify(this.loggingSystem).setLogLevel("ROOT", null); + } + @Test public void logLevelForLoggerWithNameThatCouldBeMistakenForAPathExtension() throws Exception { diff --git a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index 96e6bc50a3..4c1f692613 100644 --- a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -927,6 +927,9 @@ In order to configure a given logger, you `POST` a partial entity to the resourc } ---- +TIP: You can also pass a `null` `configuredLevel` to "reset" the specific level of the +logger (and use the default configuration instead). + [[production-ready-metrics]] diff --git a/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystem.java b/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystem.java index c21f8bba14..d2794ab6b6 100644 --- a/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystem.java +++ b/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystem.java @@ -116,7 +116,8 @@ public abstract class LoggingSystem { * Sets the logging level for a given logger. * @param loggerName the name of the logger to set ({@code null} can be used for the * root logger). - * @param level the log level + * @param level the log level ({@code null} can be used to remove any custom level + * for the logger and use the default configuration instead) */ public void setLogLevel(String loggerName, LogLevel level) { throw new UnsupportedOperationException("Unable to set log level"); diff --git a/spring-boot/src/main/java/org/springframework/boot/logging/java/JavaLoggingSystem.java b/spring-boot/src/main/java/org/springframework/boot/logging/java/JavaLoggingSystem.java index 16d3941a7e..779e5a3529 100644 --- a/spring-boot/src/main/java/org/springframework/boot/logging/java/JavaLoggingSystem.java +++ b/spring-boot/src/main/java/org/springframework/boot/logging/java/JavaLoggingSystem.java @@ -117,7 +117,6 @@ public class JavaLoggingSystem extends AbstractLoggingSystem { @Override public void setLogLevel(String loggerName, LogLevel level) { - Assert.notNull(level, "Level must not be null"); if (loggerName == null || ROOT_LOGGER_NAME.equals(loggerName)) { loggerName = ""; } diff --git a/spring-boot/src/test/java/org/springframework/boot/logging/java/JavaLoggingSystemTests.java b/spring-boot/src/test/java/org/springframework/boot/logging/java/JavaLoggingSystemTests.java index fa715308b6..d45ba1118a 100644 --- a/spring-boot/src/test/java/org/springframework/boot/logging/java/JavaLoggingSystemTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/logging/java/JavaLoggingSystemTests.java @@ -168,6 +168,19 @@ public class JavaLoggingSystemTests extends AbstractLoggingSystemTests { .isEqualTo(1); } + @Test + public void setLevelToNull() throws Exception { + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(null, null, null); + this.logger.fine("Hello"); + this.loggingSystem.setLogLevel("org.springframework.boot", LogLevel.DEBUG); + this.logger.fine("Hello"); + this.loggingSystem.setLogLevel("org.springframework.boot", null); + this.logger.fine("Hello"); + assertThat(StringUtils.countOccurrencesOf(this.output.toString(), "Hello")) + .isEqualTo(1); + } + @Test public void getLoggingConfigurations() throws Exception { this.loggingSystem.beforeInitialize(); diff --git a/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java b/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java index 2938ce72fe..fa61d4823d 100644 --- a/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java @@ -141,6 +141,19 @@ public class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests { .isEqualTo(1); } + @Test + public void setLevelToNull() throws Exception { + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(null, null, null); + this.logger.debug("Hello"); + this.loggingSystem.setLogLevel("org.springframework.boot", LogLevel.DEBUG); + this.logger.debug("Hello"); + this.loggingSystem.setLogLevel("org.springframework.boot", null); + this.logger.debug("Hello"); + assertThat(StringUtils.countOccurrencesOf(this.output.toString(), "Hello")) + .isEqualTo(1); + } + @Test public void getLoggingConfigurations() throws Exception { this.loggingSystem.beforeInitialize(); diff --git a/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java b/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java index ea0470f611..d408664a88 100644 --- a/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java @@ -184,6 +184,19 @@ public class LogbackLoggingSystemTests extends AbstractLoggingSystemTests { .isEqualTo(1); } + @Test + public void setLevelToNull() throws Exception { + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(this.initializationContext, null, null); + this.logger.debug("Hello"); + this.loggingSystem.setLogLevel("org.springframework.boot", LogLevel.DEBUG); + this.logger.debug("Hello"); + this.loggingSystem.setLogLevel("org.springframework.boot", null); + this.logger.debug("Hello"); + assertThat(StringUtils.countOccurrencesOf(this.output.toString(), "Hello")) + .isEqualTo(1); + } + @Test public void getLoggingConfigurations() throws Exception { this.loggingSystem.beforeInitialize();