Commit 238d0fed authored by Phillip Webb's avatar Phillip Webb

Improve Log4J log level reset support

Update `Log4J2LoggingSystem` so that call to `setLevel` with a `null`
level with remove the logger if it was previously configured by a
`LoggingSystem` call.

To track which loggers have been configured by us, and which have been
configure directly by the user, a custom `LoggerConfig` subclass is
used. We'll only remove `LevelSetLoggerConfig` classes, for any others
we'll call `setLevel(null)` on the config.

Prior to this commit, it was impossible to set then reset a logger
level using the actuator endpoint. This is because Log4J doesn't provide
a way to get the actual configured level. If the `setLevel(null)` has
been applied, then `getLevel()` will return the value of the parent
logger or a default value of `ERROR`.

Fixes gh-24298
parent 96823869
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -214,16 +214,37 @@ public class Log4J2LoggingSystem extends Slf4JLoggingSystem { ...@@ -214,16 +214,37 @@ public class Log4J2LoggingSystem extends Slf4JLoggingSystem {
@Override @Override
public void setLogLevel(String loggerName, LogLevel logLevel) { public void setLogLevel(String loggerName, LogLevel logLevel) {
Level level = LEVELS.convertSystemToNative(logLevel); setLogLevel(loggerName, LEVELS.convertSystemToNative(logLevel));
}
private void setLogLevel(String loggerName, Level level) {
LoggerConfig logger = getLogger(loggerName); LoggerConfig logger = getLogger(loggerName);
if (level == null) {
clearLogLevel(loggerName, logger);
}
else {
setLogLevel(loggerName, logger, level);
}
getLoggerContext().updateLoggers();
}
private void clearLogLevel(String loggerName, LoggerConfig logger) {
if (logger instanceof LevelSetLoggerConfig) {
getLoggerContext().getConfiguration().removeLogger(loggerName);
}
else {
logger.setLevel(null);
}
}
private void setLogLevel(String loggerName, LoggerConfig logger, Level level) {
if (logger == null) { if (logger == null) {
logger = new LoggerConfig(loggerName, level, true); getLoggerContext().getConfiguration().addLogger(loggerName,
getLoggerContext().getConfiguration().addLogger(loggerName, logger); new LevelSetLoggerConfig(loggerName, level, true));
} }
else { else {
logger.setLevel(level); logger.setLevel(level);
} }
getLoggerContext().updateLoggers();
} }
@Override @Override
...@@ -348,4 +369,15 @@ public class Log4J2LoggingSystem extends Slf4JLoggingSystem { ...@@ -348,4 +369,15 @@ public class Log4J2LoggingSystem extends Slf4JLoggingSystem {
} }
/**
* {@link LoggerConfig} used when the user has set a specific {@link Level}.
*/
private static class LevelSetLoggerConfig extends LoggerConfig {
LevelSetLoggerConfig(String name, Level level, boolean additive) {
super(name, level, additive);
}
}
} }
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -25,7 +25,6 @@ import java.util.EnumSet; ...@@ -25,7 +25,6 @@ import java.util.EnumSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.logging.Level;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
...@@ -34,6 +33,7 @@ import org.apache.logging.log4j.LogManager; ...@@ -34,6 +33,7 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.config.Reconfigurable; import org.apache.logging.log4j.core.config.Reconfigurable;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
...@@ -241,7 +241,7 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests { ...@@ -241,7 +241,7 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests {
this.loggingSystem.beforeInitialize(); this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(null, null, null); this.loggingSystem.initialize(null, null, null);
java.util.logging.Logger julLogger = java.util.logging.Logger.getLogger(getClass().getName()); java.util.logging.Logger julLogger = java.util.logging.Logger.getLogger(getClass().getName());
julLogger.setLevel(Level.INFO); julLogger.setLevel(java.util.logging.Level.INFO);
julLogger.severe("Hello world"); julLogger.severe("Hello world");
assertThat(output).contains("Hello world"); assertThat(output).contains("Hello world");
} }
...@@ -338,6 +338,38 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests { ...@@ -338,6 +338,38 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests {
verify(listener, times(4)).propertyChange(any(PropertyChangeEvent.class)); verify(listener, times(4)).propertyChange(any(PropertyChangeEvent.class));
} }
@Test
void getLoggingConfigurationWithResetLevelReturnsNull() {
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(null, null, null);
this.loggingSystem.setLogLevel("com.example", LogLevel.WARN);
this.loggingSystem.setLogLevel("com.example.test", LogLevel.DEBUG);
LoggerConfiguration configuration = this.loggingSystem.getLoggerConfiguration("com.example.test");
assertThat(configuration)
.isEqualTo(new LoggerConfiguration("com.example.test", LogLevel.DEBUG, LogLevel.DEBUG));
this.loggingSystem.setLogLevel("com.example.test", null);
LoggerConfiguration updatedConfiguration = this.loggingSystem.getLoggerConfiguration("com.example.test");
assertThat(updatedConfiguration).isNull();
}
@Test
void getLoggingConfigurationWithResetLevelWhenAlreadyConfiguredReturnsParentConfiguredLevel() {
LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(null, null, null);
loggerContext.getConfiguration().addLogger("com.example.test",
new LoggerConfig("com.example.test", org.apache.logging.log4j.Level.INFO, false));
this.loggingSystem.setLogLevel("com.example", LogLevel.WARN);
this.loggingSystem.setLogLevel("com.example.test", LogLevel.DEBUG);
LoggerConfiguration configuration = this.loggingSystem.getLoggerConfiguration("com.example.test");
assertThat(configuration)
.isEqualTo(new LoggerConfiguration("com.example.test", LogLevel.DEBUG, LogLevel.DEBUG));
this.loggingSystem.setLogLevel("com.example.test", null);
LoggerConfiguration updatedConfiguration = this.loggingSystem.getLoggerConfiguration("com.example.test");
assertThat(updatedConfiguration)
.isEqualTo(new LoggerConfiguration("com.example.test", LogLevel.WARN, LogLevel.WARN));
}
private String getRelativeClasspathLocation(String fileName) { private String getRelativeClasspathLocation(String fileName) {
String defaultPath = ClassUtils.getPackageName(getClass()); String defaultPath = ClassUtils.getPackageName(getClass());
defaultPath = defaultPath.replace('.', '/'); defaultPath = defaultPath.replace('.', '/');
......
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