+++ /dev/null
-package org.argeo.init.logging;
-
-import java.lang.System.Logger.Level;
-import java.util.logging.Handler;
-import java.util.logging.LogRecord;
-
-/**
- * A fallback {@link Handler} forwarding only messages and logger name (all
- * other {@link LogRecord} information is lost.
- */
-class ThinHandler extends Handler {
- @Override
- public void publish(LogRecord record) {
- java.lang.System.Logger systemLogger = ThinLoggerFinder.getLogger(record.getLoggerName());
- systemLogger.log(fromJulLevel(record.getLevel()), record.getMessage());
- }
-
- protected Level fromJulLevel(java.util.logging.Level julLevel) {
- if (java.util.logging.Level.ALL.equals(julLevel))
- return Level.ALL;
- else if (java.util.logging.Level.FINER.equals(julLevel))
- return Level.TRACE;
- else if (java.util.logging.Level.FINE.equals(julLevel))
- return Level.DEBUG;
- else if (java.util.logging.Level.INFO.equals(julLevel))
- return Level.INFO;
- else if (java.util.logging.Level.WARNING.equals(julLevel))
- return Level.WARNING;
- else if (java.util.logging.Level.SEVERE.equals(julLevel))
- return Level.ERROR;
- else if (java.util.logging.Level.OFF.equals(julLevel))
- return Level.OFF;
- else
- throw new IllegalArgumentException("Unsupported JUL level " + julLevel);
- }
-
- @Override
- public void flush() {
- }
-
- @Override
- public void close() throws SecurityException {
- }
-
-}
\ No newline at end of file
package org.argeo.init.logging;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.lang.System.Logger.Level;
+import java.util.Map;
+import java.util.Properties;
+import java.util.logging.Handler;
import java.util.logging.LogManager;
-import java.util.logging.Logger;
+import java.util.logging.LogRecord;
/**
- * Fallback wrapper around the java.util.logging framework, when thinb logging
+ * Fallback wrapper around the java.util.logging framework, when thin logging
* could not be instantiated directly.
*/
class ThinJavaUtilLogging {
- public static void init() {
+ private LogManager logManager;
+
+ private ThinJavaUtilLogging(LogManager logManager) {
+ this.logManager = logManager;
+ this.logManager.reset();
+ }
+
+ static ThinJavaUtilLogging init() {
LogManager logManager = LogManager.getLogManager();
- logManager.reset();
- Logger rootLogger = logManager.getLogger("");
- rootLogger.addHandler(new ThinHandler());
- rootLogger.setLevel(java.util.logging.Level.INFO);
+ ThinJavaUtilLogging thinJul = new ThinJavaUtilLogging(logManager);
+ return thinJul;
+ }
+
+ private static Level fromJulLevel(java.util.logging.Level julLevel) {
+ if (java.util.logging.Level.ALL.equals(julLevel))
+ return Level.ALL;
+ else if (java.util.logging.Level.FINER.equals(julLevel))
+ return Level.TRACE;
+ else if (java.util.logging.Level.FINE.equals(julLevel))
+ return Level.DEBUG;
+ else if (java.util.logging.Level.INFO.equals(julLevel))
+ return Level.INFO;
+ else if (java.util.logging.Level.WARNING.equals(julLevel))
+ return Level.WARNING;
+ else if (java.util.logging.Level.SEVERE.equals(julLevel))
+ return Level.ERROR;
+ else if (java.util.logging.Level.OFF.equals(julLevel))
+ return Level.OFF;
+ else
+ throw new IllegalArgumentException("Unsupported JUL level " + julLevel);
+ }
+
+ private static java.util.logging.Level toJulLevel(Level level) {
+ if (Level.ALL.equals(level))
+ return java.util.logging.Level.ALL;
+ else if (Level.TRACE.equals(level))
+ return java.util.logging.Level.FINER;
+ else if (Level.DEBUG.equals(level))
+ return java.util.logging.Level.FINE;
+ else if (Level.INFO.equals(level))
+ return java.util.logging.Level.INFO;
+ else if (Level.WARNING.equals(level))
+ return java.util.logging.Level.WARNING;
+ else if (Level.ERROR.equals(level))
+ return java.util.logging.Level.SEVERE;
+ else if (Level.OFF.equals(level))
+ return java.util.logging.Level.OFF;
+ else
+ throw new IllegalArgumentException("Unsupported logging level " + level);
+ }
+
+ void readConfiguration(Map<String, Level> configuration) {
+ this.logManager.reset();
+ Properties properties = new Properties();
+ for (String name : configuration.keySet()) {
+ properties.put(name + ".level", toJulLevel(configuration.get(name)).toString());
+ }
+ try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+ properties.store(out, null);
+ try (ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray())) {
+ logManager.readConfiguration(in);
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot apply JUL configuration", e);
+ }
+ logManager.getLogger("").addHandler(new ThinHandler());
+ }
+
+ /**
+ * A fallback {@link Handler} forwarding only messages and logger name (all
+ * other {@link LogRecord} information is lost.
+ */
+ private static class ThinHandler extends Handler {
+ @Override
+ public void publish(LogRecord record) {
+ java.lang.System.Logger systemLogger = ThinLoggerFinder.getLogger(record.getLoggerName());
+ systemLogger.log(ThinJavaUtilLogging.fromJulLevel(record.getLevel()), record.getMessage());
+ }
+
+ @Override
+ public void flush() {
+ }
+
+ @Override
+ public void close() throws SecurityException {
+ }
+
}
}
import java.lang.System.Logger;
import java.lang.System.LoggerFinder;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
-/** Factory for Java system logging. */
+/**
+ * Factory for Java system logging. As it has to be a public class in order to
+ * be exposed as a service provider, it is also the main entry point for the
+ * thin logging system, via static methos.
+ */
public class ThinLoggerFinder extends LoggerFinder {
private static ThinLogging logging;
+ private static ThinJavaUtilLogging javaUtilLogging;
public ThinLoggerFinder() {
if (logging != null)
throw new IllegalStateException("Only one logging can be initialised.");
- logging = new ThinLogging();
+ init();
}
@Override
return logging.getLogger(name, module);
}
+ private static void init() {
+ logging = new ThinLogging();
+
+ Map<String, String> configuration = new HashMap<>();
+ for (Object key : System.getProperties().keySet()) {
+ Objects.requireNonNull(key);
+ String property = key.toString();
+ if (property.startsWith(ThinLogging.LEVEL_PROPERTY_PREFIX)
+ || property.equals(ThinLogging.DEFAULT_LEVEL_PROPERTY))
+ configuration.put(property, System.getProperty(property));
+ }
+ logging.update(configuration);
+ }
+
/**
* Falls back to java.util.logging if thin logging was not already initialised.
*/
public static void lazyInit() {
if (logging != null)
return;
- logging = new ThinLogging();
- ThinJavaUtilLogging.init();
+ if (javaUtilLogging != null)
+ return;
+ init();
+ javaUtilLogging = ThinJavaUtilLogging.init();
+ javaUtilLogging.readConfiguration(logging.getLevels());
+ }
+
+ public static void update(Map<String, String> configuration) {
+ if (logging == null)
+ throw new IllegalStateException("Thin logging must be initialized first");
+ logging.update(configuration);
+ if (javaUtilLogging != null)
+ javaUtilLogging.readConfiguration(logging.getLevels());
}
- public static Logger getLogger(String name) {
+ static Logger getLogger(String name) {
return logging.getLogger(name, null);
}
}
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;
/** 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<String, ThinLogger> loggers = new TreeMap<>();
private NavigableMap<String, Level> levels = new TreeMap<>();
+ private volatile boolean updatingConfiguration = false;
private final ExecutorService executor;
private final LogEntryPublisher publisher;
Runtime.getRuntime().addShutdownHook(new Thread(() -> close(), "Log shutdown"));
- setDefaultLevel(Level.INFO);
+ // initial default level
+ levels.put("", Level.WARNING);
}
protected void close() {
}
}
- 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<String, Level> entry = levels.ceilingEntry(name);
assert entry != null;
return level.getSeverity() >= entry.getValue().getSeverity();
return loggers.get(name);
}
+ void update(Map<String, String> configuration) {
+ synchronized (levels) {
+ updatingConfiguration = true;
+
+ Map<String, Level> 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<Map.Entry<String, Level>> it = levels.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<String, Level> 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<String, Level> getLevels() {
+ return Collections.unmodifiableNavigableMap(levels);
+ }
+
class ThinLogger implements System.Logger {
private final String name;
private boolean callLocationEnabled = true;
@Override
public boolean isLoggable(Level level) {
+ // TODO optimise by referencing the applicable level in this class?
return ThinLogging.this.isLoggable(name, level);
}
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());
}
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":