From 07d8d73d457408e779f5b26b9cecf0db2f6912a4 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 14 Jul 2025 13:48:13 +1200 Subject: [PATCH] Add support for debug logging when dynamically changing log levels --- .../io/avaje/simplelogger/LoggerContext.java | 4 ++- .../simplelogger/encoder/SimpleLogger.java | 6 ++-- .../encoder/SimpleLoggerFactory.java | 29 +++++++++++++++++-- .../dynamic/DynamicLogLevels.java | 14 +++++++-- .../dynamic/DynamicLogLevelsTest.java | 15 ++++++++++ .../resources/avaje-logger-test.properties | 3 +- 6 files changed, 62 insertions(+), 9 deletions(-) diff --git a/avaje-simple-json-logger/src/main/java/io/avaje/simplelogger/LoggerContext.java b/avaje-simple-json-logger/src/main/java/io/avaje/simplelogger/LoggerContext.java index 3eebb89..09d9c1d 100644 --- a/avaje-simple-json-logger/src/main/java/io/avaje/simplelogger/LoggerContext.java +++ b/avaje-simple-json-logger/src/main/java/io/avaje/simplelogger/LoggerContext.java @@ -24,6 +24,8 @@ static LoggerContext get() { /** * Apply a set of name log level pairs and update log levels for all impacted loggers. + * + * @return Map of changed loggers and their new levels. */ - void putAll(Map nameLevels); + Map putAll(Map nameLevels); } diff --git a/avaje-simple-json-logger/src/main/java/io/avaje/simplelogger/encoder/SimpleLogger.java b/avaje-simple-json-logger/src/main/java/io/avaje/simplelogger/encoder/SimpleLogger.java index 9839e60..fd5d25b 100644 --- a/avaje-simple-json-logger/src/main/java/io/avaje/simplelogger/encoder/SimpleLogger.java +++ b/avaje-simple-json-logger/src/main/java/io/avaje/simplelogger/encoder/SimpleLogger.java @@ -19,9 +19,11 @@ final class SimpleLogger extends LegacyAbstractLogger { this.level = level; } - void setNewLevel(int newLevel) { + boolean setNewLevel(int newLevel) { // atomic assignment - this.level = newLevel; + final boolean changed = level != newLevel; + level = newLevel; + return changed; } @Override diff --git a/avaje-simple-json-logger/src/main/java/io/avaje/simplelogger/encoder/SimpleLoggerFactory.java b/avaje-simple-json-logger/src/main/java/io/avaje/simplelogger/encoder/SimpleLoggerFactory.java index 1f652e5..072598d 100644 --- a/avaje-simple-json-logger/src/main/java/io/avaje/simplelogger/encoder/SimpleLoggerFactory.java +++ b/avaje-simple-json-logger/src/main/java/io/avaje/simplelogger/encoder/SimpleLoggerFactory.java @@ -5,6 +5,7 @@ import org.slf4j.spi.LocationAwareLogger; import java.util.Map; +import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -46,16 +47,40 @@ static int stringToLevel(String levelStr) { return LocationAwareLogger.INFO_INT; } + static String level(int level) { + switch (level) { + case LocationAwareLogger.TRACE_INT: + return "trace"; + case LocationAwareLogger.DEBUG_INT: + return "debug"; + case LocationAwareLogger.INFO_INT: + return "info"; + case LocationAwareLogger.WARN_INT: + return "warn"; + case LocationAwareLogger.ERROR_INT: + return "error"; + case LOG_LEVEL_OFF: + return "off"; + default: + return "Level" + level; + } + } + @Override - public void putAll(Map nameLevels) { + public Map putAll(Map nameLevels) { nameLevels.forEach(this::putLevel); + final Map changed = new TreeMap<>(); for (Map.Entry entry : loggerMap.entrySet()) { final String key = entry.getKey(); if (adjustedKey(key, nameLevels)) { - entry.getValue().setNewLevel(level(key)); + int newLevel = level(key); + if (entry.getValue().setNewLevel(newLevel)) { + changed.put(key, level(newLevel)); + } } } + return changed; } private boolean adjustedKey(String key, Map nameLevels) { diff --git a/avaje-simple-logger/src/main/java/io/avaje/simplelogger/dynamic/DynamicLogLevels.java b/avaje-simple-logger/src/main/java/io/avaje/simplelogger/dynamic/DynamicLogLevels.java index 5c03ef4..a833eb5 100644 --- a/avaje-simple-logger/src/main/java/io/avaje/simplelogger/dynamic/DynamicLogLevels.java +++ b/avaje-simple-logger/src/main/java/io/avaje/simplelogger/dynamic/DynamicLogLevels.java @@ -4,6 +4,8 @@ import io.avaje.config.ConfigurationPlugin; import io.avaje.config.ModificationEvent; import io.avaje.simplelogger.LoggerContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.Map; @@ -13,6 +15,8 @@ */ public final class DynamicLogLevels implements ConfigurationPlugin { + private static final Logger log = LoggerFactory.getLogger("io.avaje.simplelogger"); + private static String trimKey(String key) { return key.substring(10); } @@ -24,11 +28,14 @@ public void apply(Configuration configuration) { for (String key : config.keys()) { String rawLevel = config.getNullable(key); if (rawLevel != null) { - nameLevels.put(trimKey(key), rawLevel); + nameLevels.put(key, rawLevel); } } if (!nameLevels.isEmpty()) { - LoggerContext.get().putAll(nameLevels); + var changed = LoggerContext.get().putAll(nameLevels); + log.debug("apply log levels:{} changed loggers:{}", nameLevels, changed); + } else { + log.debug("dynamic log levels enabled"); } configuration.onChange(this::onChangeAny); @@ -47,7 +54,8 @@ private void onChangeAny(ModificationEvent modificationEvent) { }); if (!nameLevels.isEmpty()) { - LoggerContext.get().putAll(nameLevels); + var changed = LoggerContext.get().putAll(nameLevels); + log.debug("apply log levels:{} changed loggers:{}", nameLevels, changed); } } diff --git a/avaje-simple-logger/src/test/java/io/avaje/simplelogger/dynamic/DynamicLogLevelsTest.java b/avaje-simple-logger/src/test/java/io/avaje/simplelogger/dynamic/DynamicLogLevelsTest.java index a742ccb..717f09d 100644 --- a/avaje-simple-logger/src/test/java/io/avaje/simplelogger/dynamic/DynamicLogLevelsTest.java +++ b/avaje-simple-logger/src/test/java/io/avaje/simplelogger/dynamic/DynamicLogLevelsTest.java @@ -1,6 +1,8 @@ package io.avaje.simplelogger.dynamic; +import io.avaje.config.Config; +import io.avaje.config.Configuration; import io.avaje.simplelogger.LoggerContext; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -8,6 +10,8 @@ import java.util.Map; +import static org.assertj.core.api.Assertions.assertThat; + class DynamicLogLevelsTest { static final Logger logFoo = LoggerFactory.getLogger("org.foo.MyFoo"); @@ -15,6 +19,9 @@ class DynamicLogLevelsTest { @Test void test() { + Configuration configuration = Config.asConfiguration(); + assertThat(configuration).isNotNull(); + logFoo.debug("hi foo"); logBar.debug("hi bar before"); @@ -22,6 +29,14 @@ void test() { .putAll(Map.of("org.bar.extra", "trace")); logBar.debug("hi bar after log level change"); + + configuration.putAll(Map.of( + "junk", "junk", + "log.level.org.foo", "debug", + "log.level.org.bar.extra", "warn")); + + logBar.debug("hi bar after dynamic log level change"); + logFoo.debug("hi foo last"); } } diff --git a/avaje-simple-logger/src/test/resources/avaje-logger-test.properties b/avaje-simple-logger/src/test/resources/avaje-logger-test.properties index 2301925..c404140 100644 --- a/avaje-simple-logger/src/test/resources/avaje-logger-test.properties +++ b/avaje-simple-logger/src/test/resources/avaje-logger-test.properties @@ -1,4 +1,5 @@ ## can set config including log levels here log.level.org.foo=trace -log.level.io.avaje.config=trace \ No newline at end of file +log.level.io.avaje.config=trace +log.level.io.avaje.simplelogger=debug