X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;f=org.argeo.init%2Fsrc%2Forg%2Fargeo%2Finit%2Flogging%2FThinLogging.java;h=ff602ad51a7c06abe532fc5af3111f9c18870a93;hb=1a65fa72abac6a458139240efa281138aac9ef2b;hp=5b2c93924505112c9d4f4bade1ac7b6fcf88152f;hpb=3b45f571938e0eb6803084aac3f2bd298e6026ba;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 5b2c93924..ff602ad51 100644 --- a/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java +++ b/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java @@ -1,9 +1,14 @@ package org.argeo.init.logging; +import java.io.IOException; import java.io.PrintStream; import java.io.Serializable; import java.lang.System.Logger; import java.lang.System.Logger.Level; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.text.MessageFormat; import java.time.Instant; import java.util.Collections; @@ -43,10 +48,12 @@ class ThinLogging implements Consumer> { */ final static String PROP_ARGEO_LOGGING_SYNCHRONOUS = "argeo.logging.synchronous"; /** - * Whether to enable jounrald compatible output, either: auto (default), true, + * Whether to enable journald compatible output, either: auto (default), true, * or false. */ final static String PROP_ARGEO_LOGGING_JOURNALD = "argeo.logging.journald"; + /** A file to which additionally write log entries. */ + final static String PROP_ARGEO_LOGGING_FILE = "argeo.logging.file"; /** * The level from which call location (that is, line number in Java code) will * be searched (default is WARNING) @@ -67,6 +74,7 @@ class ThinLogging implements Consumer> { private final LogEntryPublisher publisher; private PrintStreamSubscriber synchronousSubscriber; + private PrintStream fileOut; private final boolean journald; private final Level callLocationLevel; @@ -74,13 +82,29 @@ class ThinLogging implements Consumer> { private final boolean synchronous; ThinLogging() { - publisher = new LogEntryPublisher(); synchronous = Boolean.parseBoolean(System.getProperty(PROP_ARGEO_LOGGING_SYNCHRONOUS, "false")); if (synchronous) { synchronousSubscriber = new PrintStreamSubscriber(); + publisher = null; } else { PrintStreamSubscriber subscriber = new PrintStreamSubscriber(); + publisher = new LogEntryPublisher(); publisher.subscribe(subscriber); + String logFileStr = System.getProperty(PROP_ARGEO_LOGGING_FILE); + if (logFileStr != null) { + Path logFilePath = Paths.get(logFileStr); + if (!Files.exists(logFilePath.getParent())) { + System.err.println("Parent directory of " + logFilePath + " does not exist"); + } else { + try { + fileOut = new PrintStream(Files.newOutputStream(logFilePath), true, StandardCharsets.UTF_8); + publisher.subscribe(new PrintStreamSubscriber(fileOut, fileOut)); + } catch (IOException e) { + System.err.println("Cannot write log to " + logFilePath); + e.printStackTrace(); + } + } + } } Runtime.getRuntime().addShutdownHook(new Thread(() -> close(), "Log shutdown")); @@ -146,6 +170,14 @@ class ThinLogging implements Consumer> { // silent } } + + if (fileOut != null) { + try { + fileOut.close(); + } catch (Exception e) { + // silent + } + } } private Level computeApplicableLevel(String name) { @@ -220,6 +252,45 @@ class ThinLogging implements Consumer> { return Collections.unmodifiableNavigableMap(levels); } + private void dispatchLogEntry(ThinLogger logger, Level level, ResourceBundle bundle, String msg, Instant instant, + Thread thread, Throwable thrown, StackTraceElement callLocation) { + assert level != null; + assert logger != null; +// assert msg != null; + assert instant != null; + assert thread != null; + + if (msg == null) + msg = "null"; + + final long sequence = nextEntry.incrementAndGet(); + + Map logEntry = new LogEntryMap(sequence); + + // same object as key class name + logEntry.put(KEY_LEVEL, level); + logEntry.put(KEY_MSG, msg); + logEntry.put(KEY_INSTANT, instant); + if (thrown != null) + logEntry.put(KEY_THROWABLE, thrown); + if (callLocation != null) + logEntry.put(KEY_CALL_LOCATION, callLocation); + + // object is a string + logEntry.put(KEY_LOGGER, logger.getName()); + logEntry.put(KEY_THREAD, thread.getName()); + + // should be unmodifiable for security reasons + if (synchronous) { + assert synchronousSubscriber != null; + synchronousSubscriber.onNext(logEntry); + } else { + if (!publisher.isClosed()) + publisher.submit(Collections.unmodifiableMap(logEntry)); + } + + } + /* * INTERNAL CLASSES */ @@ -268,7 +339,7 @@ class ThinLogging implements Consumer> { // measure timestamp first Instant now = Instant.now(); Thread thread = Thread.currentThread(); - publisher.log(this, level, bundle, msg, now, thread, thrown, findCallLocation(level, thread)); + dispatchLogEntry(ThinLogger.this, level, bundle, msg, now, thread, thrown, findCallLocation(level, thread)); } @Override @@ -295,7 +366,8 @@ class ThinLogging implements Consumer> { format = sb.toString(); } String msg = params == null ? format : MessageFormat.format(format, params); - publisher.log(this, level, bundle, msg, now, thread, (Throwable) null, findCallLocation(level, thread)); + dispatchLogEntry(ThinLogger.this, level, bundle, msg, now, thread, (Throwable) null, + findCallLocation(level, thread)); } private void setLevel(Level level) { @@ -353,40 +425,10 @@ class ThinLogging implements Consumer> { super(); } - private void log(ThinLogger logger, Level level, ResourceBundle bundle, String msg, Instant instant, - Thread thread, Throwable thrown, StackTraceElement callLocation) { - assert level != null; - assert logger != null; - assert msg != null; - assert instant != null; - assert thread != null; - - final long sequence = nextEntry.incrementAndGet(); - - Map logEntry = new LogEntryMap(sequence); - - // same object as key class name - logEntry.put(KEY_LEVEL, level); - logEntry.put(KEY_MSG, msg); - logEntry.put(KEY_INSTANT, instant); - if (thrown != null) - logEntry.put(KEY_THROWABLE, thrown); - if (callLocation != null) - logEntry.put(KEY_CALL_LOCATION, callLocation); - - // object is a string - logEntry.put(KEY_LOGGER, logger.getName()); - logEntry.put(KEY_THREAD, thread.getName()); - - // should be unmodifiable for security reasons - if (synchronous) { - assert synchronousSubscriber != null; - synchronousSubscriber.onNext(logEntry); - } else { - if (!isClosed()) - submit(Collections.unmodifiableMap(logEntry)); - } - } +// private void log(ThinLogger logger, Level level, ResourceBundle bundle, String msg, Instant instant, +// Thread thread, Throwable thrown, StackTraceElement callLocation) { +// +// } }