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;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
-import org.argeo.init.RuntimeContext;
-import org.argeo.init.Service;
+import org.argeo.api.init.RuntimeContext;
+import org.argeo.internal.init.InternalState;
/**
* A thin logging system based on the {@link Logger} framework. It is a
*/
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)
// 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 SortedMap<String, ThinLogger> loggers = Collections.synchronizedSortedMap(new TreeMap<>());
private NavigableMap<String, Level> levels = new TreeMap<>();
private volatile boolean updatingConfiguration = false;
private final LogEntryPublisher publisher;
private PrintStreamSubscriber synchronousSubscriber;
+ private PrintStream fileOut;
private final boolean journald;
private final Level callLocationLevel;
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"));
}
private void close() {
- RuntimeContext runtimeContext = Service.getRuntimeContext();
+ RuntimeContext runtimeContext = InternalState.getMainRuntimeContext();
if (runtimeContext != null) {
try {
runtimeContext.waitForStop(0);
// silent
}
}
+
+ if (fileOut != null) {
+ try {
+ fileOut.close();
+ } catch (Exception e) {
+ // silent
+ }
+ }
}
private Level computeApplicableLevel(String name) {
}
public Logger getLogger(String name, Module module) {
+ Objects.requireNonNull(name, "logger name");
if (!loggers.containsKey(name)) {
ThinLogger logger = new ThinLogger(name, computeApplicableLevel(name));
loggers.put(name, logger);
+ return logger;
}
return loggers.get(name);
}
case "org.osgi.service.log.Logger":
case "org.eclipse.osgi.internal.log.LoggerImpl":
case "org.argeo.api.cms.CmsLog":
- case "org.argeo.init.osgi.OsgiBootUtils":
- case "org.slf4j.impl.ArgeoLogger":
- case "org.argeo.cms.internal.osgi.CmsOsgiLogger":
case "org.eclipse.jetty.util.log.Slf4jLog":
case "sun.util.logging.internal.LoggingProviderImpl$JULWrapper":
+ case "org.slf4j.impl.ArgeoLogger":
+ case "org.argeo.cms.internal.osgi.CmsOsgiLogger":
+ case "org.argeo.init.osgi.OsgiBootUtils":
lowestLoggerInterface = i;
continue stack;
default:
}
}
- public static void main(String args[]) {
- Logger logger = System.getLogger(ThinLogging.class.getName());
- logger.log(Logger.Level.ALL, "Log all");
- logger.log(Logger.Level.TRACE, "Multi\nline\ntrace");
- logger.log(Logger.Level.DEBUG, "Log debug");
- logger.log(Logger.Level.INFO, "Log info");
- logger.log(Logger.Level.WARNING, "Log warning");
- logger.log(Logger.Level.ERROR, "Log exception", new Throwable());
-
- try {
- // we ait a bit in order to make sure all messages are flushed
- // TODO synchronize more efficiently
- // executor.awaitTermination(300, TimeUnit.MILLISECONDS);
- ForkJoinPool.commonPool().awaitTermination(300, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- // silent
- }
-
- }
+// public static void main(String args[]) {
+// Logger logger = System.getLogger(ThinLogging.class.getName());
+// logger.log(Logger.Level.ALL, "Log all");
+// logger.log(Logger.Level.TRACE, "Multi\nline\ntrace");
+// logger.log(Logger.Level.DEBUG, "Log debug");
+// logger.log(Logger.Level.INFO, "Log info");
+// logger.log(Logger.Level.WARNING, "Log warning");
+// logger.log(Logger.Level.ERROR, "Log exception", new Throwable());
+//
+// try {
+// // we wait a bit in order to make sure all messages are flushed
+// // TODO synchronize more efficiently
+// // executor.awaitTermination(300, TimeUnit.MILLISECONDS);
+// ForkJoinPool.commonPool().awaitTermination(300, TimeUnit.MILLISECONDS);
+// } catch (InterruptedException e) {
+// // silent
+// }
+//
+// }
}