refactor: Delay file logging until IAppPaths initialized

The logger, which also writes to a file in addition to console,
requires `IAppPaths` in order to find the directory to place the log
files. However, this cannot be obtained until the system calculates
the app data directory OR the user specifies it with the `--app-data`
option.

A custom sink has been added that will allow the logger to write to
console without file logs until that initialization is performed and
the log directory is available.
pull/76/head
Robert Dailey 2 years ago
parent 035836db49
commit a1f07c4ad0

@ -0,0 +1,29 @@
using System.IO.Abstractions;
using AutoFixture.NUnit3;
using NSubstitute;
using NUnit.Framework;
using Recyclarr.Logging;
using Serilog.Events;
using TestLibrary.AutoFixture;
using TrashLib;
namespace Recyclarr.Tests.Logging;
[TestFixture]
[Parallelizable(ParallelScope.All)]
public class DelayedFileSinkTest
{
[Test, AutoMockData]
public void Should_not_open_file_if_app_data_invalid(
[Frozen] IAppPaths paths,
[Frozen] IFileSystem fs,
LogEvent logEvent,
DelayedFileSink sut)
{
paths.IsAppDataPathValid.Returns(false);
sut.Emit(logEvent);
fs.File.DidNotReceiveWithAnyArgs().OpenWrite(default!);
}
}

@ -16,6 +16,7 @@ public class AppPaths : IAppPaths
public string DefaultConfigFilename => "recyclarr.yml";
public bool IsAppDataPathValid => _appDataPath is not null;
public void SetAppDataPath(string path) => _appDataPath = path;
[SuppressMessage("Design", "CA1024:Use properties where appropriate")]

@ -32,6 +32,7 @@ public static class CompositionRoot
private static void SetupLogging(ContainerBuilder builder)
{
builder.RegisterType<LogJanitor>().As<ILogJanitor>();
builder.RegisterType<DelayedFileSink>().As<IDelayedFileSink>();
builder.RegisterType<LoggingLevelSwitch>().SingleInstance();
builder.RegisterType<LoggerFactory>();
builder.Register(c => c.Resolve<LoggerFactory>().Create())

@ -0,0 +1,39 @@
using System.IO.Abstractions;
using Serilog.Events;
using TrashLib;
namespace Recyclarr.Logging;
public class DelayedFileSink : IDelayedFileSink
{
private readonly IAppPaths _paths;
private readonly Lazy<StreamWriter> _stream;
public DelayedFileSink(IAppPaths paths, IFileSystem fs)
{
_paths = paths;
_stream = new Lazy<StreamWriter>(() =>
{
var logPath = fs.Path.Combine(_paths.LogDirectory, $"trash_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log");
return fs.File.CreateText(logPath);
});
}
public void Emit(LogEvent logEvent)
{
if (!_paths.IsAppDataPathValid)
{
return;
}
_stream.Value.WriteLine(logEvent.RenderMessage());
}
public void Dispose()
{
if (_stream.IsValueCreated)
{
_stream.Value.Close();
}
}
}

@ -0,0 +1,7 @@
using Serilog.Core;
namespace Recyclarr.Logging;
public interface IDelayedFileSink : ILogEventSink, IDisposable
{
}

@ -1,33 +1,27 @@
using System.IO.Abstractions;
using Serilog;
using Serilog.Core;
using TrashLib;
namespace Recyclarr.Logging;
public class LoggerFactory
{
private readonly IFileSystem _fs;
private readonly IAppPaths _paths;
private readonly LoggingLevelSwitch _logLevel;
private readonly IDelayedFileSink _fileSink;
public LoggerFactory(IFileSystem fs, IAppPaths paths, LoggingLevelSwitch logLevel)
public LoggerFactory(LoggingLevelSwitch logLevel, IDelayedFileSink fileSink)
{
_fs = fs;
_paths = paths;
_logLevel = logLevel;
_fileSink = fileSink;
}
public ILogger Create()
{
var logPath = _fs.Path.Combine(_paths.LogDirectory, $"trash_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log");
const string consoleTemplate = "[{Level:u3}] {Message:lj}{NewLine}{Exception}";
return new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console(outputTemplate: consoleTemplate, levelSwitch: _logLevel)
.WriteTo.File(logPath)
.WriteTo.Sink(_fileSink)
.CreateLogger();
}
}

@ -10,4 +10,5 @@ public interface IAppPaths
string RepoDirectory { get; }
string CacheDirectory { get; }
string DefaultConfigFilename { get; }
bool IsAppDataPathValid { get; }
}

Loading…
Cancel
Save