From: AsamK Date: Sat, 29 Jan 2022 11:35:19 +0000 (+0100) Subject: Add --log-file parameter to write logs to separate file X-Git-Tag: v0.10.3~5 X-Git-Url: https://git.nmode.ca/signal-cli/commitdiff_plain/2e74acaabe9e24dda980c19ac174be10157f4578 Add --log-file parameter to write logs to separate file Use logback for more control over the log output Fixes #845 --- diff --git a/build.gradle.kts b/build.gradle.kts index 19ed08d7..fa87a606 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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")) } diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index c5e61cee..46c106eb 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -38,6 +38,30 @@ "allDeclaredMethods":true, "allPublicMethods":true }, +{ + "name":"ch.qos.logback.classic.pattern.DateConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.pattern.LevelConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.pattern.LineSeparatorConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.pattern.LoggerConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.pattern.MessageConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.pattern.ThreadConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"char[]" }, @@ -176,6 +200,13 @@ "allDeclaredMethods":true, "allPublicMethods":true }, +{ + "name":"java.io.File", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.io.FilePermission" +}, { "name":"java.io.Serializable", "allDeclaredMethods":true @@ -228,6 +259,9 @@ "allDeclaredFields":true, "queryAllDeclaredMethods":true }, +{ + "name":"java.lang.RuntimePermission" +}, { "name":"java.lang.String", "allPublicMethods":true @@ -248,6 +282,16 @@ {"name":"getType","parameterTypes":[] } ] }, +{ + "name":"java.net.NetPermission" +}, +{ + "name":"java.net.SocketPermission" +}, +{ + "name":"java.net.URLPermission", + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String"] }] +}, { "name":"java.nio.Buffer", "allDeclaredMethods":true, @@ -258,12 +302,18 @@ "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" }, @@ -322,6 +372,9 @@ "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true }, +{ + "name":"java.util.PropertyPermission" +}, { "name":"java.util.RandomAccess", "allDeclaredMethods":true diff --git a/graalvm-config-dir/resource-config.json b/graalvm-config-dir/resource-config.json index a52689ae..c0866365 100644 --- a/graalvm-config-dir/resource-config.json +++ b/graalvm-config-dir/resource-config.json @@ -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" }, diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index b71f549c..e7296207 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -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. diff --git a/src/main/java/org/asamk/signal/App.java b/src/main/java/org/asamk/signal/App.java index 6c4726d5..d06cd798 100644 --- a/src/main/java/org/asamk/signal/App.java +++ b/src/main/java/org/asamk/signal/App.java @@ -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 index 00000000..9271d6b7 --- /dev/null +++ b/src/main/java/org/asamk/signal/LogConfigurator.java @@ -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 createLoggingConsoleAppender( + final LoggerContext lc, final LayoutWrappingEncoder layoutEncoder + ) { + return new ConsoleAppender<>() {{ + setContext(lc); + setName("console"); + setTarget("System.err"); + setEncoder(layoutEncoder); + start(); + }}; + } + + private FileAppender createLoggingFileAppender( + final LoggerContext lc, final LayoutWrappingEncoder layoutEncoder + ) { + return new FileAppender<>() {{ + setContext(lc); + setName("file"); + setFile(logFile.getAbsolutePath()); + setEncoder(layoutEncoder); + start(); + }}; + } + + private LayoutWrappingEncoder createLayoutWrappingEncoder(final Layout 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(); + }}; + } +} diff --git a/src/main/java/org/asamk/signal/Main.java b/src/main/java/org/asamk/signal/Main.java index 104a9105..f0b78590 100644 --- a/src/main/java/org/asamk/signal/Main.java +++ b/src/main/java/org/asamk/signal/Main.java @@ -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.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 index 00000000..354cf5bd --- /dev/null +++ b/src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator @@ -0,0 +1 @@ +org.asamk.signal.LogConfigurator