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)