From 43a11f0e4c4775c4525c99098c7570a0d5298f59 Mon Sep 17 00:00:00 2001 From: Marius Nechifor Date: Thu, 28 Nov 2024 23:12:08 +0200 Subject: [PATCH] added Serilog and file logging (#17) --- README.md | 14 ++-- code/Common/Common.csproj | 4 ++ .../Configuration/Logging/FileLogConfig.cs | 12 ++++ .../Configuration/Logging/LoggingConfig.cs | 16 +++++ .../DependencyInjection/LoggingDI.cs | 66 +++++++++++++++++++ code/Executable/Executable.csproj | 8 ++- code/Executable/Jobs/GenericJob.cs | 3 + code/Executable/Program.cs | 1 + code/Executable/appsettings.Development.json | 9 ++- code/Executable/appsettings.json | 9 ++- code/test/docker-compose.yml | 6 +- 11 files changed, 132 insertions(+), 16 deletions(-) create mode 100644 code/Common/Configuration/Logging/FileLogConfig.cs create mode 100644 code/Common/Configuration/Logging/LoggingConfig.cs create mode 100644 code/Executable/DependencyInjection/LoggingDI.cs diff --git a/README.md b/README.md index ac15bf7..5208f9e 100644 --- a/README.md +++ b/README.md @@ -88,8 +88,12 @@ docker run -d \ version: "3.3" services: cleanuperr: + volumes: + - ./cleanuperr/logs:/var/logs environment: - - LOGGING__LOGLEVEL__DEFAULT=Information + - LOGGING__LOGLEVEL=Information + - LOGGING__FILE__ENABLED=false + - LOGGING__FILE__PATH=/var/logs/ - TRIGGERS__QUEUECLEANER=0 0/5 * * * ? - TRIGGERS__CONTENTBLOCKER=0 0/5 * * * ? @@ -137,7 +141,9 @@ services: | Variable | Required | Description | Default value | |---|---|---|---| -| LOGGING__LOGLEVEL__DEFAULT | No | Can be `Debug`, `Information`, `Warning` or `Error` | Information | +| LOGGING__LOGLEVEL | No | Can be `Verbose`, `Debug`, `Information`, `Warning`, `Error` or `Fatal` | `Information` | +| LOGGING__FILE__ENABLED | No | Enable or disable logging to file | false | +| LOGGING__FILE__PATH | No | Directory where to save the log files | empty | ||||| | TRIGGERS__QUEUECLEANER | Yes if queue cleaner is enabled | [Quartz cron trigger](https://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html) | 0 0/5 * * * ? | | TRIGGERS__CONTENTBLOCKER | Yes if content blocker is enabled | [Quartz cron trigger](https://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html) | 0 0/5 * * * ? | @@ -165,11 +171,11 @@ services: | TRANSMISSION__USERNAME | No | Transmission user | empty | | TRANSMISSION__PASSWORD | No | Transmission password | empty | ||||| -| SONARR__ENABLED | No | Whether Sonarr cleanup is enabled or not | true | +| SONARR__ENABLED | No | Enable or disable Sonarr cleanup | true | | SONARR__INSTANCES__0__URL | Yes | First Sonarr instance url | http://localhost:8989 | | SONARR__INSTANCES__0__APIKEY | Yes | First Sonarr instance API key | empty | ||||| -| RADARR__ENABLED | No | Whether Radarr cleanup is enabled or not | false | +| RADARR__ENABLED | No | Enable or disable Radarr cleanup | false | | RADARR__INSTANCES__0__URL | Yes | First Radarr instance url | http://localhost:8989 | | RADARR__INSTANCES__0__APIKEY | Yes | First Radarr instance API key | empty | diff --git a/code/Common/Common.csproj b/code/Common/Common.csproj index 3a63532..8edf131 100644 --- a/code/Common/Common.csproj +++ b/code/Common/Common.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/code/Common/Configuration/Logging/FileLogConfig.cs b/code/Common/Configuration/Logging/FileLogConfig.cs new file mode 100644 index 0000000..17d3ed7 --- /dev/null +++ b/code/Common/Configuration/Logging/FileLogConfig.cs @@ -0,0 +1,12 @@ +namespace Common.Configuration.Logging; + +public class FileLogConfig : IConfig +{ + public bool Enabled { get; set; } + + public string Path { get; set; } = string.Empty; + + public void Validate() + { + } +} \ No newline at end of file diff --git a/code/Common/Configuration/Logging/LoggingConfig.cs b/code/Common/Configuration/Logging/LoggingConfig.cs new file mode 100644 index 0000000..269f0e6 --- /dev/null +++ b/code/Common/Configuration/Logging/LoggingConfig.cs @@ -0,0 +1,16 @@ +using Serilog.Events; + +namespace Common.Configuration.Logging; + +public class LoggingConfig : IConfig +{ + public const string SectionName = "Logging"; + + public LogEventLevel LogLevel { get; set; } + + public FileLogConfig? File { get; set; } + + public void Validate() + { + } +} \ No newline at end of file diff --git a/code/Executable/DependencyInjection/LoggingDI.cs b/code/Executable/DependencyInjection/LoggingDI.cs new file mode 100644 index 0000000..5efbd6c --- /dev/null +++ b/code/Executable/DependencyInjection/LoggingDI.cs @@ -0,0 +1,66 @@ +using Common.Configuration.Logging; +using Infrastructure.Verticals.ContentBlocker; +using Infrastructure.Verticals.QueueCleaner; +using Serilog; +using Serilog.Events; +using Serilog.Templates; +using Serilog.Templates.Themes; + +namespace Executable.DependencyInjection; + +public static class LoggingDI +{ + public static ILoggingBuilder AddLogging(this ILoggingBuilder builder, IConfiguration configuration) + { + LoggingConfig? config = configuration.GetSection(LoggingConfig.SectionName).Get(); + + if (!string.IsNullOrEmpty(config?.File?.Path) && !Directory.Exists(config.File.Path)) + { + try + { + Directory.CreateDirectory(config.File.Path); + } + catch (Exception exception) + { + throw new Exception($"log file path is not a valid directory | {config.File.Path}", exception); + } + } + + LoggerConfiguration logConfig = new(); + const string consoleOutputTemplate = "[{@t:yyyy-MM-dd HH:mm:ss.fff} {@l:u3}]{#if JobName is not null} {Concat('[',JobName,']'),PAD}{#end} {@m}\n{@x}"; + const string fileOutputTemplate = "{@t:yyyy-MM-dd HH:mm:ss.fff zzz} [{@l:u3}]{#if JobName is not null} {Concat('[',JobName,']'),PAD}{#end} {@m:lj}\n{@x}"; + LogEventLevel level = LogEventLevel.Information; + List jobNames = [nameof(ContentBlocker), nameof(QueueCleaner)]; + int padding = jobNames.Max(x => x.Length) + 2; + + if (config is not null) + { + level = config.LogLevel; + + if (config.File?.Enabled is true) + { + logConfig.WriteTo.File( + path: Path.Combine(config.File.Path, "cleanuperr-.txt"), + formatter: new ExpressionTemplate(fileOutputTemplate.Replace("PAD", padding.ToString())), + fileSizeLimitBytes: 10L * 1024 * 1024, + rollingInterval: RollingInterval.Day, + rollOnFileSizeLimit: true + ); + } + } + + Log.Logger = logConfig + .MinimumLevel.Is(level) + .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information) + .MinimumLevel.Override("Quartz", LogEventLevel.Warning) + .MinimumLevel.Override("System.Net.Http.HttpClient", LogEventLevel.Error) + .WriteTo.Console(new ExpressionTemplate(consoleOutputTemplate.Replace("PAD", padding.ToString()))) + .Enrich.FromLogContext() + .Enrich.WithProperty("ApplicationName", "cleanuperr") + .CreateLogger(); + + return builder + .ClearProviders() + .AddSerilog(); + } +} \ No newline at end of file diff --git a/code/Executable/Executable.csproj b/code/Executable/Executable.csproj index 703a45e..0518296 100644 --- a/code/Executable/Executable.csproj +++ b/code/Executable/Executable.csproj @@ -9,11 +9,17 @@ - + + + + + + + diff --git a/code/Executable/Jobs/GenericJob.cs b/code/Executable/Jobs/GenericJob.cs index c8fce01..8fc8adc 100644 --- a/code/Executable/Jobs/GenericJob.cs +++ b/code/Executable/Jobs/GenericJob.cs @@ -1,5 +1,6 @@ using Infrastructure.Verticals.Jobs; using Quartz; +using Serilog.Context; namespace Executable.Jobs; @@ -19,6 +20,8 @@ public sealed class GenericJob : IJob public async Task Execute(IJobExecutionContext context) { + using var _ = LogContext.PushProperty("JobName", typeof(T).Name); + try { await _handler.ExecuteAsync(); diff --git a/code/Executable/Program.cs b/code/Executable/Program.cs index a987692..1c1ebb2 100644 --- a/code/Executable/Program.cs +++ b/code/Executable/Program.cs @@ -3,6 +3,7 @@ using Executable.DependencyInjection; var builder = Host.CreateApplicationBuilder(args); builder.Services.AddInfrastructure(builder.Configuration); +builder.Logging.AddLogging(builder.Configuration); var host = builder.Build(); diff --git a/code/Executable/appsettings.Development.json b/code/Executable/appsettings.Development.json index 50aa0af..37bbe3e 100644 --- a/code/Executable/appsettings.Development.json +++ b/code/Executable/appsettings.Development.json @@ -1,10 +1,9 @@ { "Logging": { - "LogLevel": { - "Default": "Debug", - "Microsoft.Hosting.Lifetime": "Information", - "Quartz": "Warning", - "System.Net.Http.HttpClient": "Error" + "LogLevel": "Debug", + "File": { + "Enabled": false, + "Path": "" } }, "Triggers": { diff --git a/code/Executable/appsettings.json b/code/Executable/appsettings.json index a21b054..a5d5454 100644 --- a/code/Executable/appsettings.json +++ b/code/Executable/appsettings.json @@ -1,10 +1,9 @@ { "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.Hosting.Lifetime": "Information", - "Quartz": "Warning", - "System.Net.Http.HttpClient": "Error" + "LogLevel": "Information", + "File": { + "Enabled": false, + "Path": "" } }, "Triggers": { diff --git a/code/test/docker-compose.yml b/code/test/docker-compose.yml index 27d77a4..002520a 100644 --- a/code/test/docker-compose.yml +++ b/code/test/docker-compose.yml @@ -171,7 +171,9 @@ services: image: flaminel/cleanuperr:latest container_name: cleanuperr environment: - - LOGGING__LOGLEVEL__DEFAULT=Debug + - LOGGING__LOGLEVEL=Debug + - LOGGING__FILE__ENABLED=false + - LOGGING__FILE__PATH=/var/logs - TRIGGERS__QUEUECLEANER=0/30 * * * * ? - TRIGGERS__CONTENTBLOCKER=0/30 * * * * ? @@ -207,6 +209,8 @@ services: - RADARR__ENABLED=true - RADARR__INSTANCES__0__URL=http://radarr:7878 - RADARR__INSTANCES__0__APIKEY=705b553732ab4167ab23909305d60600 + volumes: + - ./data/cleanuperr/logs:/var/logs restart: unless-stopped depends_on: - qbittorrent