]> nmode's Git Repositories - signal-cli/commitdiff
Add --log-file parameter to write logs to separate file
authorAsamK <asamk@gmx.de>
Sat, 29 Jan 2022 11:35:19 +0000 (12:35 +0100)
committerAsamK <asamk@gmx.de>
Sat, 29 Jan 2022 14:05:08 +0000 (15:05 +0100)
Use logback for more control over the log output

Fixes #845

build.gradle.kts
graalvm-config-dir/reflect-config.json
graalvm-config-dir/resource-config.json
man/signal-cli.1.adoc
src/main/java/org/asamk/signal/App.java
src/main/java/org/asamk/signal/LogConfigurator.java [new file with mode: 0644]
src/main/java/org/asamk/signal/Main.java
src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator [new file with mode: 0644]

index 19ed08d7ea630d8b41ac75777d6a2e50a84fb8da..fa87a606e41796b7cfddfaf700f1556cbc91458f 100644 (file)
@@ -37,7 +37,8 @@ dependencies {
     implementation("com.fasterxml.jackson.core", "jackson-databind", "2.13.1")
     implementation("net.sourceforge.argparse4j", "argparse4j", "0.9.0")
     implementation("com.github.hypfvieh", "dbus-java-transport-native-unixsocket", "4.0.0")
-    implementation("org.slf4j", "slf4j-simple", "1.7.32")
+    implementation("org.slf4j", "slf4j-api", "1.7.32")
+    implementation("ch.qos.logback", "logback-classic", "1.2.10")
     implementation("org.slf4j", "jul-to-slf4j", "1.7.32")
     implementation(project(":lib"))
 }
index c5e61cee32929f5fcf978fce4b9a78d517d1d73d..46c106ebf2b90b8e1344839f55fb9b4c97fdd461 100644 (file)
   "allDeclaredMethods":true,
   "allPublicMethods":true
 },
+{
+  "name":"ch.qos.logback.classic.pattern.DateConverter",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"ch.qos.logback.classic.pattern.LevelConverter",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"ch.qos.logback.classic.pattern.LineSeparatorConverter",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"ch.qos.logback.classic.pattern.LoggerConverter",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"ch.qos.logback.classic.pattern.MessageConverter",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"ch.qos.logback.classic.pattern.ThreadConverter",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
 {
   "name":"char[]"
 },
   "allDeclaredMethods":true,
   "allPublicMethods":true
 },
+{
+  "name":"java.io.File",
+  "methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
+},
+{
+  "name":"java.io.FilePermission"
+},
 {
   "name":"java.io.Serializable",
   "allDeclaredMethods":true
   "allDeclaredFields":true,
   "queryAllDeclaredMethods":true
 },
+{
+  "name":"java.lang.RuntimePermission"
+},
 {
   "name":"java.lang.String",
   "allPublicMethods":true
     {"name":"getType","parameterTypes":[] }
   ]
 },
+{
+  "name":"java.net.NetPermission"
+},
+{
+  "name":"java.net.SocketPermission"
+},
+{
+  "name":"java.net.URLPermission",
+  "methods":[{"name":"<init>","parameterTypes":["java.lang.String","java.lang.String"] }]
+},
 {
   "name":"java.nio.Buffer",
   "allDeclaredMethods":true,
   "allDeclaredMethods":true,
   "allPublicMethods":true
 },
+{
+  "name":"java.security.AllPermission"
+},
 {
   "name":"java.security.KeyStoreSpi"
 },
 {
   "name":"java.security.SecureRandomParameters"
 },
+{
+  "name":"java.security.SecurityPermission"
+},
 {
   "name":"java.security.cert.PKIXRevocationChecker"
 },
   "queryAllDeclaredMethods":true,
   "queryAllDeclaredConstructors":true
 },
