This commit introduces significant changes to the initialization process of Spectre.Console and the logging system: 1. Logger Initialization: - Implement a two-phase initialization for the LoggerFactory. - First phase creates an ILogger writing to the CLI console without runtime configuration. - Second phase wraps the initial logger with additional sinks (e.g., file logging) based on CLI arguments. 2. Spectre.Console Setup: - Refactor AutofacTypeRegistrar to store registrations in lists. - Implement Build() method to register types with Autofac when called. - This approach better aligns with Autofac's registration and resolution separation. 3. Global Setup Tasks: - Introduce AppDataDirSetupTask and LoggerSetupTask. - Modify IGlobalSetupTask interface to accept BaseCommandSettings. - Update existing setup tasks to conform to the new interface. 4. Error Handling: - Implement top-level exception handling in Program.Main(). - Remove IFlurlHttpExceptionHandler interface, simplify FlurlHttpExceptionHandler. 5. Logging Improvements: - Move console logging setup to LoggerFactory. - Introduce IndirectLoggerDecorator to allow dynamic logger updates. - Simplify log template management. 6. Dependency Injection: - Update CompositionRoot to reflect new logger and setup task structure. - Remove LoggingAutofacModule, integrate its functionality into CompositionRoot. These changes improve the flexibility and maintainability of the application's startup process, particularly in handling logging and CLI argument processing. The new structure allows for more dynamic configuration of services based on runtime parameters.pull/351/head
parent
e4feb92980
commit
a4bb339f07
@ -1,11 +0,0 @@
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace Recyclarr.Cli.Console;
|
||||
|
||||
public static class CommandConfiguratorExtensions
|
||||
{
|
||||
public static ICommandConfigurator WithExample(this ICommandConfigurator cli, params string[] args)
|
||||
{
|
||||
return cli.WithExample(args);
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
using Recyclarr.Cli.Console.Commands;
|
||||
using Recyclarr.Platform;
|
||||
|
||||
namespace Recyclarr.Cli.Console.Setup;
|
||||
|
||||
public class AppDataDirSetupTask(IAppDataSetup appDataSetup) : IGlobalSetupTask
|
||||
{
|
||||
public void OnStart(BaseCommandSettings cmd)
|
||||
{
|
||||
appDataSetup.SetAppDataDirectoryOverride(cmd.AppData ?? "");
|
||||
}
|
||||
|
||||
public void OnFinish()
|
||||
{
|
||||
}
|
||||
}
|
@ -1,15 +1,23 @@
|
||||
using Recyclarr.Cli.Console.Commands;
|
||||
|
||||
namespace Recyclarr.Cli.Console.Setup;
|
||||
|
||||
[UsedImplicitly]
|
||||
public class CompositeGlobalSetupTask(IOrderedEnumerable<IGlobalSetupTask> tasks) : IGlobalSetupTask
|
||||
public class CompositeGlobalSetupTask(IOrderedEnumerable<Lazy<IGlobalSetupTask>> tasks) : IGlobalSetupTask
|
||||
{
|
||||
public void OnStart()
|
||||
public void OnStart(BaseCommandSettings cmd)
|
||||
{
|
||||
tasks.ForEach(x => x.OnStart());
|
||||
foreach (var task in tasks)
|
||||
{
|
||||
task.Value.OnStart(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnFinish()
|
||||
{
|
||||
tasks.Reverse().ForEach(x => x.OnFinish());
|
||||
foreach (var task in tasks.Reverse())
|
||||
{
|
||||
task.Value.OnFinish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,9 @@
|
||||
using Recyclarr.Cli.Console.Commands;
|
||||
|
||||
namespace Recyclarr.Cli.Console.Setup;
|
||||
|
||||
public interface IGlobalSetupTask
|
||||
{
|
||||
void OnStart()
|
||||
{
|
||||
}
|
||||
|
||||
void OnFinish()
|
||||
{
|
||||
}
|
||||
void OnStart(BaseCommandSettings cmd);
|
||||
void OnFinish();
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
using Recyclarr.Cli.Console.Commands;
|
||||
using Recyclarr.Cli.Logging;
|
||||
using Recyclarr.Logging;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Recyclarr.Cli.Console.Setup;
|
||||
|
||||
public class LoggerSetupTask(
|
||||
LoggingLevelSwitch loggingLevelSwitch,
|
||||
LoggerFactory loggerFactory,
|
||||
IEnumerable<ILogConfigurator> logConfigurators)
|
||||
: IGlobalSetupTask
|
||||
{
|
||||
public void OnStart(BaseCommandSettings cmd)
|
||||
{
|
||||
loggingLevelSwitch.MinimumLevel = cmd.Debug switch
|
||||
{
|
||||
true => LogEventLevel.Debug,
|
||||
_ => LogEventLevel.Information
|
||||
};
|
||||
|
||||
loggerFactory.AddLogConfiguration(logConfigurators);
|
||||
}
|
||||
|
||||
public void OnFinish()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
@ -1,12 +1,17 @@
|
||||
using Recyclarr.Cli.Console.Commands;
|
||||
using Recyclarr.Platform;
|
||||
|
||||
namespace Recyclarr.Cli.Console.Setup;
|
||||
|
||||
public class ProgramInformationDisplayTask(ILogger log, IAppPaths paths) : IGlobalSetupTask
|
||||
{
|
||||
public void OnStart()
|
||||
public void OnStart(BaseCommandSettings cmd)
|
||||
{
|
||||
log.Debug("Recyclarr Version: {Version}", GitVersionInformation.InformationalVersion);
|
||||
log.Debug("App Data Dir: {AppData}", paths.AppDataDirectory);
|
||||
}
|
||||
|
||||
public void OnFinish()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
using Recyclarr.Logging;
|
||||
using Recyclarr.Platform;
|
||||
using Serilog.Core;
|
||||
using Serilog.Templates;
|
||||
using Serilog.Templates.Themes;
|
||||
|
||||
namespace Recyclarr.Cli.Logging;
|
||||
|
||||
internal class ConsoleLogSinkConfigurator(LoggingLevelSwitch levelSwitch, IEnvironment env) : ILogConfigurator
|
||||
{
|
||||
public void Configure(LoggerConfiguration config)
|
||||
{
|
||||
config.WriteTo.Console(BuildExpressionTemplate(), levelSwitch: levelSwitch);
|
||||
}
|
||||
|
||||
private ExpressionTemplate BuildExpressionTemplate()
|
||||
{
|
||||
var template = "[{@l:u3}] " + LogTemplates.Base;
|
||||
|
||||
var raw = !string.IsNullOrEmpty(env.GetEnvironmentVariable("NO_COLOR"));
|
||||
return new ExpressionTemplate(template, theme: raw ? null : TemplateTheme.Code);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Recyclarr.Cli.Logging;
|
||||
|
||||
internal class IndirectLoggerDecorator(LoggerFactory loggerFactory) : ILogger
|
||||
{
|
||||
public void Write(LogEvent logEvent)
|
||||
{
|
||||
loggerFactory.Logger.Write(logEvent);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
using Recyclarr.Logging;
|
||||
using Recyclarr.Platform;
|
||||
using Serilog.Core;
|
||||
using Serilog.Templates;
|
||||
using Serilog.Templates.Themes;
|
||||
|
||||
namespace Recyclarr.Cli.Logging;
|
||||
|
||||
public class LoggerFactory(IEnvironment env, LoggingLevelSwitch levelSwitch)
|
||||
{
|
||||
public ILogger Logger { get; private set; } = LogSetup.BaseConfiguration()
|
||||
.WriteTo.Console(BuildExpressionTemplate(env), levelSwitch: levelSwitch)
|
||||
.CreateLogger();
|
||||
|
||||
private static ExpressionTemplate BuildExpressionTemplate(IEnvironment env)
|
||||
{
|
||||
var template = "[{@l:u3}] " + LogSetup.BaseTemplate;
|
||||
|
||||
var raw = !string.IsNullOrEmpty(env.GetEnvironmentVariable("NO_COLOR"));
|
||||
return new ExpressionTemplate(template, theme: raw ? null : TemplateTheme.Code);
|
||||
}
|
||||
|
||||
public void AddLogConfiguration(IEnumerable<ILogConfigurator> configurators)
|
||||
{
|
||||
var config = LogSetup.BaseConfiguration()
|
||||
.WriteTo.Logger(Logger);
|
||||
|
||||
// throw new InvalidOperationException("testing only"); // testing only
|
||||
|
||||
foreach (var configurator in configurators)
|
||||
{
|
||||
configurator.Configure(config);
|
||||
}
|
||||
|
||||
Logger = config.CreateLogger();
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
namespace Recyclarr.Cli.Processors.ErrorHandling;
|
||||
|
||||
public interface IFlurlHttpExceptionHandler
|
||||
{
|
||||
Task ProcessServiceErrorMessages(IServiceErrorMessageExtractor extractor);
|
||||
}
|
@ -1,48 +1,49 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Autofac;
|
||||
using Recyclarr.Cli.Console;
|
||||
using Recyclarr.Cli.Console.Helpers;
|
||||
using Spectre.Console;
|
||||
using Recyclarr.Cli.Processors;
|
||||
using Recyclarr.Cli.Processors.ErrorHandling;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace Recyclarr.Cli;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
[SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification =
|
||||
"Top level catch-all to translate exceptions; lack of specificity is intentional")]
|
||||
public static async Task<int> Main(string[] args)
|
||||
{
|
||||
var builder = new ContainerBuilder();
|
||||
CompositionRoot.Setup(builder);
|
||||
var scope = builder.Build();
|
||||
|
||||
var app = new CommandApp(new AutofacTypeRegistrar(builder));
|
||||
app.Configure(config =>
|
||||
try
|
||||
{
|
||||
#if DEBUG
|
||||
config.PropagateExceptions();
|
||||
config.ValidateExamples();
|
||||
#endif
|
||||
var app = scope.Resolve<CommandApp>();
|
||||
app.Configure(config =>
|
||||
{
|
||||
#if DEBUG
|
||||
config.ValidateExamples();
|
||||
#endif
|
||||
|
||||
config.Settings.StrictParsing = true;
|
||||
config.PropagateExceptions();
|
||||
config.UseStrictParsing();
|
||||
|
||||
config.SetApplicationName("recyclarr");
|
||||
config.SetApplicationVersion(
|
||||
$"v{GitVersionInformation.SemVer} ({GitVersionInformation.FullBuildMetaData})");
|
||||
config.SetApplicationName("recyclarr");
|
||||
config.SetApplicationVersion(
|
||||
$"v{GitVersionInformation.SemVer} ({GitVersionInformation.FullBuildMetaData})");
|
||||
|
||||
config.SetExceptionHandler((ex, resolver) =>
|
||||
{
|
||||
var log = (ILogger?) resolver?.Resolve(typeof(ILogger));
|
||||
if (log is null)
|
||||
{
|
||||
AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Error(ex, "Non-recoverable Exception");
|
||||
}
|
||||
CliSetup.Commands(config);
|
||||
});
|
||||
|
||||
CliSetup.Commands(config);
|
||||
});
|
||||
|
||||
return await app.RunAsync(args);
|
||||
return await app.RunAsync(args);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var log = scope.Resolve<ILogger>();
|
||||
var exceptionHandler = new ConsoleExceptionHandler(log);
|
||||
await exceptionHandler.HandleException(e);
|
||||
return (int) ExitStatus.Failed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Recyclarr.Logging;
|
||||
|
||||
public static class LogSetup
|
||||
{
|
||||
public static string BaseTemplate { get; } = GetBaseTemplateString();
|
||||
|
||||
public static LoggerConfiguration BaseConfiguration()
|
||||
{
|
||||
return new LoggerConfiguration()
|
||||
.MinimumLevel.Is(LogEventLevel.Verbose)
|
||||
.Enrich.FromLogContext()
|
||||
.Enrich.With<FlurlExceptionSanitizingEnricher>();
|
||||
}
|
||||
|
||||
private static string GetBaseTemplateString()
|
||||
{
|
||||
var scope = LogProperty.Scope;
|
||||
|
||||
return
|
||||
$"{{#if {scope} is not null}}{{{scope}}}: {{#end}}" +
|
||||
"{@m}" +
|
||||
"{#if SanitizedExceptionMessage is not null}: {SanitizedExceptionMessage}{#end}\n";
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
namespace Recyclarr.Logging;
|
||||
|
||||
public static class LogTemplates
|
||||
{
|
||||
public static string Base { get; } = GetBaseTemplateString();
|
||||
|
||||
private static string GetBaseTemplateString()
|
||||
{
|
||||
var scope = LogProperty.Scope;
|
||||
|
||||
return
|
||||
$"{{#if {scope} is not null}}{{{scope}}}: {{#end}}" +
|
||||
"{@m}" +
|
||||
"{#if SanitizedExceptionMessage is not null}: {SanitizedExceptionMessage}{#end}\n";
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Recyclarr.Logging;
|
||||
|
||||
public class LoggerFactory(IEnumerable<ILogConfigurator> configurators)
|
||||
{
|
||||
public ILogger Create()
|
||||
{
|
||||
var config = new LoggerConfiguration()
|
||||
.MinimumLevel.Is(LogEventLevel.Verbose)
|
||||
.Enrich.FromLogContext()
|
||||
.Enrich.With<FlurlExceptionSanitizingEnricher>();
|
||||
|
||||
foreach (var configurator in configurators)
|
||||
{
|
||||
configurator.Configure(config);
|
||||
}
|
||||
|
||||
return config.CreateLogger();
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
using Autofac;
|
||||
using Serilog;
|
||||
using Serilog.Core;
|
||||
using Module = Autofac.Module;
|
||||
|
||||
namespace Recyclarr.Logging;
|
||||
|
||||
public class LoggingAutofacModule : Module
|
||||
{
|
||||
protected override void Load(ContainerBuilder builder)
|
||||
{
|
||||
base.Load(builder);
|
||||
|
||||
builder.RegisterType<LoggingLevelSwitch>().SingleInstance();
|
||||
builder.RegisterType<LoggerFactory>();
|
||||
builder.Register(c => c.Resolve<LoggerFactory>().Create()).As<ILogger>().SingleInstance();
|
||||
}
|
||||
}
|
Loading…
Reference in new issue