Skip to content

Commit 238d0fe

Browse files
committed
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
1 parent 9682386 commit 238d0fe

File tree

2 files changed

+72
-8
lines changed

2 files changed

+72
-8
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -214,16 +214,37 @@ public Set<LogLevel> getSupportedLogLevels() {
214214

215215
@Override
216216
public void setLogLevel(String loggerName, LogLevel logLevel) {
217-
Level level = LEVELS.convertSystemToNative(logLevel);
217+
setLogLevel(loggerName, LEVELS.convertSystemToNative(logLevel));
218+
}
219+
220+
private void setLogLevel(String loggerName, Level level) {
218221
LoggerConfig logger = getLogger(loggerName);
222+
if (level == null) {
223+
clearLogLevel(loggerName, logger);
224+
}
225+
else {
226+
setLogLevel(loggerName, logger, level);
227+
}
228+
getLoggerContext().updateLoggers();
229+
}
230+
231+
private void clearLogLevel(String loggerName, LoggerConfig logger) {
232+
if (logger instanceof LevelSetLoggerConfig) {
233+
getLoggerContext().getConfiguration().removeLogger(loggerName);
234+
}
235+
else {
236+
logger.setLevel(null);
237+
}
238+
}
239+
240+
private void setLogLevel(String loggerName, LoggerConfig logger, Level level) {
219241
if (logger == null) {
220-
logger = new LoggerConfig(loggerName, level, true);
221-
getLoggerContext().getConfiguration().addLogger(loggerName, logger);
242+
getLoggerContext().getConfiguration().addLogger(loggerName,
243+
new LevelSetLoggerConfig(loggerName, level, true));
222244
}
223245
else {
224246
logger.setLevel(level);
225247
}
226-
getLoggerContext().updateLoggers();
227248
}
228249

229250
@Override
@@ -348,4 +369,15 @@ public LoggingSystem getLoggingSystem(ClassLoader classLoader) {
348369

349370
}
350371

372+
/**
373+
* {@link LoggerConfig} used when the user has set a specific {@link Level}.
374+
*/
375+
private static class LevelSetLoggerConfig extends LoggerConfig {
376+
377+
LevelSetLoggerConfig(String name, Level level, boolean additive) {
378+
super(name, level, additive);
379+
}
380+
381+
}
382+
351383
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,7 +25,6 @@
2525
import java.util.LinkedHashMap;
2626
import java.util.List;
2727
import java.util.Map;
28-
import java.util.logging.Level;
2928

3029
import com.fasterxml.jackson.databind.ObjectMapper;
3130
import org.apache.commons.logging.Log;
@@ -34,6 +33,7 @@
3433
import org.apache.logging.log4j.Logger;
3534
import org.apache.logging.log4j.core.LoggerContext;
3635
import org.apache.logging.log4j.core.config.Configuration;
36+
import org.apache.logging.log4j.core.config.LoggerConfig;
3737
import org.apache.logging.log4j.core.config.Reconfigurable;
3838
import org.junit.jupiter.api.AfterEach;
3939
import org.junit.jupiter.api.BeforeEach;
@@ -241,7 +241,7 @@ void loggingThatUsesJulIsCaptured(CapturedOutput output) {
241241
this.loggingSystem.beforeInitialize();
242242
this.loggingSystem.initialize(null, null, null);
243243
java.util.logging.Logger julLogger = java.util.logging.Logger.getLogger(getClass().getName());
244-
julLogger.setLevel(Level.INFO);
244+
julLogger.setLevel(java.util.logging.Level.INFO);
245245
julLogger.severe("Hello world");
246246
assertThat(output).contains("Hello world");
247247
}
@@ -338,6 +338,38 @@ void initializationIsOnlyPerformedOnceUntilCleanedUp() {
338338
verify(listener, times(4)).propertyChange(any(PropertyChangeEvent.class));
339339
}
340340

341+
@Test
342+
void getLoggingConfigurationWithResetLevelReturnsNull() {
343+
this.loggingSystem.beforeInitialize();
344+
this.loggingSystem.initialize(null, null, null);
345+
this.loggingSystem.setLogLevel("com.example", LogLevel.WARN);
346+
this.loggingSystem.setLogLevel("com.example.test", LogLevel.DEBUG);
347+
LoggerConfiguration configuration = this.loggingSystem.getLoggerConfiguration("com.example.test");
348+
assertThat(configuration)
349+
.isEqualTo(new LoggerConfiguration("com.example.test", LogLevel.DEBUG, LogLevel.DEBUG));
350+
this.loggingSystem.setLogLevel("com.example.test", null);
351+
LoggerConfiguration updatedConfiguration = this.loggingSystem.getLoggerConfiguration("com.example.test");
352+
assertThat(updatedConfiguration).isNull();
353+
}
354+
355+
@Test
356+
void getLoggingConfigurationWithResetLevelWhenAlreadyConfiguredReturnsParentConfiguredLevel() {
357+
LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
358+
this.loggingSystem.beforeInitialize();
359+
this.loggingSystem.initialize(null, null, null);
360+
loggerContext.getConfiguration().addLogger("com.example.test",
361+
new LoggerConfig("com.example.test", org.apache.logging.log4j.Level.INFO, false));
362+
this.loggingSystem.setLogLevel("com.example", LogLevel.WARN);
363+
this.loggingSystem.setLogLevel("com.example.test", LogLevel.DEBUG);
364+
LoggerConfiguration configuration = this.loggingSystem.getLoggerConfiguration("com.example.test");
365+
assertThat(configuration)
366+
.isEqualTo(new LoggerConfiguration("com.example.test", LogLevel.DEBUG, LogLevel.DEBUG));
367+
this.loggingSystem.setLogLevel("com.example.test", null);
368+
LoggerConfiguration updatedConfiguration = this.loggingSystem.getLoggerConfiguration("com.example.test");
369+
assertThat(updatedConfiguration)
370+
.isEqualTo(new LoggerConfiguration("com.example.test", LogLevel.WARN, LogLevel.WARN));
371+
}
372+
341373
private String getRelativeClasspathLocation(String fileName) {
342374
String defaultPath = ClassUtils.getPackageName(getClass());
343375
defaultPath = defaultPath.replace('.', '/');

0 commit comments

Comments
 (0)