Allow to reset a log level

This commit ensures that `setLogLevel` on the `LoggingSystem` accepts
a `null` level. A `null` level means any customization sets on that
level should be removed and the default configuration should be used
instead.

Effectively, the level of the parent logger is going to be used when
`setLevel` is called with `null` for a given logger.

Most JMX clients do not accept to pass `null` for an argument so an
empty String is translated to null in that specific case.

Closes gh-8776
This commit is contained in:
Stephane Nicoll 2017-06-01 14:31:07 +02:00
parent bdf2b2e810
commit 605dee4700
9 changed files with 84 additions and 6 deletions

View File

@ -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

View File

@ -80,6 +80,12 @@ public class LoggersEndpointTests extends AbstractEndpointTests<LoggersEndpoint>
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);
}

View File

@ -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 {

View File

@ -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]]

View File

@ -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");

View File

@ -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 = "";
}

View File

@ -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();

View File

@ -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();

View File

@ -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();