+{
+  "name":"java.util.PropertyPermission"
+},
 {
   "name":"java.util.RandomAccess",
   "allDeclaredMethods":true
index a52689aebfcf753c49bcbf18ca6dfe805bc7d04d..c0866365331a56f148344b8a188a811e2bd04a3b 100644 (file)
@@ -4,6 +4,9 @@
     {
       "pattern":"\\QMETA-INF/maven/org.xerial/sqlite-jdbc/pom.properties\\E"
     }, 
+    {
+      "pattern":"\\QMETA-INF/services/ch.qos.logback.classic.spi.Configurator\\E"
+    }, 
     {
       "pattern":"\\QMETA-INF/services/java.sql.Driver\\E"
     }, 
index b71f549c5c72b8645cfc94e34e88cd4dcc863eef..e7296207783b2a6e59a3b524c07248e9c4da5a2a 100644 (file)
@@ -37,6 +37,10 @@ Print the version and quit.
 *--verbose*::
 Raise log level and include lib signal logs.
 
+*--log-file* LOG_FILE::
+Write log output to the given file.
+If `--verbose` is also given, the detailed logs will only be written to the log file.
+
 *--config* CONFIG::
 Set the path, where to store the config.
 Make sure you have full read/write access to the given directory.
index 6c4726d56e3e881de19b34388ee1fcc6bb7415e6..d06cd7988d1e096b3733fbeb42c9583c976458b3 100644 (file)
@@ -69,6 +69,9 @@ public class App {
         parser.addArgument("--verbose")
                 .help("Raise log level and include lib signal logs. Specify multiple times for even more logs.")
                 .action(Arguments.count());
+        parser.addArgument("--log-file")
+                .type(File.class)
+                .help("Write log output to the given file. If --verbose is also given, the detailed logs will only be written to the log file.");
         parser.addArgument("-c", "--config")
                 .help("Set the path, where to store the config (Default: $XDG_DATA_HOME/signal-cli , $HOME/.local/share/signal-cli).");
 
diff --git a/src/main/java/org/asamk/signal/LogConfigurator.java b/src/main/java/org/asamk/signal/LogConfigurator.java
new file mode 100644 (file)
index 0000000..9271d6b
--- /dev/null
@@ -0,0 +1,114 @@
+package org.asamk.signal;
+
+import java.io.File;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.PatternLayout;
+import ch.qos.logback.classic.spi.Configurator;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.ConsoleAppender;
+import ch.qos.logback.core.FileAppender;
+import ch.qos.logback.core.Layout;
+import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
+import ch.qos.logback.core.filter.Filter;
+import ch.qos.logback.core.spi.ContextAwareBase;
+import ch.qos.logback.core.spi.FilterReply;
+
+public class LogConfigurator extends ContextAwareBase implements Configurator {
+
+    private static int verboseLevel = 0;
+    private static File logFile = null;
+
+    public static void setVerboseLevel(int verboseLevel) {
+        LogConfigurator.verboseLevel = verboseLevel;
+    }
+
+    public static void setLogFile(File logFile) {
+        LogConfigurator.logFile = logFile;
+    }
+
+    public void configure(LoggerContext lc) {
+        final var rootLogger = lc.getLogger(Logger.ROOT_LOGGER_NAME);
+
+        final var defaultLevel = verboseLevel > 1 ? Level.ALL : verboseLevel > 0 ? Level.DEBUG : Level.INFO;
+        rootLogger.setLevel(defaultLevel);
+
+        final var consoleLayout = verboseLevel == 0 || logFile != null
+                ? createSimpleLoggingLayout(lc)
+                : createDetailedLoggingLayout(lc);
+        final var consoleAppender = createLoggingConsoleAppender(lc, createLayoutWrappingEncoder(consoleLayout));
+        rootLogger.addAppender(consoleAppender);
+
+        lc.getLogger("com.zaxxer.hikari")
+                .setLevel(verboseLevel > 1 ? Level.ALL : verboseLevel > 0 ? Level.INFO : Level.WARN);
+
+        if (logFile != null) {
+            consoleAppender.addFilter(new Filter<>() {
+                @Override
+                public FilterReply decide(final ILoggingEvent event) {
+                    return event.getLevel().isGreaterOrEqual(Level.INFO)
+                            && !"LibSignal".equals(event.getLoggerName())
+                            && (
+                            event.getLevel().isGreaterOrEqual(Level.WARN) || !event.getLoggerName()
+                                    .startsWith("com.zaxxer.hikari")
+                    )
+
+                            ? FilterReply.NEUTRAL : FilterReply.DENY;
+                }
+            });
+
+            final var fileLayout = createDetailedLoggingLayout(lc);
+            final var fileAppender = createLoggingFileAppender(lc, createLayoutWrappingEncoder(fileLayout));
+            rootLogger.addAppender(fileAppender);
+        }
+    }
+
+    private ConsoleAppender<ILoggingEvent> createLoggingConsoleAppender(
+            final LoggerContext lc, final LayoutWrappingEncoder<ILoggingEvent> layoutEncoder
+    ) {
+        return new ConsoleAppender<>() {{
+            setContext(lc);
+            setName("console");
+            setTarget("System.err");
+            setEncoder(layoutEncoder);
+            start();
+        }};
+    }
+
+    private FileAppender<ILoggingEvent> createLoggingFileAppender(
+            final LoggerContext lc, final LayoutWrappingEncoder<ILoggingEvent> layoutEncoder
+    ) {
+        return new FileAppender<>() {{
+            setContext(lc);
+            setName("file");
+            setFile(logFile.getAbsolutePath());
+            setEncoder(layoutEncoder);
+            start();
+        }};
+    }
+
+    private LayoutWrappingEncoder<ILoggingEvent> createLayoutWrappingEncoder(final Layout<ILoggingEvent> l) {
+        return new LayoutWrappingEncoder<>() {{
+            setContext(l.getContext());
+            setLayout(l);
+        }};
+    }
+
+    private PatternLayout createSimpleLoggingLayout(final LoggerContext lc) {
+        return new PatternLayout() {{
+            setPattern("%-5level %logger{0} - %msg%n");
+            setContext(lc);
+            start();
+        }};
+    }
+
+    private PatternLayout createDetailedLoggingLayout(final LoggerContext lc) {
+        return new PatternLayout() {{
+            setPattern("%d{yyyy-MM-dd'T'HH:mm:ss.SSSXX} [%thread] %-5level %logger{36} - %msg%n");
+            setContext(lc);
+            start();
+        }};
+    }
+}
index 104a9105bcd7e05fbf9f978da3d53b32e79d082f..f0b785905f3cfd27e2cc7fce90d47a9c97868709 100644 (file)
@@ -17,6 +17,7 @@
 package org.asamk.signal;
 
 import net.sourceforge.argparse4j.ArgumentParsers;
+import net.sourceforge.argparse4j.DefaultSettings;
 import net.sourceforge.argparse4j.impl.Arguments;
 import net.sourceforge.argparse4j.inf.ArgumentParserException;
 import net.sourceforge.argparse4j.inf.Namespace;
@@ -31,6 +32,7 @@ import org.asamk.signal.util.SecurityProvider;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.slf4j.bridge.SLF4JBridgeHandler;
 
+import java.io.File;
 import java.security.Security;
 
 public class Main {
@@ -41,8 +43,11 @@ public class Main {
         installSecurityProviderWorkaround();
 
         // Configuring the logger needs to happen before any logger is initialized
-        final var verboseLevel = getVerboseLevel(args);
-        configureLogging(verboseLevel);
+
+        final var nsLog = parseArgs(args);
+        final var verboseLevel = nsLog == null ? 0 : nsLog.getInt("verbose");
+        final var logFile = nsLog == null ? null : nsLog.<File>get("log-file");
+        configureLogging(verboseLevel, logFile);
 
         var parser = App.buildArgumentParser();
 
@@ -70,35 +75,29 @@ public class Main {
         Security.addProvider(new BouncyCastleProvider());
     }
 
-    private static int getVerboseLevel(String[] args) {
-        var parser = ArgumentParsers.newFor("signal-cli").build().defaultHelp(false);
+    private static Namespace parseArgs(String[] args) {
+        var parser = ArgumentParsers.newFor("signal-cli", DefaultSettings.VERSION_0_9_0_DEFAULT_SETTINGS)
+                .includeArgumentNamesAsKeysInResult(true)
+                .build()
+                .defaultHelp(false);
         parser.addArgument("--verbose").action(Arguments.count());
+        parser.addArgument("--log-file").type(File.class);
 
-        Namespace ns;
         try {
-            ns = parser.parseKnownArgs(args, null);
+            return parser.parseKnownArgs(args, null);
         } catch (ArgumentParserException e) {
-            return 0;
+            return null;
         }
-
-        return ns.getInt("verbose");
     }
 
-    private static void configureLogging(final int verboseLevel) {
-        final var defaultLogLevel = verboseLevel > 1 ? "trace" : verboseLevel > 0 ? "debug" : "info";
-        System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", defaultLogLevel);
+    private static void configureLogging(final int verboseLevel, final File logFile) {
+        LogConfigurator.setVerboseLevel(verboseLevel);
+        LogConfigurator.setLogFile(logFile);
+
         if (verboseLevel > 0) {
-            System.setProperty("org.slf4j.simpleLogger.showThreadName", "true");
-            System.setProperty("org.slf4j.simpleLogger.showShortLogName", "false");
-            System.setProperty("org.slf4j.simpleLogger.showDateTime", "true");
-            System.setProperty("org.slf4j.simpleLogger.dateTimeFormat", "yyyy-MM-dd'T'HH:mm:ss.SSSXX");
             java.util.logging.Logger.getLogger("")
                     .setLevel(verboseLevel > 2 ? java.util.logging.Level.FINEST : java.util.logging.Level.INFO);
             Manager.initLogger();
-        } else {
-            System.setProperty("org.slf4j.simpleLogger.showThreadName", "false");
-            System.setProperty("org.slf4j.simpleLogger.showShortLogName", "true");
-            System.setProperty("org.slf4j.simpleLogger.showDateTime", "false");
         }
         SLF4JBridgeHandler.removeHandlersForRootLogger();
         SLF4JBridgeHandler.install();
diff --git a/src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator b/src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator
new file mode 100644 (file)
index 0000000..354cf5b
--- /dev/null
@@ -0,0 +1 @@
+org.asamk.signal.LogConfigurator