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;
*/
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)
private final LogEntryPublisher publisher;
private PrintStreamSubscriber synchronousSubscriber;
+ private PrintStream fileOut;
private final boolean journald;
private final Level callLocationLevel;
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"));
// silent
}
}
+
+ if (fileOut != null) {
+ try {
+ fileOut.close();
+ } catch (Exception e) {
+ // silent
+ }
+ }
}
private Level computeApplicableLevel(String name) {
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<String, Serializable> 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
*/
// 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
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) {
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<String, Serializable> 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) {
+//
+// }
}