diff --git a/Directory.Build.props b/Directory.Build.props index 03f0112..9b75831 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - + $(NoWarn);NU1903 diff --git a/src/ArmMedia.Core/DiagnosticNameAttribute.cs b/src/ArmMedia.Core/DiagnosticNameAttribute.cs new file mode 100644 index 0000000..870c675 --- /dev/null +++ b/src/ArmMedia.Core/DiagnosticNameAttribute.cs @@ -0,0 +1,19 @@ +namespace ArmMedia.Core; + +/// +/// Marks a class with a diagnostic name that dotnet-monitor can use to filter +/// log output by category. The must match the logger category +/// passed to ILoggerFactory.CreateLogger. +/// +[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +public sealed class DiagnosticNameAttribute : Attribute +{ + /// The diagnostic name / logger category. + public string Name { get; } + + /// Creates the attribute with the given diagnostic name. + public DiagnosticNameAttribute(string name) + { + Name = name; + } +} diff --git a/src/ArmMedia.Core/Orchestration/EpisodeIdentificationOrchestrator.cs b/src/ArmMedia.Core/Orchestration/EpisodeIdentificationOrchestrator.cs index a23b97b..a530d15 100644 --- a/src/ArmMedia.Core/Orchestration/EpisodeIdentificationOrchestrator.cs +++ b/src/ArmMedia.Core/Orchestration/EpisodeIdentificationOrchestrator.cs @@ -10,23 +10,25 @@ namespace ArmMedia.Core.Orchestration; /// Runs the registered provider pipeline in configured order, merges results by /// confidence, then applies multi-part and extras post-processing. /// +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed class EpisodeIdentificationOrchestrator : IEpisodeIdentificationOrchestrator { + private const string DiagnosticCategory = "EpisodeIdentificationOrchestrator"; private readonly IReadOnlyList _providers; private readonly EpisodeIdentificationOptions _options; - private readonly ILogger _logger; + private readonly ILogger _logger; /// Initialises the orchestrator with a DI-injected provider list. public EpisodeIdentificationOrchestrator( IEnumerable providers, IOptions options, - ILogger logger) + ILoggerFactory loggerFactory) { _providers = providers .OrderBy(p => options.Value.ProviderOrder.IndexOf(p.ProviderName) is int i && i >= 0 ? i : int.MaxValue) .ToList(); _options = options.Value; - _logger = logger; + _logger = loggerFactory.CreateLogger(DiagnosticCategory); _logger.LogInformation("Provider execution order: {Order}", string.Join(" → ", _providers.Select(p => p.ProviderName))); diff --git a/src/ArmMedia.DiscDbProvider/DiscDbProvider.cs b/src/ArmMedia.DiscDbProvider/DiscDbProvider.cs index b892a29..417bac8 100644 --- a/src/ArmMedia.DiscDbProvider/DiscDbProvider.cs +++ b/src/ArmMedia.DiscDbProvider/DiscDbProvider.cs @@ -10,18 +10,20 @@ namespace ArmMedia.DiscDbProvider; /// When a matching DiscDb record is found, returns results with /// , causing the orchestrator to short-circuit. /// +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed class DiscDbProvider : IEpisodeIdentificationProvider { + private const string DiagnosticCategory = "DiscDbProvider"; private readonly IDiscDbLookupService _discDbLookup; - private readonly ILogger _logger; + private readonly ILogger _logger; /// Initialises the provider with a DiscDb lookup service. public DiscDbProvider( IDiscDbLookupService discDbLookup, - ILogger logger) + ILoggerFactory loggerFactory) { _discDbLookup = discDbLookup; - _logger = logger; + _logger = loggerFactory.CreateLogger(DiagnosticCategory); } /// diff --git a/src/ArmMedia.DvdCompareProvider/DvdCompareProvider.cs b/src/ArmMedia.DvdCompareProvider/DvdCompareProvider.cs index 425969e..7f439d2 100644 --- a/src/ArmMedia.DvdCompareProvider/DvdCompareProvider.cs +++ b/src/ArmMedia.DvdCompareProvider/DvdCompareProvider.cs @@ -14,20 +14,22 @@ namespace ArmMedia.DvdCompareProvider; /// MakeMKV produces generic track filenames but the episode structure and /// runtimes are well-documented on dvdcompare.net. /// +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed partial class DvdCompareProvider : IEpisodeIdentificationProvider { + private const string DiagnosticCategory = "DvdCompareProvider"; private readonly DvdCompareProviderOptions _options; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IHttpClientFactory? _httpClientFactory; /// Initialises the provider with options, logger, and optional HTTP client factory. public DvdCompareProvider( IOptions options, - ILogger logger, + ILoggerFactory loggerFactory, IHttpClientFactory? httpClientFactory = null) { _options = options.Value; - _logger = logger; + _logger = loggerFactory.CreateLogger(DiagnosticCategory); _httpClientFactory = httpClientFactory; } diff --git a/src/ArmMedia.FileBotProvider/FileBotCliService.cs b/src/ArmMedia.FileBotProvider/FileBotCliService.cs index 687173d..a4e3c04 100644 --- a/src/ArmMedia.FileBotProvider/FileBotCliService.cs +++ b/src/ArmMedia.FileBotProvider/FileBotCliService.cs @@ -11,11 +11,13 @@ namespace ArmMedia.FileBotProvider; /// Uses filebot -rename --action test in dry-run mode to get episode /// mappings without modifying files on disk. /// +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed partial class FileBotCliService { + private const string DiagnosticCategory = "FileBotCliService"; private readonly FileBotCliRunner? _runner; private readonly FileBotProviderOptions _options; - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// Regex that matches a single filebot dry-run line: @@ -30,11 +32,11 @@ public sealed partial class FileBotCliService /// Initialises the service with an optional CLI runner, options, and logger. public FileBotCliService( FileBotCliRunner? runner, - ILogger logger, + ILoggerFactory loggerFactory, IOptions options) { _runner = runner; - _logger = logger; + _logger = loggerFactory.CreateLogger(DiagnosticCategory); _options = options.Value; } diff --git a/src/ArmMedia.FileBotProvider/FileBotProvider.cs b/src/ArmMedia.FileBotProvider/FileBotProvider.cs index ebdaca2..1fe4b46 100644 --- a/src/ArmMedia.FileBotProvider/FileBotProvider.cs +++ b/src/ArmMedia.FileBotProvider/FileBotProvider.cs @@ -12,13 +12,15 @@ namespace ArmMedia.FileBotProvider; /// If the sidecar file is absent, this provider returns an empty result set /// and logs a debug message — it never throws. /// +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed class FileBotProvider : IEpisodeIdentificationProvider { + private const string DiagnosticCategory = "FileBotProvider"; /// Constant provider name used by both the sidecar and CLI identification paths. public const string ProviderNameConst = "FileBot"; private readonly FileBotProviderOptions _options; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly FileBotCliService? _cliService; private static readonly JsonSerializerOptions _jsonOptions = new() @@ -30,11 +32,11 @@ public sealed class FileBotProvider : IEpisodeIdentificationProvider /// Initialises the provider with options, a logger, and an optional CLI service for runtime identification. public FileBotProvider( IOptions options, - ILogger logger, + ILoggerFactory loggerFactory, FileBotCliService? cliService = null) { _options = options.Value; - _logger = logger; + _logger = loggerFactory.CreateLogger(DiagnosticCategory); _cliService = cliService; } diff --git a/src/ArmMedia.Linting/DefaultLintingEngine.cs b/src/ArmMedia.Linting/DefaultLintingEngine.cs index 1098374..f5d6e38 100644 --- a/src/ArmMedia.Linting/DefaultLintingEngine.cs +++ b/src/ArmMedia.Linting/DefaultLintingEngine.cs @@ -9,18 +9,20 @@ namespace ArmMedia.Linting; /// Default implementation of that evaluates all /// registered implementations against the episode map. /// +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed class DefaultLintingEngine : ILintingEngine { + private const string DiagnosticCategory = "DefaultLintingEngine"; private readonly IReadOnlyList _rules; - private readonly ILogger _logger; + private readonly ILogger _logger; /// Initialises the engine with DI-injected rules. public DefaultLintingEngine( IEnumerable rules, - ILogger logger) + ILoggerFactory loggerFactory) { _rules = rules.ToList(); - _logger = logger; + _logger = loggerFactory.CreateLogger(DiagnosticCategory); } /// diff --git a/src/ArmMedia.OmdbProvider/OmdbProvider.cs b/src/ArmMedia.OmdbProvider/OmdbProvider.cs index dbfea08..32dca10 100644 --- a/src/ArmMedia.OmdbProvider/OmdbProvider.cs +++ b/src/ArmMedia.OmdbProvider/OmdbProvider.cs @@ -13,20 +13,22 @@ namespace ArmMedia.OmdbProvider; /// Uses the already-configured OMDB API key from host settings. /// Returns results with . /// +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed class OmdbProvider : IEpisodeIdentificationProvider { + private const string DiagnosticCategory = "OmdbProvider"; private readonly IOmdbApiKeySource _apiKeySource; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IHttpClientFactory? _httpClientFactory; /// Initialises the provider with an API key source and logger. public OmdbProvider( IOmdbApiKeySource apiKeySource, - ILogger logger, + ILoggerFactory loggerFactory, IHttpClientFactory? httpClientFactory = null) { _apiKeySource = apiKeySource; - _logger = logger; + _logger = loggerFactory.CreateLogger(DiagnosticCategory); _httpClientFactory = httpClientFactory; } diff --git a/src/ArmMedia.TmdbProvider/TmdbProvider.cs b/src/ArmMedia.TmdbProvider/TmdbProvider.cs index 2bc0f7f..c7df32f 100644 --- a/src/ArmMedia.TmdbProvider/TmdbProvider.cs +++ b/src/ArmMedia.TmdbProvider/TmdbProvider.cs @@ -13,10 +13,12 @@ namespace ArmMedia.TmdbProvider; /// Returns results with when a matching series /// is found and episodes can be assigned. /// +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed class TmdbProvider : IEpisodeIdentificationProvider { + private const string DiagnosticCategory = "TmdbProvider"; private readonly ITmdbApiKeySource _apiKeySource; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IHttpClientFactory? _httpClientFactory; private const string BaseUrl = "https://api.themoviedb.org/3"; @@ -24,11 +26,11 @@ public sealed class TmdbProvider : IEpisodeIdentificationProvider /// Initialises the provider with an API key source, logger, and optional HTTP client factory. public TmdbProvider( ITmdbApiKeySource apiKeySource, - ILogger logger, + ILoggerFactory loggerFactory, IHttpClientFactory? httpClientFactory = null) { _apiKeySource = apiKeySource; - _logger = logger; + _logger = loggerFactory.CreateLogger(DiagnosticCategory); _httpClientFactory = httpClientFactory; } diff --git a/src/ArmMedia.TvdbProvider/TvdbProvider.cs b/src/ArmMedia.TvdbProvider/TvdbProvider.cs index fe25fde..8cf1ef8 100644 --- a/src/ArmMedia.TvdbProvider/TvdbProvider.cs +++ b/src/ArmMedia.TvdbProvider/TvdbProvider.cs @@ -16,11 +16,13 @@ namespace ArmMedia.TvdbProvider; /// ordering, which matches the physical track layout on optical discs. /// Returns results with . /// +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed class TvdbProvider : IEpisodeIdentificationProvider { + private const string DiagnosticCategory = "TvdbProvider"; private readonly ITvdbApiKeySource _apiKeySource; private readonly TvdbProviderOptions _options; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IHttpClientFactory? _httpClientFactory; // Token is cached in-memory with a simple expiry (TVDB tokens last ~24h). @@ -31,12 +33,12 @@ public sealed class TvdbProvider : IEpisodeIdentificationProvider public TvdbProvider( ITvdbApiKeySource apiKeySource, IOptions options, - ILogger logger, + ILoggerFactory loggerFactory, IHttpClientFactory? httpClientFactory = null) { _apiKeySource = apiKeySource; _options = options.Value; - _logger = logger; + _logger = loggerFactory.CreateLogger(DiagnosticCategory); _httpClientFactory = httpClientFactory; } diff --git a/src/ArmRipper.Cli/Program.cs b/src/ArmRipper.Cli/Program.cs index 74c7e17..499f6aa 100644 --- a/src/ArmRipper.Cli/Program.cs +++ b/src/ArmRipper.Cli/Program.cs @@ -123,7 +123,7 @@ using (var initScope = host.Services.CreateScope()) { - var initLogger = initScope.ServiceProvider.GetRequiredService>(); + var initLogger = initScope.ServiceProvider.GetRequiredService().CreateLogger("ArmRipper.Cli.Program"); var db = initScope.ServiceProvider.GetRequiredService(); var yamlPath = "/etc/arm/config/arm.yaml"; @@ -187,7 +187,7 @@ return 1; } -var logger = host.Services.GetRequiredService>(); +var logger = host.Services.GetRequiredService().CreateLogger("ArmRipper.Cli.Program"); logger.LogInformation("ARM .NET Ripper starting for device {Device}", deviceArg); using var scope = host.Services.CreateScope(); @@ -201,7 +201,7 @@ static async Task RunReidentifyJobAsync(IServiceProvider services, int jobId, bool save) { - var logger = services.GetRequiredService>(); + var logger = services.GetRequiredService().CreateLogger("ArmRipper.Cli.Program"); var db = services.GetRequiredService(); var orchestrator = services.GetRequiredService(); var settings = services.GetRequiredService>().Value; diff --git a/src/ArmRipper.Core/Infrastructure/BackgroundRipService.cs b/src/ArmRipper.Core/Infrastructure/BackgroundRipService.cs index 25d02e3..51cf495 100644 --- a/src/ArmRipper.Core/Infrastructure/BackgroundRipService.cs +++ b/src/ArmRipper.Core/Infrastructure/BackgroundRipService.cs @@ -10,10 +10,12 @@ namespace ArmRipper.Core.Infrastructure; +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed class BackgroundRipService(IServiceScopeFactory scopeFactory, ILoggerFactory loggerFactory, IOptions settings) : IBackgroundRipService { - private readonly ILogger logger = loggerFactory.CreateLogger("BackgroundRipService"); + private const string DiagnosticCategory = "BackgroundRipService"; + private readonly ILogger logger = loggerFactory.CreateLogger(DiagnosticCategory); private readonly IOptions _settings = settings; private readonly ConcurrentDictionary _activeRips = new(); private readonly ConcurrentDictionary _ejectCooldowns = new(); diff --git a/src/ArmRipper.Core/Infrastructure/CliProcessRunner.cs b/src/ArmRipper.Core/Infrastructure/CliProcessRunner.cs index 0f4a952..78bd2bb 100644 --- a/src/ArmRipper.Core/Infrastructure/CliProcessRunner.cs +++ b/src/ArmRipper.Core/Infrastructure/CliProcessRunner.cs @@ -4,9 +4,11 @@ namespace ArmRipper.Core.Infrastructure; +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public class CliProcessRunner(ILoggerFactory loggerFactory) : ICliProcessRunner { - private readonly ILogger logger = loggerFactory.CreateLogger(nameof(CliProcessRunner)); + private const string DiagnosticCategory = "CliProcessRunner"; + private readonly ILogger logger = loggerFactory.CreateLogger(DiagnosticCategory); public async Task RunAsync( string fileName, string arguments, diff --git a/src/ArmRipper.Core/Infrastructure/DiscPollingService.cs b/src/ArmRipper.Core/Infrastructure/DiscPollingService.cs index e16e75d..d9357e6 100644 --- a/src/ArmRipper.Core/Infrastructure/DiscPollingService.cs +++ b/src/ArmRipper.Core/Infrastructure/DiscPollingService.cs @@ -20,6 +20,7 @@ namespace ArmRipper.Core.Infrastructure; /// When a disc is inserted on a drive in "autodetect" mode, it automatically kicks off /// a rip via . /// +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed class DiscPollingService( IServiceScopeFactory scopeFactory, IOptions settings, @@ -28,7 +29,8 @@ public sealed class DiscPollingService( IBackgroundRipService backgroundRipService) : BackgroundService, IDiscPollingNotifier { - private readonly ILogger _logger = loggerFactory.CreateLogger("DiscPollingService"); + private const string DiagnosticCategory = "DiscPollingService"; + private readonly ILogger _logger = loggerFactory.CreateLogger(DiagnosticCategory); private readonly IOptions _settings = settings; /// Seconds to wait after a uevent before reading sysfs. @@ -198,7 +200,7 @@ private async Task CheckDiscPollingEnabledAsync(CancellationToken ct) [SupportedOSPlatform("linux")] private Task TryStartUeventMonitorAsync(CancellationToken ct) { - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(UeventMonitor.DiagnosticCategory); var monitor = new UeventMonitor(logger); if (!monitor.TryStart()) diff --git a/src/ArmRipper.Core/Infrastructure/UeventMonitor.cs b/src/ArmRipper.Core/Infrastructure/UeventMonitor.cs index c1b2ed9..c94d8ba 100644 --- a/src/ArmRipper.Core/Infrastructure/UeventMonitor.cs +++ b/src/ArmRipper.Core/Infrastructure/UeventMonitor.cs @@ -39,8 +39,12 @@ internal sealed record UeventMessage /// returns false. /// [SupportedOSPlatform("linux")] +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] internal sealed class UeventMonitor : IDisposable { + /// DiagnosticSource category name for dotnet-monitor filtering. + internal const string DiagnosticCategory = "UeventMonitor"; + // ── Linux constants ────────────────────────────────────── private const int AF_NETLINK = 16; private const int SOCK_RAW = 3; diff --git a/src/ArmRipper.Core/Metadata/OmdbService.cs b/src/ArmRipper.Core/Metadata/OmdbService.cs index 88c97e4..b660b13 100644 --- a/src/ArmRipper.Core/Metadata/OmdbService.cs +++ b/src/ArmRipper.Core/Metadata/OmdbService.cs @@ -4,9 +4,11 @@ namespace ArmRipper.Core.Metadata; +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed class OmdbService(ILoggerFactory loggerFactory, HttpClient httpClient) { - private readonly ILogger logger = loggerFactory.CreateLogger("OmdbService"); + private const string DiagnosticCategory = "OmdbService"; + private readonly ILogger logger = loggerFactory.CreateLogger(DiagnosticCategory); public async Task SearchAsync(string apiKey, string title, string? year = null, string? plot = "short", CancellationToken ct = default) { var url = $"https://www.omdbapi.com/?s={Uri.EscapeDataString(title)}&plot={plot}&r=json&apikey={apiKey}"; diff --git a/src/ArmRipper.Core/Metadata/TmdbService.cs b/src/ArmRipper.Core/Metadata/TmdbService.cs index fe0838c..0c5a3aa 100644 --- a/src/ArmRipper.Core/Metadata/TmdbService.cs +++ b/src/ArmRipper.Core/Metadata/TmdbService.cs @@ -4,9 +4,11 @@ namespace ArmRipper.Core.Metadata; +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed partial class TmdbService(ILoggerFactory loggerFactory, HttpClient httpClient) { - private readonly ILogger logger = loggerFactory.CreateLogger("TmdbService"); + private const string DiagnosticCategory = "TmdbService"; + private readonly ILogger logger = loggerFactory.CreateLogger(DiagnosticCategory); private const string PosterBase = "https://image.tmdb.org/t/p/original"; public async Task SearchMovieAsync(string apiKey, string query, string? year = null, CancellationToken ct = default) diff --git a/src/ArmRipper.Core/Notifications/NotificationService.cs b/src/ArmRipper.Core/Notifications/NotificationService.cs index 064c2db..1f53444 100644 --- a/src/ArmRipper.Core/Notifications/NotificationService.cs +++ b/src/ArmRipper.Core/Notifications/NotificationService.cs @@ -8,6 +8,7 @@ namespace ArmRipper.Core.Notifications; +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed class NotificationService( ILoggerFactory loggerFactory, ArmDbContext db, @@ -15,7 +16,8 @@ public sealed class NotificationService( IHttpClientFactory httpClientFactory, IEnumerable broadcasters) { - private readonly ILogger logger = loggerFactory.CreateLogger("NotificationService"); + private const string DiagnosticCategory = "NotificationService"; + private readonly ILogger logger = loggerFactory.CreateLogger(DiagnosticCategory); public const string NotifyTitle = "ARM notification"; public async Task NotifyAsync(Job? job, string title, string body, CancellationToken ct = default) diff --git a/src/ArmRipper.Core/Rip/ArmRipperService.cs b/src/ArmRipper.Core/Rip/ArmRipperService.cs index 56d0250..d5d2567 100644 --- a/src/ArmRipper.Core/Rip/ArmRipperService.cs +++ b/src/ArmRipper.Core/Rip/ArmRipperService.cs @@ -12,6 +12,7 @@ namespace ArmRipper.Core.Rip; +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed class ArmRipperService( ILoggerFactory loggerFactory, ArmDbContext db, @@ -27,7 +28,8 @@ public sealed class ArmRipperService( ITrackMapperService trackMapperService, IEpisodeIdentificationOrchestrator? episodeOrchestrator = null) : IArmRipperService { - private readonly ILogger logger = loggerFactory.CreateLogger("ArmRipperService"); + private const string DiagnosticCategory = "ArmRipperService"; + private readonly ILogger logger = loggerFactory.CreateLogger(DiagnosticCategory); private static readonly TimeSpan ProgressBroadcastInterval = TimeSpan.FromMilliseconds(200); private readonly ConcurrentDictionary progressBroadcastState = new(); public async Task RipVisualMediaAsync(Job job, string logFile, bool hasDupes, bool protection, CancellationToken ct = default) diff --git a/src/ArmRipper.Core/Rip/Conductor.cs b/src/ArmRipper.Core/Rip/Conductor.cs index f5abe52..0150983 100644 --- a/src/ArmRipper.Core/Rip/Conductor.cs +++ b/src/ArmRipper.Core/Rip/Conductor.cs @@ -9,6 +9,7 @@ namespace ArmRipper.Core.Rip; +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed class Conductor( ILoggerFactory loggerFactory, ArmDbContext db, @@ -21,7 +22,8 @@ public sealed class Conductor( IEnumerable broadcasters, JobFileLoggerProvider fileLogProvider) : IConductor { - private readonly ILogger logger = loggerFactory.CreateLogger("Conductor"); + private const string DiagnosticCategory = "Conductor"; + private readonly ILogger logger = loggerFactory.CreateLogger(DiagnosticCategory); /// Broadcast job state to all connected UI clients with error handling. private async Task BroadcastJobUpdateAsync(Job job) { diff --git a/src/ArmRipper.Core/Rip/DatabaseSubmitService.cs b/src/ArmRipper.Core/Rip/DatabaseSubmitService.cs index 126d31c..868bc90 100644 --- a/src/ArmRipper.Core/Rip/DatabaseSubmitService.cs +++ b/src/ArmRipper.Core/Rip/DatabaseSubmitService.cs @@ -8,13 +8,15 @@ namespace ArmRipper.Core.Rip; +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed class DatabaseSubmitService( ArmDbContext db, IHttpClientFactory httpClientFactory, IOptions settings, ILoggerFactory loggerFactory) : IDatabaseSubmitService { - private readonly ILogger logger = loggerFactory.CreateLogger("DatabaseSubmitService"); + private const string DiagnosticCategory = "DatabaseSubmitService"; + private readonly ILogger logger = loggerFactory.CreateLogger(DiagnosticCategory); private static readonly JsonSerializerOptions JsonOptions = new() { diff --git a/src/ArmRipper.Core/Rip/DiscDbHashService.cs b/src/ArmRipper.Core/Rip/DiscDbHashService.cs index b122063..93bb938 100644 --- a/src/ArmRipper.Core/Rip/DiscDbHashService.cs +++ b/src/ArmRipper.Core/Rip/DiscDbHashService.cs @@ -11,8 +11,11 @@ namespace ArmRipper.Core.Rip; /// /// Reference: https://github.com/TheDiscDb/data/blob/main/tools/ImportBuddy/source/ImportBuddy/TheDiscDb.Core/DiscHash/HashingExtensions.cs /// -public sealed class DiscDbHashService(ILogger logger) : IDiscDbHashService +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] +public sealed class DiscDbHashService(ILoggerFactory loggerFactory) : IDiscDbHashService { + private const string DiagnosticCategory = "DiscDbHashService"; + private readonly ILogger logger = loggerFactory.CreateLogger(DiagnosticCategory); public Task ComputeHashAsync(string mountPoint, DiscType discType, CancellationToken ct = default) { var (searchPath, pattern) = discType switch diff --git a/src/ArmRipper.Core/Rip/DiscDbMappingService.cs b/src/ArmRipper.Core/Rip/DiscDbMappingService.cs index 0317da5..c6f9e7a 100644 --- a/src/ArmRipper.Core/Rip/DiscDbMappingService.cs +++ b/src/ArmRipper.Core/Rip/DiscDbMappingService.cs @@ -10,10 +10,13 @@ namespace ArmRipper.Core.Rip; /// Caches TheDiscDb lookups in the local database so re-rips of the same disc /// don't require a network call. Keyed by content hash. /// +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed class DiscDbMappingService( ArmDbContext db, - ILogger logger) : IDiscDbMappingService + ILoggerFactory loggerFactory) : IDiscDbMappingService { + private const string DiagnosticCategory = "DiscDbMappingService"; + private readonly ILogger logger = loggerFactory.CreateLogger(DiagnosticCategory); private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, diff --git a/src/ArmRipper.Core/Rip/DiscDbQueryService.cs b/src/ArmRipper.Core/Rip/DiscDbQueryService.cs index 588f213..e6bc573 100644 --- a/src/ArmRipper.Core/Rip/DiscDbQueryService.cs +++ b/src/ArmRipper.Core/Rip/DiscDbQueryService.cs @@ -13,11 +13,14 @@ namespace ArmRipper.Core.Rip; /// /// Reference: https://github.com/TheDiscDb/data/blob/main/tools/ImportBuddy/source/ImportBuddy/TheDiscDb.Client/GraphQL/Queries/GetDiscDetailByContentHash.graphql /// +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed class DiscDbQueryService( IHttpClientFactory httpClientFactory, IOptions settings, - ILogger logger) : IDiscDbQueryService + ILoggerFactory loggerFactory) : IDiscDbQueryService { + private const string DiagnosticCategory = "DiscDbQueryService"; + private readonly ILogger logger = loggerFactory.CreateLogger(DiagnosticCategory); private const string DefaultApiBaseUrl = "https://thediscdb.com/graphql"; private static readonly JsonSerializerOptions JsonOptions = new() { diff --git a/src/ArmRipper.Core/Rip/FfmpegService.cs b/src/ArmRipper.Core/Rip/FfmpegService.cs index 3180d6a..fc64512 100644 --- a/src/ArmRipper.Core/Rip/FfmpegService.cs +++ b/src/ArmRipper.Core/Rip/FfmpegService.cs @@ -9,6 +9,7 @@ namespace ArmRipper.Core.Rip; +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed partial class FfmpegService( ICliProcessRunner runner, ILoggerFactory loggerFactory, @@ -16,7 +17,8 @@ public sealed partial class FfmpegService( IOptions settings, ITranscodeSlotLimiter transcodeSlotLimiter) : IFfmpegService { - private readonly ILogger logger = loggerFactory.CreateLogger("FfmpegService"); + private const string DiagnosticCategory = "FfmpegService"; + private readonly ILogger logger = loggerFactory.CreateLogger(DiagnosticCategory); public async Task TranscodeMkvAsync(Job job, string rawPath, string outputPath, IProgress? progress = null, CancellationToken ct = default) { logger.LogInformation("Starting FFmpeg for MKV files"); diff --git a/src/ArmRipper.Core/Rip/HandBrakeService.cs b/src/ArmRipper.Core/Rip/HandBrakeService.cs index 6be8db2..d7bd2e4 100644 --- a/src/ArmRipper.Core/Rip/HandBrakeService.cs +++ b/src/ArmRipper.Core/Rip/HandBrakeService.cs @@ -8,6 +8,7 @@ namespace ArmRipper.Core.Rip; +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed partial class HandBrakeService( ICliProcessRunner runner, ILoggerFactory loggerFactory, @@ -15,7 +16,8 @@ public sealed partial class HandBrakeService( IOptions settings, ITranscodeSlotLimiter transcodeSlotLimiter) : IHandBrakeService { - private readonly ILogger logger = loggerFactory.CreateLogger("HandBrakeService"); + private const string DiagnosticCategory = "HandBrakeService"; + private readonly ILogger logger = loggerFactory.CreateLogger(DiagnosticCategory); public async Task TranscodeMkvAsync(Job job, string rawPath, string outputPath, IProgress? progress = null, CancellationToken ct = default) { logger.LogInformation("Starting HandBrake for MKV files"); diff --git a/src/ArmRipper.Core/Rip/IdentifyService.cs b/src/ArmRipper.Core/Rip/IdentifyService.cs index 1e9c334..15575b7 100644 --- a/src/ArmRipper.Core/Rip/IdentifyService.cs +++ b/src/ArmRipper.Core/Rip/IdentifyService.cs @@ -11,6 +11,7 @@ namespace ArmRipper.Core.Rip; +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed partial class IdentifyService( ICliProcessRunner runner, ILoggerFactory loggerFactory, @@ -22,7 +23,8 @@ public sealed partial class IdentifyService( IDiscDbMappingService discDbMappingService, IBackgroundRipService backgroundRipService) : IIdentifyService { - private readonly ILogger logger = loggerFactory.CreateLogger("IdentifyService"); + private const string DiagnosticCategory = "IdentifyService"; + private readonly ILogger logger = loggerFactory.CreateLogger(DiagnosticCategory); private static readonly JsonSerializerOptions JsonOptions = new() { diff --git a/src/ArmRipper.Core/Rip/MakeMkvService.cs b/src/ArmRipper.Core/Rip/MakeMkvService.cs index 3ffd995..da0733b 100644 --- a/src/ArmRipper.Core/Rip/MakeMkvService.cs +++ b/src/ArmRipper.Core/Rip/MakeMkvService.cs @@ -10,8 +10,10 @@ namespace ArmRipper.Core.Rip; +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public partial class MakeMkvService : IMakeMkvService { + private const string DiagnosticCategory = "MakeMkvService"; private const string Source = "MakeMKV"; private const string BetaKeyApi = "https://cable.ayra.ch/MakeMKV/api.php?json"; private const string BetaKeyForum = "https://forum.makemkv.com/forum/viewtopic.php?f=5&t=1053"; @@ -28,7 +30,7 @@ public partial class MakeMkvService : IMakeMkvService public MakeMkvService(ICliProcessRunner runner, ILoggerFactory loggerFactory, IOptions settings, ArmDbContext db, IHttpClientFactory httpClientFactory) { _runner = runner; - _logger = loggerFactory.CreateLogger("MakeMkvService"); + _logger = loggerFactory.CreateLogger(DiagnosticCategory); _settings = settings; _db = db; _httpClientFactory = httpClientFactory; diff --git a/src/ArmRipper.Core/Rip/MusicBrainzService.cs b/src/ArmRipper.Core/Rip/MusicBrainzService.cs index 9bf4317..b6a3296 100644 --- a/src/ArmRipper.Core/Rip/MusicBrainzService.cs +++ b/src/ArmRipper.Core/Rip/MusicBrainzService.cs @@ -9,6 +9,7 @@ namespace ArmRipper.Core.Rip; +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed partial class MusicBrainzService( ICliProcessRunner runner, ILoggerFactory loggerFactory, @@ -16,7 +17,8 @@ public sealed partial class MusicBrainzService( IOptions settings, HttpClient httpClient) : IMusicBrainzService { - private readonly ILogger logger = loggerFactory.CreateLogger("MusicBrainzService"); + private const string DiagnosticCategory = "MusicBrainzService"; + private readonly ILogger logger = loggerFactory.CreateLogger(DiagnosticCategory); public async Task IdentifyAsync(Job job, CancellationToken ct = default) { var discId = await GetDiscIdAsync(job.DevPath!, ct); diff --git a/src/ArmRipper.Core/Rip/TrackMapperService.cs b/src/ArmRipper.Core/Rip/TrackMapperService.cs index 03a8866..a203353 100644 --- a/src/ArmRipper.Core/Rip/TrackMapperService.cs +++ b/src/ArmRipper.Core/Rip/TrackMapperService.cs @@ -8,10 +8,13 @@ namespace ArmRipper.Core.Rip; /// Matches TheDiscDb title metadata to MakeMKV-identified tracks using /// a combination of track index, duration, and file size matching. /// +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed class TrackMapperService( ArmDbContext db, - ILogger logger) : ITrackMapperService + ILoggerFactory loggerFactory) : ITrackMapperService { + private const string DiagnosticCategory = "TrackMapperService"; + private readonly ILogger logger = loggerFactory.CreateLogger(DiagnosticCategory); // Duration tolerance: ±5% or ±30s, whichever is larger private const double DurationTolerancePercent = 0.05; private const int DurationToleranceSeconds = 30; diff --git a/src/ArmRipper.WebUi/Program.cs b/src/ArmRipper.WebUi/Program.cs index 9c2bc9e..7e73b07 100644 --- a/src/ArmRipper.WebUi/Program.cs +++ b/src/ArmRipper.WebUi/Program.cs @@ -135,7 +135,7 @@ using (var scope = app.Services.CreateScope()) { - var initLogger = scope.ServiceProvider.GetRequiredService>(); + var initLogger = scope.ServiceProvider.GetRequiredService().CreateLogger("ArmRipper.WebUi.Program"); var db = scope.ServiceProvider.GetRequiredService(); var yamlPath = "/etc/arm/config/arm.yaml"; diff --git a/src/ArmRipper.WebUi/Services/ShutdownJobCancellationService.cs b/src/ArmRipper.WebUi/Services/ShutdownJobCancellationService.cs index 9fc9a52..2f1872f 100644 --- a/src/ArmRipper.WebUi/Services/ShutdownJobCancellationService.cs +++ b/src/ArmRipper.WebUi/Services/ShutdownJobCancellationService.cs @@ -16,22 +16,24 @@ namespace ArmRipper.WebUi.Services; /// - For each active rip, sets the job status to Stopping and saves to DB /// BEFORE cancelling the token. This ensures the job can be resumed on restart. /// +[ArmMedia.Core.DiagnosticName(DiagnosticCategory)] public sealed class ShutdownJobCancellationService : IHostedService { + private const string DiagnosticCategory = "ShutdownJobCancellationService"; private readonly IBackgroundRipService _backgroundRipService; private readonly IServiceScopeFactory _scopeFactory; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IHostApplicationLifetime _appLifetime; public ShutdownJobCancellationService( IBackgroundRipService backgroundRipService, IServiceScopeFactory scopeFactory, - ILogger logger, + ILoggerFactory loggerFactory, IHostApplicationLifetime appLifetime) { _backgroundRipService = backgroundRipService; _scopeFactory = scopeFactory; - _logger = logger; + _logger = loggerFactory.CreateLogger(DiagnosticCategory); _appLifetime = appLifetime; } diff --git a/tests/ArmMedia.Tests/DiscDbProvider/DiscDbProviderTests.cs b/tests/ArmMedia.Tests/DiscDbProvider/DiscDbProviderTests.cs index c9e82a0..8e64df0 100644 --- a/tests/ArmMedia.Tests/DiscDbProvider/DiscDbProviderTests.cs +++ b/tests/ArmMedia.Tests/DiscDbProvider/DiscDbProviderTests.cs @@ -67,7 +67,7 @@ public async Task WhenDiscDbRecordFound_ReturnsDefinitiveResults() var provider = new ArmMedia.DiscDbProvider.DiscDbProvider( lookupService.Object, - NullLogger.Instance); + NullLoggerFactory.Instance); var results = await provider.IdentifyAsync(MakeContext()); @@ -90,7 +90,7 @@ public async Task WhenNoDiscDbRecord_ReturnsEmpty() var provider = new ArmMedia.DiscDbProvider.DiscDbProvider( lookupService.Object, - NullLogger.Instance); + NullLoggerFactory.Instance); var results = await provider.IdentifyAsync(MakeContext()); @@ -107,7 +107,7 @@ public void ProviderName_IsDiscDb() var lookupService = new Mock(); var provider = new ArmMedia.DiscDbProvider.DiscDbProvider( lookupService.Object, - NullLogger.Instance); + NullLoggerFactory.Instance); Assert.Equal("DiscDb", provider.ProviderName); } diff --git a/tests/ArmMedia.Tests/FileBotProvider/FileBotProviderTests.cs b/tests/ArmMedia.Tests/FileBotProvider/FileBotProviderTests.cs index 13e4be1..cee54ac 100644 --- a/tests/ArmMedia.Tests/FileBotProvider/FileBotProviderTests.cs +++ b/tests/ArmMedia.Tests/FileBotProvider/FileBotProviderTests.cs @@ -25,7 +25,7 @@ public void Dispose() private ArmMedia.FileBotProvider.FileBotProvider MakeProvider(string mapPath) { var options = Options.Create(new FileBotProviderOptions { MapFilePath = mapPath }); - return new ArmMedia.FileBotProvider.FileBotProvider(options, NullLogger.Instance); + return new ArmMedia.FileBotProvider.FileBotProvider(options, NullLoggerFactory.Instance); } private DiscContext MakeContext(string discId = "TEST_DISC") => new() diff --git a/tests/ArmMedia.Tests/Orchestration/ExtrasDetectionTests.cs b/tests/ArmMedia.Tests/Orchestration/ExtrasDetectionTests.cs index 2c8af1e..ea31633 100644 --- a/tests/ArmMedia.Tests/Orchestration/ExtrasDetectionTests.cs +++ b/tests/ArmMedia.Tests/Orchestration/ExtrasDetectionTests.cs @@ -24,7 +24,7 @@ private static EpisodeIdentificationOrchestrator MakeOrchestrator( IEnumerable providers, EpisodeIdentificationOptions? opts = null) => new(providers, Options.Create(opts ?? DefaultOptions()), - NullLogger.Instance); + NullLoggerFactory.Instance); // ───────────────────────────────────────────────────────────────────────── // TC-EX-01: Short track with no episode match → IsExtra = true diff --git a/tests/ArmMedia.Tests/Orchestration/MultiPartDetectionTests.cs b/tests/ArmMedia.Tests/Orchestration/MultiPartDetectionTests.cs index c72ce74..46915df 100644 --- a/tests/ArmMedia.Tests/Orchestration/MultiPartDetectionTests.cs +++ b/tests/ArmMedia.Tests/Orchestration/MultiPartDetectionTests.cs @@ -28,7 +28,7 @@ private static EpisodeIdentificationOrchestrator MakeOrchestrator( return new EpisodeIdentificationOrchestrator( providers, Options.Create(opts ?? DefaultOptions()), - NullLogger.Instance); + NullLoggerFactory.Instance); } // ───────────────────────────────────────────────────────────────────────── diff --git a/tests/ArmMedia.Tests/Orchestration/OrchestratorMergeTests.cs b/tests/ArmMedia.Tests/Orchestration/OrchestratorMergeTests.cs index 464dc89..6d2c9d4 100644 --- a/tests/ArmMedia.Tests/Orchestration/OrchestratorMergeTests.cs +++ b/tests/ArmMedia.Tests/Orchestration/OrchestratorMergeTests.cs @@ -44,7 +44,7 @@ private static EpisodeIdentificationOrchestrator MakeOrchestrator( return new EpisodeIdentificationOrchestrator( providers, options, - NullLogger.Instance); + NullLoggerFactory.Instance); } // ───────────────────────────────────────────────────────────────────────── diff --git a/tests/ArmMedia.Tests/Orchestration/PipelineIntegrationTests.cs b/tests/ArmMedia.Tests/Orchestration/PipelineIntegrationTests.cs index c96e3bf..fe6d784 100644 --- a/tests/ArmMedia.Tests/Orchestration/PipelineIntegrationTests.cs +++ b/tests/ArmMedia.Tests/Orchestration/PipelineIntegrationTests.cs @@ -79,7 +79,7 @@ public async Task FullPipeline_ProducesExpectedEpisodeMap() var orchestrator = new EpisodeIdentificationOrchestrator( [discDbProvider.Object, fileBotProvider.Object], options, - NullLogger.Instance); + NullLoggerFactory.Instance); // ── Act: Run identification ────────────────────────────────────────── var episodeMap = await orchestrator.IdentifyAsync(ctx); @@ -143,7 +143,7 @@ public async Task FullPipeline_ProducesExpectedEpisodeMap() [new DuplicateEpisodeLintRule(), new EpisodeGapLintRule(), new LowConfidenceLintRule(), new RuntimeMismatchLintRule(), new MultiPartDurationMismatchLintRule(), new SeasonMismatchLintRule()], - NullLogger.Instance); + NullLoggerFactory.Instance); var report = lintEngine.Lint(episodeMap, lintOptions); // ── Assert: Lint report is clean ───────────────────────────────────── diff --git a/tests/ArmRipper.Core.Tests/DiscDbHashServiceTests.cs b/tests/ArmRipper.Core.Tests/DiscDbHashServiceTests.cs index 611a986..34bac9b 100644 --- a/tests/ArmRipper.Core.Tests/DiscDbHashServiceTests.cs +++ b/tests/ArmRipper.Core.Tests/DiscDbHashServiceTests.cs @@ -1,7 +1,6 @@ using ArmRipper.Core.Models; using ArmRipper.Core.Rip; -using Microsoft.Extensions.Logging; -using Moq; +using Microsoft.Extensions.Logging.Abstractions; namespace ArmRipper.Core.Tests.Rip; @@ -9,8 +8,7 @@ public class DiscDbHashServiceTests { private static DiscDbHashService CreateService() { - var logger = new Mock>().Object; - return new DiscDbHashService(logger); + return new DiscDbHashService(NullLoggerFactory.Instance); } [Fact] diff --git a/tests/ArmRipper.Core.Tests/TrackMapperServiceTests.cs b/tests/ArmRipper.Core.Tests/TrackMapperServiceTests.cs index f32f028..af1b787 100644 --- a/tests/ArmRipper.Core.Tests/TrackMapperServiceTests.cs +++ b/tests/ArmRipper.Core.Tests/TrackMapperServiceTests.cs @@ -4,8 +4,7 @@ using ArmRipper.Core.Models; using ArmRipper.Core.Rip; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Moq; +using Microsoft.Extensions.Logging.Abstractions; namespace ArmRipper.Core.Tests.Rip; @@ -19,8 +18,7 @@ private static (TrackMapperService Service, ArmDbContext Db) CreateService() var db = new ArmDbContext(options); db.Database.OpenConnection(); db.Database.EnsureCreated(); - var logger = new Mock>().Object; - return (new TrackMapperService(db, logger), db); + return (new TrackMapperService(db, NullLoggerFactory.Instance), db); } private static async Task AddJobWithTracksAsync(ArmDbContext db, Job job, params Track[] tracks)