Working multi RCP platform
[lgpl/argeo-commons.git] / org.argeo.init / src / org / argeo / init / logging / ThinLogging.java
index 5b2c93924505112c9d4f4bade1ac7b6fcf88152f..dd6fad2e3a5a54c261517b02f720cacf9f925278 100644 (file)
@@ -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;
@@ -24,8 +29,8 @@ import java.util.concurrent.TimeUnit;
 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
@@ -43,10 +48,12 @@ class ThinLogging implements Consumer<Map<String, Object>> {
         */
        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)
@@ -61,12 +68,13 @@ class ThinLogging implements Consumer<Map<String, Object>> {
        // 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;
@@ -74,13 +82,29 @@ class ThinLogging implements Consumer<Map<String, Object>> {
        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"));
 
@@ -126,7 +150,7 @@ class ThinLogging implements Consumer<Map<String, Object>> {
        }
 
        private void close() {
-               RuntimeContext runtimeContext = Service.getRuntimeContext();
+               RuntimeContext runtimeContext = InternalState.getMainRuntimeContext();
                if (runtimeContext != null) {
                        try {
                                runtimeContext.waitForStop(0);
@@ -146,6 +170,14 @@ class ThinLogging implements Consumer<Map<String, Object>> {
                                // silent
                        }
                }
+
+               if (fileOut != null) {
+                       try {
+                               fileOut.close();
+                       } catch (Exception e) {
+                               // silent
+                       }
+               }
        }
 
        private Level computeApplicableLevel(String name) {
@@ -159,9 +191,11 @@ class ThinLogging implements Consumer<Map<String, Object>> {
        }
 
        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);
        }
@@ -220,6 +254,45 @@ class ThinLogging implements Consumer<Map<String, Object>> {
                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
         */
@@ -268,7 +341,7 @@ class ThinLogging implements Consumer<Map<String, Object>> {
                        // 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 +368,8 @@ class ThinLogging implements Consumer<Map<String, Object>> {
                                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) {
@@ -321,11 +395,11 @@ class ThinLogging implements Consumer<Map<String, Object>> {
                                        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:
@@ -353,40 +427,10 @@ class ThinLogging implements Consumer<Map<String, Object>> {
                        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) {
+//
+//             }
 
        }
 
@@ -567,24 +611,24 @@ class ThinLogging implements Consumer<Map<String, Object>> {
                }
        }
 
-       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
+//             }
+//
+//     }
 
 }