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=e7edc19a89e9858b9a7ee0d1ba9eb0e21f97f45c;hpb=b7d8618ce593bbeca7e311d32a4d98988e27f877;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 e7edc19a8..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,19 +24,22 @@ import java.util.concurrent.TimeUnit; /** A thin logging system based on the {@link Logger} framework. */ class ThinLogging { -// private static ThinLogging instance; + 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; ThinLogging() { -// if (instance != null) -// throw new IllegalStateException("Only one logger finder cann be instantiated"); -// instance = this; - executor = Executors.newCachedThreadPool((r) -> { Thread t = new Thread(r); t.setDaemon(true); @@ -46,6 +51,9 @@ class ThinLogging { publisher.subscribe(subscriber); Runtime.getRuntime().addShutdownHook(new Thread(() -> close(), "Log shutdown")); + + // initial default level + levels.put("", Level.WARNING); } protected void close() { @@ -59,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(); @@ -80,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; @@ -96,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); } @@ -110,25 +176,29 @@ class ThinLogging { public void log(Level level, ResourceBundle bundle, String format, Object... params) { // measure timestamp first Instant now = Instant.now(); - String msg = MessageFormat.format(format, params); + + // 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()); } 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": case "org.osgi.service.log.Logger": case "org.argeo.cms.Log": + case "org.slf4j.impl.ArgeoLogger": + case "org.eclipse.jetty.util.log.Slf4jLog": lowestLoggerInterface = i; continue stack; default: