X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;f=org.argeo.init%2Fsrc%2Forg%2Fargeo%2Finit%2Flogging%2FThinLogging.java;h=22c1c779b40363bd9ede703ad641d2839d791c2d;hb=8be06a167888a8ba1f262bbe34c70385807d11c0;hp=2c2b04d2c7952e50006e3b05c39cb0bca8bf7abf;hpb=938f9575c8091c723fa076f3c962e4b29556d2c5;p=lgpl%2Fargeo-commons.git diff --git a/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java b/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java index 2c2b04d2c..22c1c779b 100644 --- a/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java +++ b/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java @@ -5,6 +5,8 @@ import java.lang.System.Logger; import java.lang.System.Logger.Level; import java.text.MessageFormat; import java.time.Instant; +import java.util.Collections; +import java.util.Iterator; import java.util.Map; import java.util.NavigableMap; import java.util.Objects; @@ -22,8 +24,17 @@ import java.util.concurrent.TimeUnit; /** A thin logging system based on the {@link Logger} framework. */ class ThinLogging { + final static String DEFAULT_LEVEL_NAME = ""; + + final static String DEFAULT_LEVEL_PROPERTY = "log"; + final static String LEVEL_PROPERTY_PREFIX = DEFAULT_LEVEL_PROPERTY + "."; + + // we don't synchronize maps on purpose as it would be + // too expensive during normal operation + // updates to the config may be shortly inconsistent private SortedMap loggers = new TreeMap<>(); private NavigableMap levels = new TreeMap<>(); + private volatile boolean updatingConfiguration = false; private final ExecutorService executor; private final LogEntryPublisher publisher; @@ -41,7 +52,8 @@ class ThinLogging { Runtime.getRuntime().addShutdownHook(new Thread(() -> close(), "Log shutdown")); - setDefaultLevel(Level.INFO); + // initial default level + levels.put("", Level.WARNING); } protected void close() { @@ -55,14 +67,21 @@ class ThinLogging { } } - public void setDefaultLevel(Level level) { - levels.put("", level); - } - public boolean isLoggable(String name, Level level) { Objects.requireNonNull(name); Objects.requireNonNull(level); + if (updatingConfiguration) { + synchronized (levels) { + try { + levels.wait(); + // TODO make exit more robust + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + } + } + Map.Entry entry = levels.ceilingEntry(name); assert entry != null; return level.getSeverity() >= entry.getValue().getSeverity(); @@ -76,6 +95,56 @@ class ThinLogging { return loggers.get(name); } + void update(Map configuration) { + synchronized (levels) { + updatingConfiguration = true; + + Map backup = new TreeMap<>(levels); + + boolean fullReset = configuration.containsKey(DEFAULT_LEVEL_PROPERTY); + try { + properties: for (String property : configuration.keySet()) { + if (!property.startsWith(LEVEL_PROPERTY_PREFIX)) + continue properties; + String levelStr = configuration.get(property); + Level level = Level.valueOf(levelStr); + levels.put(property.substring(LEVEL_PROPERTY_PREFIX.length()), level); + } + + if (fullReset) { + Iterator> it = levels.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = it.next(); + String name = entry.getKey(); + if (!configuration.containsKey(LEVEL_PROPERTY_PREFIX + name)) { + it.remove(); + } + } +// for (String name : levels.keySet()) { +// if (!configuration.containsKey(LEVEL_PROPERTY_PREFIX + name)) { +// levels.remove(name); +// } +// } + Level newDefaultLevel = Level.valueOf(configuration.get(DEFAULT_LEVEL_PROPERTY)); + levels.put(DEFAULT_LEVEL_NAME, newDefaultLevel); + // TODO notify everyone? + } + assert levels.containsKey(DEFAULT_LEVEL_NAME); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + levels.clear(); + levels.putAll(backup); + } + updatingConfiguration = false; + levels.notifyAll(); + } + + } + + Map getLevels() { + return Collections.unmodifiableNavigableMap(levels); + } + class ThinLogger implements System.Logger { private final String name; private boolean callLocationEnabled = true; @@ -92,6 +161,7 @@ class ThinLogging { @Override public boolean isLoggable(Level level) { + // TODO optimise by referencing the applicable level in this class? return ThinLogging.this.isLoggable(name, level); } @@ -106,6 +176,9 @@ class ThinLogging { public void log(Level level, ResourceBundle bundle, String format, Object... params) { // measure timestamp first Instant now = Instant.now(); + + // NOTE: this is the method called when logging a plain message without + // exception, so it should be considered as a format only when args are not null String msg = params == null ? format : MessageFormat.format(format, params); publisher.log(this, level, bundle, msg, null, now, findCallLocation()); } @@ -113,14 +186,12 @@ class ThinLogging { protected StackTraceElement findCallLocation() { StackTraceElement callLocation = null; if (callLocationEnabled) { -// Throwable locator = new Throwable(); -// StackTraceElement[] stack = locator.getStackTrace(); StackTraceElement[] stack = Thread.currentThread().getStackTrace(); - // TODO make it smarter by finding the lowest logger interface in the stack int lowestLoggerInterface = 0; stack: for (int i = 2; i < stack.length; i++) { String className = stack[i].getClassName(); switch (className) { + // TODO make it more configurable case "java.lang.System$Logger": case "java.util.logging.Logger": case "org.apache.commons.logging.Log":