Make logging configurable
authorMathieu Baudier <mbaudier@argeo.org>
Fri, 31 Dec 2021 10:25:40 +0000 (11:25 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Fri, 31 Dec 2021 10:25:40 +0000 (11:25 +0100)
org.argeo.init/src/org/argeo/init/logging/ThinLogEntry.java
org.argeo.init/src/org/argeo/init/logging/ThinLoggerFinder.java
org.argeo.init/src/org/argeo/init/logging/ThinLogging.java
org.argeo.init/src/org/argeo/init/osgi/Activator.java
org.argeo.init/src/org/argeo/init/osgi/AdminThread.java
org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeContext.java

index c71fb6d277afb493b89fb848e32067c66f89489f..51aadec32af4b96133d8a25a061f98621a10e6c8 100644 (file)
@@ -9,6 +9,7 @@ import java.util.Optional;
 import java.util.concurrent.atomic.AtomicLong;
 
 /** A log entry with equals semantics based on an incremental long sequence. */
+@Deprecated
 class ThinLogEntry implements Serializable {
        private static final long serialVersionUID = 5915553445193937270L;
 
index 4147534dd2695abcdc393f7c988ef49965ddc4df..f443b2e669d28aa9599716e126adb24397db6f66 100644 (file)
@@ -1,10 +1,13 @@
 package org.argeo.init.logging;
 
+import java.io.Serializable;
 import java.lang.System.Logger;
 import java.lang.System.LoggerFinder;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.Flow;
+import java.util.function.Consumer;
 
 /**
  * Factory for Java system logging. As it has to be a public class in order to
@@ -29,7 +32,7 @@ public class ThinLoggerFinder extends LoggerFinder {
        private static void init() {
                logging = new ThinLogging();
 
-               Map<String, String> configuration = new HashMap<>();
+               Map<String, Object> configuration = new HashMap<>();
                for (Object key : System.getProperties().keySet()) {
                        Objects.requireNonNull(key);
                        String property = key.toString();
@@ -37,11 +40,12 @@ public class ThinLoggerFinder extends LoggerFinder {
                                        || property.equals(ThinLogging.DEFAULT_LEVEL_PROPERTY))
                                configuration.put(property, System.getProperty(property));
                }
-               logging.update(configuration);
+               logging.accept(configuration);
        }
 
        /**
-        * Falls back to java.util.logging if thin logging was not already initialised.
+        * Falls back to java.util.logging if thin logging was not already initialised
+        * by the {@link LoggerFinder} mechanism.
         */
        public static void lazyInit() {
                if (logging != null)
@@ -53,10 +57,20 @@ public class ThinLoggerFinder extends LoggerFinder {
                javaUtilLogging.readConfiguration(logging.getLevels());
        }
 
-       public static void update(Map<String, String> configuration) {
+       public static Consumer<Map<String, Object>> getConfigurationConsumer() {
+               Objects.requireNonNull(logging);
+               return logging;
+       }
+
+       public static Flow.Publisher<Map<String, Serializable>> getLogEntryPublisher() {
+               Objects.requireNonNull(logging);
+               return logging.getLogEntryPublisher();
+       }
+
+       static void update(Map<String, Object> configuration) {
                if (logging == null)
                        throw new IllegalStateException("Thin logging must be initialized first");
-               logging.update(configuration);
+               logging.accept(configuration);
                if (javaUtilLogging != null)
                        javaUtilLogging.readConfiguration(logging.getLevels());
        }
index 22c1c779b40363bd9ede703ad641d2839d791c2d..9866a1f232e2a218d9c98d8699b089f619ca7d01 100644 (file)
@@ -1,11 +1,13 @@
 package org.argeo.init.logging;
 
 import java.io.PrintStream;
+import java.io.Serializable;
 import java.lang.System.Logger;
 import java.lang.System.Logger.Level;
 import java.text.MessageFormat;
 import java.time.Instant;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.NavigableMap;
@@ -21,14 +23,24 @@ import java.util.concurrent.Flow;
 import java.util.concurrent.Flow.Subscription;
 import java.util.concurrent.SubmissionPublisher;
 import java.util.concurrent.TimeUnit;
-
-/** A thin logging system based on the {@link Logger} framework. */
-class ThinLogging {
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
+
+/**
+ * A thin logging system based on the {@link Logger} framework. It is a
+ * {@link Consumer} of configuration, and can be registered as such.
+ */
+class ThinLogging implements Consumer<Map<String, Object>> {
        final static String DEFAULT_LEVEL_NAME = "";
 
        final static String DEFAULT_LEVEL_PROPERTY = "log";
        final static String LEVEL_PROPERTY_PREFIX = DEFAULT_LEVEL_PROPERTY + ".";
 
+       final static String JOURNALD_PROPERTY = "argeo.logging.journald";
+       final static String CALL_LOCATION_PROPERTY = "argeo.logging.callLocation";
+
+       private final static AtomicLong nextEntry = new AtomicLong(0l);
+
        // we don't synchronize maps on purpose as it would be
        // too expensive during normal operation
        // updates to the config may be shortly inconsistent
@@ -39,6 +51,9 @@ class ThinLogging {
        private final ExecutorService executor;
        private final LogEntryPublisher publisher;
 
+       private final boolean journald;
+       private final Level callLocationLevel;
+
        ThinLogging() {
                executor = Executors.newCachedThreadPool((r) -> {
                        Thread t = new Thread(r);
@@ -54,9 +69,52 @@ class ThinLogging {
 
                // initial default level
                levels.put("", Level.WARNING);
+
+               // Logging system config
+               // journald
+
+//             Map<String, String> env = new TreeMap<>(System.getenv());
+//             for (String key : env.keySet()) {
+//                     System.out.println(key + "=" + env.get(key));
+//             }
+
+               String journaldStr = System.getProperty(JOURNALD_PROPERTY, "auto");
+               switch (journaldStr) {
+               case "auto":
+                       String systemdInvocationId = System.getenv("INVOCATION_ID");
+                       if (systemdInvocationId != null) {// in systemd
+                               // check whether we are indirectly in a desktop app (e.g. eclipse)
+                               String desktopFilePid = System.getenv("GIO_LAUNCHED_DESKTOP_FILE_PID");
+                               if (desktopFilePid != null) {
+                                       Long javaPid = ProcessHandle.current().pid();
+                                       if (!javaPid.toString().equals(desktopFilePid)) {
+                                               journald = false;
+                                               break;
+                                       }
+                               }
+                               journald = true;
+                               break;
+                       }
+                       journald = false;
+                       break;
+               case "true":
+               case "on":
+                       journald = true;
+                       break;
+               case "false":
+               case "off":
+                       journald = false;
+                       break;
+               default:
+                       throw new IllegalArgumentException(
+                                       "Unsupported value '" + journaldStr + "' for property " + JOURNALD_PROPERTY);
+               }
+
+               String callLocationStr = System.getProperty(CALL_LOCATION_PROPERTY, Level.WARNING.getName());
+               callLocationLevel = Level.valueOf(callLocationStr);
        }
 
-       protected void close() {
+       private void close() {
                publisher.close();
                try {
                        // we ait a bit in order to make sure all messages are flushed
@@ -67,35 +125,40 @@ class ThinLogging {
                }
        }
 
-       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);
+       private Level computeApplicableLevel(String name) {
+               Map.Entry<String, Level> entry = levels.floorEntry(name);
                assert entry != null;
-               return level.getSeverity() >= entry.getValue().getSeverity();
+               return entry.getValue();
+
        }
 
+//     private 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);
+//                             }
+//                     }
+//             }
+//
+//             return level.getSeverity() >= computeApplicableLevel(name).getSeverity();
+//     }
+
        public Logger getLogger(String name, Module module) {
                if (!loggers.containsKey(name)) {
-                       ThinLogger logger = new ThinLogger(name);
+                       ThinLogger logger = new ThinLogger(name, computeApplicableLevel(name));
                        loggers.put(name, logger);
                }
                return loggers.get(name);
        }
 
-       void update(Map<String, String> configuration) {
+       public void accept(Map<String, Object> configuration) {
                synchronized (levels) {
                        updatingConfiguration = true;
 
@@ -106,7 +169,7 @@ class ThinLogging {
                                properties: for (String property : configuration.keySet()) {
                                        if (!property.startsWith(LEVEL_PROPERTY_PREFIX))
                                                continue properties;
-                                       String levelStr = configuration.get(property);
+                                       String levelStr = configuration.get(property).toString();
                                        Level level = Level.valueOf(levelStr);
                                        levels.put(property.substring(LEVEL_PROPERTY_PREFIX.length()), level);
                                }
@@ -120,16 +183,17 @@ class ThinLogging {
                                                        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));
+                                       Level newDefaultLevel = Level.valueOf(configuration.get(DEFAULT_LEVEL_PROPERTY).toString());
                                        levels.put(DEFAULT_LEVEL_NAME, newDefaultLevel);
                                        // TODO notify everyone?
                                }
                                assert levels.containsKey(DEFAULT_LEVEL_NAME);
+
+                               // recompute all levels
+                               for (String name : loggers.keySet()) {
+                                       ThinLogger logger = loggers.get(name);
+                                       logger.setLevel(computeApplicableLevel(name));
+                               }
                        } catch (IllegalArgumentException e) {
                                e.printStackTrace();
                                levels.clear();
@@ -141,17 +205,27 @@ class ThinLogging {
 
        }
 
+       Flow.Publisher<Map<String, Serializable>> getLogEntryPublisher() {
+               return publisher;
+       }
+
        Map<String, Level> getLevels() {
                return Collections.unmodifiableNavigableMap(levels);
        }
 
-       class ThinLogger implements System.Logger {
+       /*
+        * INTERNAL CLASSES
+        */
+       
+       private class ThinLogger implements System.Logger {
                private final String name;
-               private boolean callLocationEnabled = true;
 
-               protected ThinLogger(String name) {
+               private Level level;
+
+               protected ThinLogger(String name, Level level) {
                        assert Objects.nonNull(name);
                        this.name = name;
+                       this.level = level;
                }
 
                @Override
@@ -161,32 +235,59 @@ class ThinLogging {
 
                @Override
                public boolean isLoggable(Level level) {
+                       return level.getSeverity() >= getLevel().getSeverity();
                        // TODO optimise by referencing the applicable level in this class?
-                       return ThinLogging.this.isLoggable(name, level);
+//                     return ThinLogging.this.isLoggable(name, level);
+               }
+
+               private Level getLevel() {
+                       if (updatingConfiguration) {
+                               synchronized (levels) {
+                                       try {
+                                               levels.wait();
+                                               // TODO make exit more robust
+                                       } catch (InterruptedException e) {
+                                               throw new IllegalStateException(e);
+                                       }
+                               }
+                       }
+                       return level;
                }
 
                @Override
                public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
+                       if (!isLoggable(level))
+                               return;
                        // measure timestamp first
                        Instant now = Instant.now();
-                       publisher.log(this, level, bundle, msg, thrown, now, findCallLocation());
+                       Thread thread = Thread.currentThread();
+                       publisher.log(this, level, bundle, msg, now, thread, thrown, findCallLocation(level, thread));
                }
 
                @Override
                public void log(Level level, ResourceBundle bundle, String format, Object... params) {
+                       if (!isLoggable(level))
+                               return;
                        // measure timestamp first
                        Instant now = Instant.now();
+                       Thread thread = Thread.currentThread();
 
                        // 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());
+                       publisher.log(this, level, bundle, msg, now, thread, (Throwable) null, findCallLocation(level, thread));
+               }
+
+               private void setLevel(Level level) {
+                       this.level = level;
                }
 
-               protected StackTraceElement findCallLocation() {
+               private StackTraceElement findCallLocation(Level level, Thread thread) {
+                       assert level != null;
+                       assert thread != null;
                        StackTraceElement callLocation = null;
-                       if (callLocationEnabled) {
-                               StackTraceElement[] stack = Thread.currentThread().getStackTrace();
+                       if (level.getSeverity() >= callLocationLevel.getSeverity()) {
+                               StackTraceElement[] stack = thread.getStackTrace();
                                int lowestLoggerInterface = 0;
                                stack: for (int i = 2; i < stack.length; i++) {
                                        String className = stack[i].getClassName();
@@ -212,27 +313,93 @@ class ThinLogging {
 
        }
 
-       class LogEntryPublisher extends SubmissionPublisher<ThinLogEntry> {
+       private final static String KEY_LOGGER = Logger.class.getName();
+       private final static String KEY_LEVEL = Level.class.getName();
+       private final static String KEY_MSG = String.class.getName();
+       private final static String KEY_THROWABLE = Throwable.class.getName();
+       private final static String KEY_INSTANT = Instant.class.getName();
+       private final static String KEY_CALL_LOCATION = StackTraceElement.class.getName();
+       private final static String KEY_THREAD = Thread.class.getName();
 
-               protected LogEntryPublisher(Executor executor, int maxBufferCapacity) {
+       private class LogEntryPublisher extends SubmissionPublisher<Map<String, Serializable>> {
+
+               private LogEntryPublisher(Executor executor, int maxBufferCapacity) {
                        super(executor, maxBufferCapacity);
                }
 
-               void log(ThinLogger logger, Level level, ResourceBundle bundle, String msg, Throwable thrown, Instant instant,
-                               StackTraceElement callLocation) {
-                       ThinLogEntry logEntry = new ThinLogEntry(logger, level, msg, instant, thrown, callLocation);
-                       submit(logEntry);
+               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
+                       submit(Collections.unmodifiableMap(logEntry));
                }
 
        }
 
-       class PrintStreamSubscriber implements Flow.Subscriber<ThinLogEntry> {
+       /**
+        * An internal optimisation for collections. It should not be referred to
+        * directly as a type.
+        */
+       // TODO optimise memory with a custom map implementation?
+       // but access may be slower
+       private static class LogEntryMap extends HashMap<String, Serializable> {
+               private static final long serialVersionUID = 7361434381922521356L;
+
+               private final long sequence;
+
+               private LogEntryMap(long sequence) {
+                       // maximum 7 fields, so using default load factor 0.75
+                       // an initial size of 10 should prevent rehashing (7 / 0.75 ~ 9.333)
+                       // see HashMap class description for more details
+                       super(10, 0.75f);
+                       this.sequence = sequence;
+               }
+
+               @Override
+               public boolean equals(Object o) {
+                       if (o instanceof LogEntryMap)
+                               return sequence == ((LogEntryMap) o).sequence;
+                       else if (o instanceof Map) {
+                               Map<?, ?> map = (Map<?, ?>) o;
+                               return get(KEY_INSTANT).equals(map.get(KEY_INSTANT)) && get(KEY_THREAD).equals(map.get(KEY_THREAD));
+                       } else
+                               return false;
+               }
+
+               @Override
+               public int hashCode() {
+                       return (int) sequence;
+               }
+
+       }
+
+       private class PrintStreamSubscriber implements Flow.Subscriber<Map<String, Serializable>> {
                private PrintStream out;
                private PrintStream err;
                private int writeToErrLevel = Level.WARNING.getSeverity();
 
-               private boolean journald = false;
-
                protected PrintStreamSubscriber() {
                        this(System.out, System.err);
                }
@@ -242,18 +409,23 @@ class ThinLogging {
                        this.err = err;
                }
 
+               private Level getLevel(Map<String, Serializable> logEntry) {
+                       return (Level) logEntry.get(KEY_LEVEL);
+               }
+
                @Override
                public void onSubscribe(Subscription subscription) {
                        subscription.request(Long.MAX_VALUE);
                }
 
                @Override
-               public void onNext(ThinLogEntry item) {
-                       if (item.getLevel().getSeverity() >= writeToErrLevel) {
+               public void onNext(Map<String, Serializable> item) {
+                       if (getLevel(item).getSeverity() >= writeToErrLevel) {
                                err.print(toPrint(item));
                        } else {
                                out.print(toPrint(item));
                        }
+                       // TODO flush for journald?
                }
 
                @Override
@@ -267,22 +439,37 @@ class ThinLogging {
                        err.flush();
                }
 
-               protected boolean prefixOnEachLine() {
-                       return journald;
-               }
-
-               protected String firstLinePrefix(ThinLogEntry logEntry) {
-                       return journald ? linePrefix(logEntry)
-                                       : logEntry.getLevel().toString() + "\t" + logEntry.getInstant() + " ";
+               protected String firstLinePrefix(Map<String, Serializable> logEntry) {
+                       Level level = getLevel(logEntry);
+                       String spaces;
+                       switch (level) {
+                       case ERROR:
+                       case DEBUG:
+                       case TRACE:
+                               spaces = "   ";
+                               break;
+                       case INFO:
+                               spaces = "    ";
+                               break;
+                       case WARNING:
+                               spaces = " ";
+                               break;
+                       case ALL:
+                               spaces = "     ";
+                               break;
+                       default:
+                               throw new IllegalArgumentException("Unsupported level " + level);
+                       }
+                       return journald ? linePrefix(logEntry) : logEntry.get(KEY_INSTANT) + " " + level + spaces;
                }
 
-               protected String firstLineSuffix(ThinLogEntry logEntry) {
-                       return " - " + (logEntry.getCallLocation().isEmpty() ? logEntry.getLoggerName()
-                                       : logEntry.getCallLocation().get());
+               protected String firstLineSuffix(Map<String, Serializable> logEntry) {
+                       return " - " + (logEntry.containsKey(KEY_CALL_LOCATION) ? logEntry.get(KEY_CALL_LOCATION)
+                                       : logEntry.get(KEY_LOGGER)) + " [" + logEntry.get(KEY_THREAD) + "]";
                }
 
-               protected String linePrefix(ThinLogEntry logEntry) {
-                       return journald ? "<" + levelToJournald(logEntry.getLevel()) + ">" : "";
+               protected String linePrefix(Map<String, Serializable> logEntry) {
+                       return journald ? "<" + levelToJournald(getLevel(logEntry)) + ">" : "";
                }
 
                protected int levelToJournald(Level level) {
@@ -297,9 +484,9 @@ class ThinLogging {
                                return 7;
                }
 
-               protected String toPrint(ThinLogEntry logEntry) {
+               protected String toPrint(Map<String, Serializable> logEntry) {
                        StringBuilder sb = new StringBuilder();
-                       StringTokenizer st = new StringTokenizer(logEntry.getMessage(), "\r\n");
+                       StringTokenizer st = new StringTokenizer((String) logEntry.get(KEY_MSG), "\r\n");
                        assert st.hasMoreTokens();
 
                        // first line
@@ -317,8 +504,8 @@ class ThinLogging {
                                sb.append('\n');
                        }
 
-                       if (!logEntry.getThrowable().isEmpty()) {
-                               Throwable throwable = logEntry.getThrowable().get();
+                       if (logEntry.containsKey(KEY_THROWABLE)) {
+                               Throwable throwable = (Throwable) logEntry.get(KEY_THROWABLE);
                                sb.append(prefix);
                                addThrowable(sb, prefix, throwable);
                        }
@@ -345,10 +532,12 @@ class ThinLogging {
 
        public static void main(String args[]) {
                Logger logger = System.getLogger(ThinLogging.class.getName());
-               logger.log(Logger.Level.INFO, "Hello log!");
-               logger.log(Logger.Level.ERROR, "Hello error!");
-               logger.log(Logger.Level.DEBUG, "Hello multi\nline\ndebug!");
-               logger.log(Logger.Level.WARNING, "Hello exception!", new Throwable());
+               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());
        }
 
 }
index 3e0d8e1179c251d1299ed99657c354c07030cf44..a4f5a371d9d4c8ab708762b6be70156208510741 100644 (file)
@@ -2,6 +2,7 @@ package org.argeo.init.osgi;
 
 import java.lang.System.Logger;
 import java.lang.System.Logger.Level;
+import java.util.Objects;
 
 import org.argeo.init.logging.ThinLoggerFinder;
 import org.osgi.framework.BundleActivator;
@@ -17,28 +18,34 @@ public class Activator implements BundleActivator {
                // must be called first
                ThinLoggerFinder.lazyInit();
        }
-       Logger logger = System.getLogger(Activator.class.getName());
+       private Logger logger = System.getLogger(Activator.class.getName());
 
        private Long checkpoint = null;
+       private OsgiRuntimeContext runtimeContext;
 
        public void start(final BundleContext bundleContext) throws Exception {
+               if (runtimeContext == null) {
+                       runtimeContext = new OsgiRuntimeContext(bundleContext);
+               }
                logger.log(Level.DEBUG, () -> "Argeo init via OSGi activator");
 
                // admin thread
-               Thread adminThread = new AdminThread(bundleContext);
-               adminThread.start();
+//             Thread adminThread = new AdminThread(bundleContext);
+//             adminThread.start();
 
                // bootstrap
-               OsgiBoot osgiBoot = new OsgiBoot(bundleContext);
+//             OsgiBoot osgiBoot = new OsgiBoot(bundleContext);
                if (checkpoint == null) {
-                       osgiBoot.bootstrap();
+//                     osgiBoot.bootstrap();
                        checkpoint = System.currentTimeMillis();
                } else {
-                       osgiBoot.update();
+                       runtimeContext.update();
                        checkpoint = System.currentTimeMillis();
                }
        }
 
        public void stop(BundleContext context) throws Exception {
+               Objects.requireNonNull(runtimeContext);
+               runtimeContext.stop(context);
        }
 }
index fdb6fe92317e601465af68cf52fd7da1d5128368..e493e2c7e0d7621a33ccd73ef12633b93b9d997a 100644 (file)
@@ -6,6 +6,7 @@ import org.osgi.framework.BundleContext;
 import org.osgi.framework.launch.Framework;
 
 /** Monitors the runtime and can shut it down. */
+@Deprecated
 public class AdminThread extends Thread {
        public final static String PROP_ARGEO_OSGI_SHUTDOWN_FILE = "argeo.osgi.shutdownFile";
        private File shutdownFile;
index 91756e32b6759921484c0c1bf6b937408ccd4ab7..7f44f6b2746dccc914312180b493d56e7328ce3c 100644 (file)
@@ -1,24 +1,42 @@
 package org.argeo.init.osgi;
 
+import java.util.Collections;
+import java.util.Hashtable;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.ServiceLoader;
+import java.util.concurrent.Flow;
+import java.util.function.Consumer;
 
 import org.argeo.init.RuntimeContext;
+import org.argeo.init.logging.ThinLoggerFinder;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
 import org.osgi.framework.launch.Framework;
 import org.osgi.framework.launch.FrameworkFactory;
 
+/** An OSGi runtime context. */
 public class OsgiRuntimeContext implements RuntimeContext {
        private Map<String, String> config;
        private Framework framework;
        private OsgiBoot osgiBoot;
 
+       @SuppressWarnings("rawtypes")
+       private ServiceRegistration<Consumer> loggingConfigurationSr;
+       @SuppressWarnings("rawtypes")
+       private ServiceRegistration<Flow.Publisher> logEntryPublisherSr;
+
        public OsgiRuntimeContext(Map<String, String> config) {
                this.config = config;
        }
 
+       public OsgiRuntimeContext(BundleContext bundleContext) {
+               start(bundleContext);
+       }
+
        @Override
        public void run() {
                ServiceLoader<FrameworkFactory> sl = ServiceLoader.load(FrameworkFactory.class);
@@ -29,17 +47,44 @@ public class OsgiRuntimeContext implements RuntimeContext {
                try {
                        framework.start();
                        BundleContext bundleContext = framework.getBundleContext();
-                       osgiBoot = new OsgiBoot(bundleContext);
-                       osgiBoot.bootstrap();
+                       start(bundleContext);
                } catch (BundleException e) {
                        throw new IllegalStateException("Cannot start OSGi framework", e);
                }
        }
 
+       public void start(BundleContext bundleContext) {
+               // logging
+               loggingConfigurationSr = bundleContext.registerService(Consumer.class,
+                               ThinLoggerFinder.getConfigurationConsumer(),
+                               new Hashtable<>(Collections.singletonMap(Constants.SERVICE_PID, "argeo.logging.configuration")));
+               logEntryPublisherSr = bundleContext.registerService(Flow.Publisher.class,
+                               ThinLoggerFinder.getLogEntryPublisher(),
+                               new Hashtable<>(Collections.singletonMap(Constants.SERVICE_PID, "argeo.logging.publisher")));
+
+               osgiBoot = new OsgiBoot(bundleContext);
+               osgiBoot.bootstrap();
+
+       }
+
+       public void update() {
+               Objects.requireNonNull(osgiBoot);
+               osgiBoot.update();
+       }
+
+       public void stop(BundleContext bundleContext) {
+               if (loggingConfigurationSr != null)
+                       loggingConfigurationSr.unregister();
+               if (logEntryPublisherSr != null)
+                       logEntryPublisherSr.unregister();
+
+       }
+
        @Override
        public void waitForStop(long timeout) throws InterruptedException {
                if (framework == null)
                        throw new IllegalStateException("Framework is not initialised");
+               stop(framework.getBundleContext());
                framework.waitForStop(timeout);
        }