Add --log-file parameter to write logs to separate file

Use logback for more control over the log output

Fixes #845
This commit is contained in:
AsamK 2022-01-29 12:35:19 +01:00
parent 95cc0ae7fd
commit 2e74acaabe
8 changed files with 199 additions and 21 deletions

View file

@ -37,7 +37,8 @@ dependencies {
implementation("com.fasterxml.jackson.core", "jackson-databind", "2.13.1") implementation("com.fasterxml.jackson.core", "jackson-databind", "2.13.1")
implementation("net.sourceforge.argparse4j", "argparse4j", "0.9.0") implementation("net.sourceforge.argparse4j", "argparse4j", "0.9.0")
implementation("com.github.hypfvieh", "dbus-java-transport-native-unixsocket", "4.0.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("org.slf4j", "jul-to-slf4j", "1.7.32")
implementation(project(":lib")) implementation(project(":lib"))
} }

View file

@ -38,6 +38,30 @@
"allDeclaredMethods":true, "allDeclaredMethods":true,
"allPublicMethods":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[]" "name":"char[]"
}, },
@ -176,6 +200,13 @@
"allDeclaredMethods":true, "allDeclaredMethods":true,
"allPublicMethods":true "allPublicMethods":true
}, },
{
"name":"java.io.File",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
},
{
"name":"java.io.FilePermission"
},
{ {
"name":"java.io.Serializable", "name":"java.io.Serializable",
"allDeclaredMethods":true "allDeclaredMethods":true
@ -228,6 +259,9 @@
"allDeclaredFields":true, "allDeclaredFields":true,
"queryAllDeclaredMethods":true "queryAllDeclaredMethods":true
}, },
{
"name":"java.lang.RuntimePermission"
},
{ {
"name":"java.lang.String", "name":"java.lang.String",
"allPublicMethods":true "allPublicMethods":true
@ -248,6 +282,16 @@
{"name":"getType","parameterTypes":[] } {"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", "name":"java.nio.Buffer",
"allDeclaredMethods":true, "allDeclaredMethods":true,
@ -258,12 +302,18 @@
"allDeclaredMethods":true, "allDeclaredMethods":true,
"allPublicMethods":true "allPublicMethods":true
}, },
{
"name":"java.security.AllPermission"
},
{ {
"name":"java.security.KeyStoreSpi" "name":"java.security.KeyStoreSpi"
}, },
{ {
"name":"java.security.SecureRandomParameters" "name":"java.security.SecureRandomParameters"
}, },
{
"name":"java.security.SecurityPermission"
},
{ {
"name":"java.security.cert.PKIXRevocationChecker" "name":"java.security.cert.PKIXRevocationChecker"
}, },
@ -322,6 +372,9 @@
"queryAllDeclaredMethods":true, "queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true "queryAllDeclaredConstructors":true
}, },
{
"name":"java.util.PropertyPermission"
},
{ {
"name":"java.util.RandomAccess", "name":"java.util.RandomAccess",
"allDeclaredMethods":true "allDeclaredMethods":true

View file

@ -4,6 +4,9 @@
{ {
"pattern":"\\QMETA-INF/maven/org.xerial/sqlite-jdbc/pom.properties\\E" "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" "pattern":"\\QMETA-INF/services/java.sql.Driver\\E"
}, },

View file

@ -37,6 +37,10 @@ Print the version and quit.
*--verbose*:: *--verbose*::
Raise log level and include lib signal logs. 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:: *--config* CONFIG::
Set the path, where to store the config. Set the path, where to store the config.
Make sure you have full read/write access to the given directory. Make sure you have full read/write access to the given directory.

View file

@ -69,6 +69,9 @@ public class App {
parser.addArgument("--verbose") parser.addArgument("--verbose")
.help("Raise log level and include lib signal logs. Specify multiple times for even more logs.") .help("Raise log level and include lib signal logs. Specify multiple times for even more logs.")
.action(Arguments.count()); .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") parser.addArgument("-c", "--config")
.help("Set the path, where to store the config (Default: $XDG_DATA_HOME/signal-cli , $HOME/.local/share/signal-cli)."); .help("Set the path, where to store the config (Default: $XDG_DATA_HOME/signal-cli , $HOME/.local/share/signal-cli).");

View file

@ -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();
}};
}
}

View file

@ -17,6 +17,7 @@
package org.asamk.signal; package org.asamk.signal;
import net.sourceforge.argparse4j.ArgumentParsers; import net.sourceforge.argparse4j.ArgumentParsers;
import net.sourceforge.argparse4j.DefaultSettings;
import net.sourceforge.argparse4j.impl.Arguments; import net.sourceforge.argparse4j.impl.Arguments;
import net.sourceforge.argparse4j.inf.ArgumentParserException; import net.sourceforge.argparse4j.inf.ArgumentParserException;
import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Namespace;
@ -31,6 +32,7 @@ import org.asamk.signal.util.SecurityProvider;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.bridge.SLF4JBridgeHandler; import org.slf4j.bridge.SLF4JBridgeHandler;
import java.io.File;
import java.security.Security; import java.security.Security;
public class Main { public class Main {
@ -41,8 +43,11 @@ public class Main {
installSecurityProviderWorkaround(); installSecurityProviderWorkaround();
// Configuring the logger needs to happen before any logger is initialized // 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(); var parser = App.buildArgumentParser();
@ -70,35 +75,29 @@ public class Main {
Security.addProvider(new BouncyCastleProvider()); Security.addProvider(new BouncyCastleProvider());
} }
private static int getVerboseLevel(String[] args) { private static Namespace parseArgs(String[] args) {
var parser = ArgumentParsers.newFor("signal-cli").build().defaultHelp(false); 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("--verbose").action(Arguments.count());
parser.addArgument("--log-file").type(File.class);
Namespace ns;
try { try {
ns = parser.parseKnownArgs(args, null); return parser.parseKnownArgs(args, null);
} catch (ArgumentParserException e) { } catch (ArgumentParserException e) {
return 0; return null;
} }
return ns.getInt("verbose");
} }
private static void configureLogging(final int verboseLevel) { private static void configureLogging(final int verboseLevel, final File logFile) {
final var defaultLogLevel = verboseLevel > 1 ? "trace" : verboseLevel > 0 ? "debug" : "info"; LogConfigurator.setVerboseLevel(verboseLevel);
System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", defaultLogLevel); LogConfigurator.setLogFile(logFile);
if (verboseLevel > 0) { 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("") java.util.logging.Logger.getLogger("")
.setLevel(verboseLevel > 2 ? java.util.logging.Level.FINEST : java.util.logging.Level.INFO); .setLevel(verboseLevel > 2 ? java.util.logging.Level.FINEST : java.util.logging.Level.INFO);
Manager.initLogger(); 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.removeHandlersForRootLogger();
SLF4JBridgeHandler.install(); SLF4JBridgeHandler.install();

View file

@ -0,0 +1 @@
org.asamk.signal.LogConfigurator