import java.util.SortedMap;
import java.util.StringTokenizer;
import java.util.TreeMap;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
import java.util.concurrent.Flow;
import java.util.concurrent.Flow.Subscription;
+import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.SubmissionPublisher;
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;
+
/**
* A thin logging system based on the {@link Logger} framework. It is a
* {@link Consumer} of configuration, and can be registered as such.
private NavigableMap<String, Level> levels = new TreeMap<>();
private volatile boolean updatingConfiguration = false;
- private final ExecutorService executor;
+// private final ExecutorService executor;
private final LogEntryPublisher publisher;
+ private PrintStreamSubscriber synchronousSubscriber;
private final boolean journald;
private final Level callLocationLevel;
- ThinLogging() {
- executor = Executors.newCachedThreadPool((r) -> {
- Thread t = new Thread(r);
- t.setDaemon(true);
- return t;
- });
- publisher = new LogEntryPublisher(executor, Flow.defaultBufferSize());
+ private boolean synchronous = true;
- PrintStreamSubscriber subscriber = new PrintStreamSubscriber();
- publisher.subscribe(subscriber);
+ ThinLogging() {
+// executor = Executors.newCachedThreadPool((r) -> {
+// Thread t = new Thread(r);
+// t.setDaemon(true);
+// return t;
+// });
+ if (synchronous) {
+ publisher = new LogEntryPublisher();
+ synchronousSubscriber = new PrintStreamSubscriber();
+ } else {
+ publisher = new LogEntryPublisher();
+
+ PrintStreamSubscriber subscriber = new PrintStreamSubscriber();
+ publisher.subscribe(subscriber);
+
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> close(), "Log shutdown"));
- Runtime.getRuntime().addShutdownHook(new Thread(() -> close(), "Log shutdown"));
+ }
// initial default level
levels.put("", Level.WARNING);
}
private void close() {
+ RuntimeContext runtimeContext = Service.getRuntimeContext();
+ if (runtimeContext != null) {
+ try {
+ runtimeContext.waitForStop(0);
+ } catch (InterruptedException e) {
+ // silent
+ }
+ }
+
publisher.close();
try {
// we ait a bit in order to make sure all messages are flushed
// TODO synchronize more efficiently
- executor.awaitTermination(300, TimeUnit.MILLISECONDS);
+ // executor.awaitTermination(300, TimeUnit.MILLISECONDS);
+ ForkJoinPool.commonPool().awaitTermination(300, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
// silent
}
// 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
+// if (format.contains("{}"))// workaround for weird Jetty formatting
+// params = null;
+ // TODO move this to slf4j wrapper?
+ if (format.contains("{}")) {
+ StringBuilder sb = new StringBuilder();
+ String[] segments = format.split("\\{\\}");
+ for (int i = 0; i < segments.length; i++) {
+ sb.append(segments[i]);
+ if (i != (segments.length - 1))
+ sb.append("{" + i + "}");
+ }
+ 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));
}
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":
private class LogEntryPublisher extends SubmissionPublisher<Map<String, Serializable>> {
- private LogEntryPublisher(Executor executor, int maxBufferCapacity) {
- super(executor, maxBufferCapacity);
+ private LogEntryPublisher() {
+ super();
}
private void log(ThinLogger logger, Level level, ResourceBundle bundle, String msg, Instant instant,
logEntry.put(KEY_THREAD, thread.getName());
// should be unmodifiable for security reasons
- submit(Collections.unmodifiableMap(logEntry));
+ if (synchronous) {
+ assert synchronousSubscriber != null;
+ synchronousSubscriber.onNext(logEntry);
+ } else {
+ if (!isClosed())
+ submit(Collections.unmodifiableMap(logEntry));
+ }
}
}
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
+ }
+
}
}