diff --git a/WalletWasabi.Daemon/Config.cs b/WalletWasabi.Daemon/Config.cs index 8540285a88f..aceb529f5d6 100644 --- a/WalletWasabi.Daemon/Config.cs +++ b/WalletWasabi.Daemon/Config.cs @@ -1,3 +1,4 @@ +using Microsoft.Extensions.Logging; using NBitcoin; using System; using System.Collections; @@ -8,6 +9,7 @@ using System.Net; using WalletWasabi.Exceptions; using WalletWasabi.Helpers; +using WalletWasabi.Logging; using WalletWasabi.Models; using WalletWasabi.Tor; using WalletWasabi.Userfacing; @@ -23,6 +25,14 @@ public Config(PersistentConfig persistentConfig, string[] cliArgs) PersistentConfig = persistentConfig; CliArgs = cliArgs; + LogMode[] defaultLogModes; + +#if RELEASE + defaultLogModes = [LogMode.Console, LogMode.File]; +#else + defaultLogModes = [LogMode.Debug, LogMode.Console, LogMode.File]; +#endif + Data = new() { [ nameof(Network)] = ( @@ -109,6 +119,9 @@ [ nameof(BlockOnlyMode)] = ( [ nameof(LogLevel)] = ( "The level of detail in the logs: trace, debug, info, warning, error, or critical", GetStringValue("LogLevel", value: "", cliArgs)), + [ nameof(LogModes)] = ( + "The logging modes: console, and file (for multiple values use comma as a separator)", + GetLogModeArrayValue("LogModes", arrayValues: defaultLogModes, cliArgs)), [ nameof(EnableGpu)] = ( "Use a GPU to render the user interface", GetBoolValue("EnableGpu", PersistentConfig.EnableGpu, cliArgs)), @@ -162,6 +175,7 @@ [ nameof(CoordinatorIdentifier)] = ( public Money DustThreshold => GetEffectiveValue(nameof(DustThreshold)); public bool BlockOnlyMode => GetEffectiveValue(nameof(BlockOnlyMode)); public string LogLevel => GetEffectiveValue(nameof(LogLevel)); + public LogMode[] LogModes => GetEffectiveValue(nameof(LogModes)); public bool EnableGpu => GetEffectiveValue(nameof(EnableGpu)); public string CoordinatorIdentifier => GetEffectiveValue(nameof(CoordinatorIdentifier)); @@ -331,6 +345,30 @@ private static StringArrayValue GetStringArrayValue(string key, string[] arrayVa return new StringArrayValue(arrayValues, arrayValues, ValueSource.Disk); } + private static LogModeArrayValue GetLogModeArrayValue(string key, LogMode[] arrayValues, string[] cliArgs) + { + if (GetOverrideValue(key, cliArgs, out string? overrideValue, out ValueSource? valueSource)) + { + LogMode[] logModes = overrideValue.Split(',', StringSplitOptions.RemoveEmptyEntries) + .Where(x => !string.IsNullOrWhiteSpace(x)) // Filter our whitespace-only elements. + .Select(x => + { + if (!Enum.TryParse(x.Trim(), ignoreCase: true, out LogMode logMode)) + { + throw new NotSupportedException($"Logging mode '{x}' is not supported."); + } + + return logMode; + }) + .ToHashSet() // Remove duplicates. + .ToArray(); + + return new LogModeArrayValue(arrayValues, logModes, valueSource.Value); + } + + return new LogModeArrayValue(arrayValues, arrayValues, ValueSource.Disk); + } + private static bool GetOverrideValue(string key, string[] cliArgs, [NotNullWhen(true)] out string? overrideValue, [NotNullWhen(true)] out ValueSource? valueSource) { // CLI arguments have higher precedence than environment variables. @@ -421,6 +459,7 @@ private record IntValue(int Value, int EffectiveValue, ValueSource ValueSource) private record StringValue(string Value, string EffectiveValue, ValueSource ValueSource) : ITypedValue; private record NullableStringValue(string? Value, string? EffectiveValue, ValueSource ValueSource) : ITypedValue; private record StringArrayValue(string[] Value, string[] EffectiveValue, ValueSource ValueSource) : ITypedValue; + private record LogModeArrayValue(LogMode[] Value, LogMode[] EffectiveValue, ValueSource ValueSource) : ITypedValue; private record NetworkValue(Network Value, Network EffectiveValue, ValueSource ValueSource) : ITypedValue; private record MoneyValue(Money Value, Money EffectiveValue, ValueSource ValueSource) : ITypedValue; private record EndPointValue(EndPoint Value, EndPoint EffectiveValue, ValueSource ValueSource) : ITypedValue; diff --git a/WalletWasabi.Daemon/Global.cs b/WalletWasabi.Daemon/Global.cs index 39e803340cc..e0c8b592825 100644 --- a/WalletWasabi.Daemon/Global.cs +++ b/WalletWasabi.Daemon/Global.cs @@ -59,7 +59,8 @@ public Global(string dataDir, string configFilePath, Config config) controlPort: config.TorControlPort, torFolder: config.TorFolder, bridges: config.TorBridges, - owningProcessId: Environment.ProcessId); + owningProcessId: Environment.ProcessId, + log: Config.LogModes.Contains(LogMode.File)); HostedServices = new HostedServices(); EventBus = new EventBus(); diff --git a/WalletWasabi.Daemon/WasabiAppBuilder.cs b/WalletWasabi.Daemon/WasabiAppBuilder.cs index 94485e672ea..6721b6ef20e 100644 --- a/WalletWasabi.Daemon/WasabiAppBuilder.cs +++ b/WalletWasabi.Daemon/WasabiAppBuilder.cs @@ -149,7 +149,8 @@ private void SetupLogger() LogLevel logLevel = Enum.TryParse(Config.LogLevel, ignoreCase: true, out LogLevel parsedLevel) ? parsedLevel : LogLevel.Info; - Logger.InitializeDefaults(Path.Combine(Config.DataDir, "Logs.txt"), logLevel); + + Logger.InitializeDefaults(Path.Combine(Config.DataDir, "Logs.txt"), logLevel, Config.LogModes); } private void ShowHelp() diff --git a/WalletWasabi/Logging/Logger.cs b/WalletWasabi/Logging/Logger.cs index 654822c91ae..1ed285e6782 100644 --- a/WalletWasabi/Logging/Logger.cs +++ b/WalletWasabi/Logging/Logger.cs @@ -67,19 +67,22 @@ public static class Logger /// /// /// Use null to use default or a custom value to force non-default . - public static void InitializeDefaults(string filePath, LogLevel? logLevel = null) + /// Use null to use default logging modes or custom values to force non-default logging modes. + public static void InitializeDefaults(string filePath, LogLevel? logLevel = null, LogMode[]? logModes = null) { SetFilePath(filePath); #if RELEASE - SetMinimumLevel(logLevel ??= LogLevel.Info); - SetModes(LogMode.Console, LogMode.File); - + logLevel ??= LogLevel.Info; + logModes ??= [LogMode.Console, LogMode.File]; #else - SetMinimumLevel(logLevel ??= LogLevel.Debug); - SetModes(LogMode.Debug, LogMode.Console, LogMode.File); + logLevel ??= LogLevel.Debug; + logModes ??= [LogMode.Debug, LogMode.Console, LogMode.File]; #endif + SetMinimumLevel(logLevel.Value); + SetModes(logModes); + lock (Lock) { if (MinimumLevel == LogLevel.Trace) diff --git a/WalletWasabi/Tor/TorSettings.cs b/WalletWasabi/Tor/TorSettings.cs index c6ea69fd91b..769935d3604 100644 --- a/WalletWasabi/Tor/TorSettings.cs +++ b/WalletWasabi/Tor/TorSettings.cs @@ -31,20 +31,21 @@ public TorSettings( int controlPort = DefaultControlPort, string? torFolder = null, string[]? bridges = null, - int? owningProcessId = null) + int? owningProcessId = null, + bool log = true) { IsCustomTorFolder = torFolder is not null; - bool defaultWasabiTorPorts = socksPort == DefaultSocksPort && controlPort == DefaultControlPort; + bool defaultWasabiTorPorts = socksPort == DefaultSocksPort && controlPort == DefaultControlPort; // Use different ports when user overrides Tor folder to avoid accessing the same control_auth_cookie file. - if (IsCustomTorFolder && defaultWasabiTorPorts) - { + if (IsCustomTorFolder && defaultWasabiTorPorts) + { socksPort = 37152; controlPort = 37153; - } + } - TorBinaryDir = torFolder ?? Path.Combine(MicroserviceHelpers.GetBinaryFolder(), "Tor"); + TorBinaryDir = torFolder ?? Path.Combine(MicroserviceHelpers.GetBinaryFolder(), "Tor"); TorBinaryFilePath = GetTorBinaryFilePath(TorBinaryDir); TorTransportPluginsDir = Path.Combine(TorBinaryDir, "PluggableTransports"); @@ -62,15 +63,16 @@ public TorSettings( IoHelpers.EnsureContainingDirectoryExists(LogFilePath); DistributionFolder = distributionFolderPath; TerminateOnExit = terminateOnExit; + Log = log; GeoIpPath = Path.Combine(DistributionFolder, "Tor", "Geoip", "geoip"); GeoIp6Path = Path.Combine(DistributionFolder, "Tor", "Geoip", "geoip6"); } - /// true if user specified a custom Tor folder to run a (possibly) different Tor binary than the bundled Tor, false otherwise. - public bool IsCustomTorFolder { get; } + /// true if user specified a custom Tor folder to run a (possibly) different Tor binary than the bundled Tor, false otherwise. + public bool IsCustomTorFolder { get; } - /// Full directory path where Tor binaries are placed. - public string TorBinaryDir { get; } + /// Full directory path where Tor binaries are placed. + public string TorBinaryDir { get; } /// Full directory path where Tor transports plugins are placed. public string TorTransportPluginsDir { get; } @@ -97,6 +99,9 @@ public TorSettings( /// Owning process ID for Tor program. public int? OwningProcessId { get; } + /// true if logging to TorLogs.txt file is enabled, false otherwise. + public bool Log { get; } + /// Full path to executable file that is used to start Tor process. public string TorBinaryFilePath { get; } @@ -115,7 +120,7 @@ public TorSettings( private string GeoIp6Path { get; } /// Full path to Tor binary for selected . - public static string GetTorBinaryFilePath(string path, OSPlatform ? platform = null) + public static string GetTorBinaryFilePath(string path, OSPlatform? platform = null) { return Path.Combine(path, MicroserviceHelpers.GetFilenameWithExtension(TorBinaryFileName, platform)); } @@ -146,8 +151,7 @@ public string GetCmdArguments() $"--CookieAuthFile \"{CookieAuthFilePath}\"", $"--DataDirectory \"{TorDataDir}\"", $"--GeoIPFile \"{GeoIpPath}\"", - $"--GeoIPv6File \"{GeoIp6Path}\"", - $"--Log \"notice file {LogFilePath}\"" + $"--GeoIPv6File \"{GeoIp6Path}\"" ]; if (useBridges) @@ -188,7 +192,7 @@ public string GetCmdArguments() { "obfs4" => "obfs4proxy", "webtunnel" => "webtunnel-client", - "snowflake" => "snowflake-client", + "snowflake" => "snowflake-client", _ => throw new NotSupportedException($"Unknown Tor pluggable transport '{plugin}'."), }; @@ -204,6 +208,11 @@ public string GetCmdArguments() } } + if (Log) + { + arguments.Add($"--Log \"notice file {LogFilePath}\""); + } + if (TerminateOnExit && OwningProcessId is not null) { arguments.Add($"__OwningControllerProcess {OwningProcessId}");