diff --git a/code/Common/Configuration/General/GeneralConfig.cs b/code/Common/Configuration/General/GeneralConfig.cs index a64d9432..9b806bba 100644 --- a/code/Common/Configuration/General/GeneralConfig.cs +++ b/code/Common/Configuration/General/GeneralConfig.cs @@ -1,6 +1,5 @@ using Common.Enums; using Common.Exceptions; -using Newtonsoft.Json; using Serilog.Events; namespace Common.Configuration.General; @@ -13,8 +12,7 @@ public sealed record GeneralConfig : IConfig public ushort HttpTimeout { get; init; } = 100; - [JsonProperty("http_validate_cert")] - public CertificateValidationType CertificateValidation { get; init; } = CertificateValidationType.Enabled; + public CertificateValidationType HttpCertificateValidation { get; init; } = CertificateValidationType.Enabled; public bool SearchEnabled { get; init; } = true; diff --git a/code/Data/Enums/InstanceType.cs b/code/Data/Enums/InstanceType.cs index c9dfe8d9..498c9dbb 100644 --- a/code/Data/Enums/InstanceType.cs +++ b/code/Data/Enums/InstanceType.cs @@ -5,5 +5,6 @@ public enum InstanceType Sonarr, Radarr, Lidarr, - Readarr + Readarr, + Whisparr, } \ No newline at end of file diff --git a/code/Executable/Controllers/LoggingDemoController.cs b/code/Executable/Controllers/LoggingDemoController.cs deleted file mode 100644 index 4e4c8353..00000000 --- a/code/Executable/Controllers/LoggingDemoController.cs +++ /dev/null @@ -1,90 +0,0 @@ -using Infrastructure.Logging; -using Microsoft.AspNetCore.Mvc; - -namespace Executable.Controllers; - -/// -/// Sample controller demonstrating the use of the enhanced logging system. -/// This is for demonstration purposes only. -/// -[ApiController] -[Route("api/[controller]")] -public class LoggingDemoController : ControllerBase -{ - private readonly ILogger _logger; - - public LoggingDemoController(ILogger logger) - { - _logger = logger; - } - - [HttpGet("system")] - public IActionResult LogSystemMessage() - { - // Using the Category extension - _logger.WithCategory(LoggingCategoryConstants.System) - .LogInformation("This is a system log message"); - - return Ok("System log sent"); - } - - [HttpGet("api")] - public IActionResult LogApiMessage() - { - _logger.WithCategory(LoggingCategoryConstants.Api) - .LogInformation("This is an API log message"); - - return Ok("API log sent"); - } - - [HttpGet("job")] - public IActionResult LogJobMessage([FromQuery] string jobName = "CleanupJob") - { - _logger.WithCategory(LoggingCategoryConstants.Jobs) - .WithJob(jobName) - .LogInformation("This is a job-related log message"); - - return Ok($"Job log sent for {jobName}"); - } - - [HttpGet("instance")] - public IActionResult LogInstanceMessage([FromQuery] string instance = "Sonarr") - { - _logger.WithCategory(instance.ToUpper()) - .WithInstance(instance) - .LogInformation("This is an instance-related log message"); - - return Ok($"Instance log sent for {instance}"); - } - - [HttpGet("combined")] - public IActionResult LogCombinedMessage( - [FromQuery] string category = "JOBS", - [FromQuery] string jobName = "ContentBlocker", - [FromQuery] string instance = "Sonarr") - { - _logger.WithCategory(category) - .WithJob(jobName) - .WithInstance(instance) - .LogInformation("This log message combines category, job name, and instance"); - - return Ok("Combined log sent"); - } - - [HttpGet("error")] - public IActionResult LogErrorMessage() - { - try - { - // Simulate an error - throw new InvalidOperationException("This is a test exception"); - } - catch (Exception ex) - { - _logger.WithCategory(LoggingCategoryConstants.System) - .LogError(ex, "An error occurred during processing"); - } - - return Ok("Error log sent"); - } -} diff --git a/code/Executable/DependencyInjection/LoggingDI.cs b/code/Executable/DependencyInjection/LoggingDI.cs index 6fe56997..d22c1761 100644 --- a/code/Executable/DependencyInjection/LoggingDI.cs +++ b/code/Executable/DependencyInjection/LoggingDI.cs @@ -25,10 +25,9 @@ public static class LoggingDI LoggerConfiguration logConfig = new(); const string categoryTemplate = "{#if Category is not null} {Concat('[',Category,']'),CAT_PAD}{#end}"; const string jobNameTemplate = "{#if JobName is not null} {Concat('[',JobName,']'),JOB_PAD}{#end}"; - const string instanceNameTemplate = "{#if InstanceName is not null} {Concat('[',InstanceName,']'),ARR_PAD}{#end}"; - const string consoleOutputTemplate = $"[{{@t:yyyy-MM-dd HH:mm:ss.fff}} {{@l:u3}}]{categoryTemplate}{jobNameTemplate}{instanceNameTemplate} {{@m}}\n{{@x}}"; - const string fileOutputTemplate = $"{{@t:yyyy-MM-dd HH:mm:ss.fff zzz}} [{{@l:u3}}]{categoryTemplate}{jobNameTemplate}{instanceNameTemplate} {{@m:lj}}\n{{@x}}"; + const string consoleOutputTemplate = $"[{{@t:yyyy-MM-dd HH:mm:ss.fff}} {{@l:u3}}]{jobNameTemplate}{categoryTemplate} {{@m}}\n{{@x}}"; + const string fileOutputTemplate = $"{{@t:yyyy-MM-dd HH:mm:ss.fff zzz}} [{{@l:u3}}]{jobNameTemplate}{categoryTemplate} {{@m:lj}}\n{{@x}}"; // Determine categories and padding sizes List categories = ["SYSTEM", "API", "JOBS", "NOTIFICATIONS"]; @@ -39,19 +38,24 @@ public static class LoggingDI int jobPadding = jobNames.Max(x => x.Length) + 2; // Determine instance name padding - List instanceNames = [InstanceType.Sonarr.ToString(), InstanceType.Radarr.ToString(), InstanceType.Lidarr.ToString()]; - int arrPadding = instanceNames.Max(x => x.Length) + 2; + List categoryNames = [ + InstanceType.Sonarr.ToString(), + InstanceType.Radarr.ToString(), + InstanceType.Lidarr.ToString(), + InstanceType.Readarr.ToString(), + InstanceType.Whisparr.ToString(), + "SYSTEM" + ]; + int arrPadding = categoryNames.Max(x => x.Length) + 2; // Apply padding values to templates string consoleTemplate = consoleOutputTemplate - .Replace("CAT_PAD", catPadding.ToString()) .Replace("JOB_PAD", jobPadding.ToString()) - .Replace("ARR_PAD", arrPadding.ToString()); + .Replace("CAT_PAD", catPadding.ToString()); string fileTemplate = fileOutputTemplate - .Replace("CAT_PAD", catPadding.ToString()) .Replace("JOB_PAD", jobPadding.ToString()) - .Replace("ARR_PAD", arrPadding.ToString()); + .Replace("CAT_PAD", catPadding.ToString()); // Configure base logger with dynamic level control logConfig diff --git a/code/Infrastructure/Configuration/ConfigManager.cs b/code/Infrastructure/Configuration/ConfigManager.cs index ddf84591..9ecca079 100644 --- a/code/Infrastructure/Configuration/ConfigManager.cs +++ b/code/Infrastructure/Configuration/ConfigManager.cs @@ -7,7 +7,6 @@ using Common.Configuration.General; using Common.Configuration.IgnoredDownloads; using Common.Configuration.Notification; using Common.Configuration.QueueCleaner; -using Common.Helpers; using Microsoft.Extensions.Logging; namespace Infrastructure.Configuration; @@ -17,18 +16,6 @@ public class ConfigManager : IConfigManager private readonly ILogger _logger; private readonly IConfigurationProvider _configProvider; - // Define standard config file names with cross-platform paths - private readonly string _generalConfigFile; - private readonly string _sonarrConfigFile; - private readonly string _radarrConfigFile; - private readonly string _lidarrConfigFile; - private readonly string _contentBlockerConfigFile; - private readonly string _queueCleanerConfigFile; - private readonly string _downloadCleanerConfigFile; - private readonly string _downloadClientConfigFile; - private readonly string _ignoredDownloadsConfigFile; - private readonly string _notificationsConfigFile; - private readonly Dictionary _settingsPaths; public ConfigManager( @@ -37,20 +24,8 @@ public class ConfigManager : IConfigManager { _logger = logger; _configProvider = configProvider; - string settingsPath = ConfigurationPathProvider.GetSettingsPath(); - // _generalConfigFile = Path.Combine(settingsPath, "general.json"); - // _sonarrConfigFile = Path.Combine(settingsPath, "sonarr.json"); - // _radarrConfigFile = Path.Combine(settingsPath, "radarr.json"); - // _lidarrConfigFile = Path.Combine(settingsPath, "lidarr.json"); - // _contentBlockerConfigFile = Path.Combine(settingsPath, "content_blocker.json"); - // _queueCleanerConfigFile = Path.Combine(settingsPath, "queue_cleaner.json"); - // _downloadCleanerConfigFile = Path.Combine(settingsPath, "download_cleaner.json"); - // _downloadClientConfigFile = Path.Combine(settingsPath, "download_client.json"); - // _ignoredDownloadsConfigFile = Path.Combine(settingsPath, "ignored_downloads.json"); - // _notificationsConfigFile = Path.Combine(settingsPath, "notifications.json"); - - _settingsPaths = new() + _settingsPaths = new() { { typeof(GeneralConfig), "general.json" }, { typeof(SonarrConfig), "sonarr.json" }, @@ -93,36 +68,11 @@ public class ConfigManager : IConfigManager } } } - - // Generic configuration methods - // public Task GetConfigurationAsync(string configFileName) where T : class, new() - // { - // return _configProvider.ReadConfigurationAsync(configFileName); - // } public Task GetConfigurationAsync() where T : class, new() { return _configProvider.ReadConfigurationAsync(_settingsPaths[typeof(T)]); } - - // public Task SaveConfigurationAsync(string configFileName, T config) where T : class - // { - // // Validate if it's an IConfig - // if (config is IConfig configurable) - // { - // try - // { - // configurable.Validate(); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "Configuration validation failed for {fileName}", configFileName); - // return Task.FromResult(false); - // } - // } - // - // return _configProvider.WriteConfigurationAsync(configFileName, config); - // } public Task SaveConfigurationAsync(T config) where T : class { @@ -145,245 +95,11 @@ public class ConfigManager : IConfigManager return _configProvider.WriteConfigurationAsync(configFileName, config); } - public Task UpdateConfigurationPropertyAsync(string configFileName, string propertyPath, T value) - { - return _configProvider.UpdateConfigurationPropertyAsync(configFileName, propertyPath, value); - } - - public Task MergeConfigurationAsync(string configFileName, T newValues) where T : class - { - return _configProvider.MergeConfigurationAsync(configFileName, newValues); - } - - public Task DeleteConfigurationAsync(string configFileName) - { - return _configProvider.DeleteConfigurationAsync(configFileName); - } - - public IEnumerable ListConfigurationFiles() - { - return _configProvider.ListConfigurationFiles(); - } - - // Specific configuration type methods - // public async Task GetGeneralConfigAsync() - // { - // return await _configProvider.ReadConfigurationAsync(_generalConfigFile); - // } - // - // public async Task GetSonarrConfigAsync() - // { - // return await _configProvider.ReadConfigurationAsync(_sonarrConfigFile); - // } - // - // public async Task GetRadarrConfigAsync() - // { - // return await _configProvider.ReadConfigurationAsync(_radarrConfigFile); - // } - // - // public async Task GetLidarrConfigAsync() - // { - // return await _configProvider.ReadConfigurationAsync(_lidarrConfigFile); - // } - // - // public async Task GetContentBlockerConfigAsync() - // { - // return await _configProvider.ReadConfigurationAsync(_contentBlockerConfigFile); - // } - // - // public async Task GetNotificationsConfigAsync() - // { - // return await _configProvider.ReadConfigurationAsync(_notificationsConfigFile); - // } - // - // public async Task GetQueueCleanerConfigAsync() - // { - // return await _configProvider.ReadConfigurationAsync(_queueCleanerConfigFile); - // } - // - // public async Task GetDownloadCleanerConfigAsync() - // { - // return await _configProvider.ReadConfigurationAsync(_downloadCleanerConfigFile); - // } - // - // public async Task GetDownloadClientConfigAsync() - // { - // return await _configProvider.ReadConfigurationAsync(_downloadClientConfigFile); - // } - // - // public async Task GetIgnoredDownloadsConfigAsync() - // { - // return await _configProvider.ReadConfigurationAsync(_ignoredDownloadsConfigFile); - // } - - // public Task SaveGeneralConfigAsync(GeneralConfig config) - // { - // try - // { - // config.Validate(); - // return _configProvider.WriteConfigurationAsync(_generalConfigFile, config); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "General configuration validation failed"); - // return Task.FromResult(false); - // } - // } - // - // public Task SaveSonarrConfigAsync(SonarrConfig config) - // { - // try - // { - // config.Validate(); - // return _configProvider.WriteConfigurationAsync(_sonarrConfigFile, config); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "Sonarr configuration validation failed"); - // return Task.FromResult(false); - // } - // } - // - // public Task SaveRadarrConfigAsync(RadarrConfig config) - // { - // try - // { - // config.Validate(); - // return _configProvider.WriteConfigurationAsync(_radarrConfigFile, config); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "Radarr configuration validation failed"); - // return Task.FromResult(false); - // } - // } - // - // public Task SaveLidarrConfigAsync(LidarrConfig config) - // { - // try - // { - // config.Validate(); - // return _configProvider.WriteConfigurationAsync(_lidarrConfigFile, config); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "Lidarr configuration validation failed"); - // return Task.FromResult(false); - // } - // } - // - // public Task SaveContentBlockerConfigAsync(ContentBlockerConfig config) - // { - // try - // { - // config.Validate(); - // return _configProvider.WriteConfigurationAsync(_contentBlockerConfigFile, config); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "ContentBlocker configuration validation failed"); - // return Task.FromResult(false); - // } - // } - // - // public Task SaveQueueCleanerConfigAsync(QueueCleanerConfig config) - // { - // try - // { - // config.Validate(); - // return _configProvider.WriteConfigurationAsync(_queueCleanerConfigFile, config); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "QueueCleaner configuration validation failed"); - // return Task.FromResult(false); - // } - // } - // - // public Task SaveDownloadCleanerConfigAsync(DownloadCleanerConfig config) - // { - // try - // { - // config.Validate(); - // return _configProvider.WriteConfigurationAsync(_downloadCleanerConfigFile, config); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "DownloadCleaner configuration validation failed"); - // return Task.FromResult(false); - // } - // } - // - // public Task SaveDownloadClientConfigAsync(DownloadClientConfig config) - // { - // try - // { - // config.Validate(); - // return _configProvider.WriteConfigurationAsync(_downloadClientConfigFile, config); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "DownloadClient configuration validation failed"); - // return Task.FromResult(false); - // } - // } - // - // public Task SaveIgnoredDownloadsConfigAsync(IgnoredDownloadsConfig config) - // { - // try - // { - // return _configProvider.WriteConfigurationAsync(_ignoredDownloadsConfigFile, config); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "IgnoredDownloads configuration save failed"); - // return Task.FromResult(false); - // } - // } - // - // public Task SaveNotificationsConfigAsync(NotificationsConfig config) - // { - // try - // { - // return _configProvider.WriteConfigurationAsync(_notificationsConfigFile, config); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "Notifications configuration save failed"); - // return Task.FromResult(false); - // } - // } - - // // Generic synchronous configuration methods - // public T GetConfiguration(string fileName) where T : class, new() - // { - // return _configProvider.ReadConfiguration(fileName); - // } - public T GetConfiguration() where T : class, new() { return _configProvider.ReadConfiguration(_settingsPaths[typeof(T)]); } - // public bool SaveConfiguration(string configFileName, T config) where T : class - // { - // // Validate if it's an IConfig - // if (config is IConfig configurable) - // { - // try - // { - // configurable.Validate(); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "Configuration validation failed for {fileName}", configFileName); - // return false; - // } - // } - // - // return _configProvider.WriteConfiguration(configFileName, config); - // } - public bool SaveConfiguration(T config) where T : class { string configFileName = _settingsPaths[typeof(T)]; @@ -412,209 +128,4 @@ public class ConfigManager : IConfigManager throw; } } - - public bool UpdateConfigurationProperty(string configFileName, string propertyPath, T value) - { - return _configProvider.UpdateConfigurationProperty(configFileName, propertyPath, value); - } - - public bool MergeConfiguration(string configFileName, T newValues) where T : class - { - return _configProvider.MergeConfiguration(configFileName, newValues); - } - - public bool DeleteConfiguration(string configFileName) - { - return _configProvider.DeleteConfiguration(configFileName); - } - - // Specific synchronous configuration methods for typed configs - - // public GeneralConfig GetGeneralConfig() - // { - // return _configProvider.ReadConfiguration(_generalConfigFile); - // } - // - // public SonarrConfig GetSonarrConfig() - // { - // return _configProvider.ReadConfiguration(_sonarrConfigFile); - // } - // - // public RadarrConfig GetRadarrConfig() - // { - // return _configProvider.ReadConfiguration(_radarrConfigFile); - // } - // - // public LidarrConfig GetLidarrConfig() - // { - // return _configProvider.ReadConfiguration(_lidarrConfigFile); - // } - // - // public QueueCleanerConfig GetQueueCleanerConfig() - // { - // return GetConfiguration(_queueCleanerConfigFile); - // } - // - // public ContentBlockerConfig GetContentBlockerConfig() - // { - // return _configProvider.ReadConfiguration(_contentBlockerConfigFile); - // } - // - // public DownloadCleanerConfig GetDownloadCleanerConfig() - // { - // return _configProvider.ReadConfiguration(_downloadCleanerConfigFile); - // } - // - // public DownloadClientConfig GetDownloadClientConfig() - // { - // return _configProvider.ReadConfiguration(_downloadClientConfigFile); - // } - // - // public IgnoredDownloadsConfig GetIgnoredDownloadsConfig() - // { - // return _configProvider.ReadConfiguration(_ignoredDownloadsConfigFile); - // } - // - // public NotificationsConfig GetNotificationsConfig() - // { - // return _configProvider.ReadConfiguration(_notificationsConfigFile); - // } - - // public bool SaveGeneralConfig(GeneralConfig config) - // { - // try - // { - // config.Validate(); - // return _configProvider.WriteConfiguration(_generalConfigFile, config); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "General configuration validation failed"); - // return false; - // } - // } - // - // public bool SaveSonarrConfig(SonarrConfig config) - // { - // try - // { - // config.Validate(); - // return _configProvider.WriteConfiguration(_sonarrConfigFile, config); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "Sonarr configuration validation failed"); - // return false; - // } - // } - // - // public bool SaveRadarrConfig(RadarrConfig config) - // { - // try - // { - // config.Validate(); - // return _configProvider.WriteConfiguration(_radarrConfigFile, config); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "Radarr configuration validation failed"); - // return false; - // } - // } - // - // public bool SaveLidarrConfig(LidarrConfig config) - // { - // try - // { - // config.Validate(); - // return _configProvider.WriteConfiguration(_lidarrConfigFile, config); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "Lidarr configuration validation failed"); - // return false; - // } - // } - // - // public bool SaveContentBlockerConfig(ContentBlockerConfig config) - // { - // try - // { - // config.Validate(); - // return _configProvider.WriteConfiguration(_contentBlockerConfigFile, config); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "ContentBlocker configuration validation failed"); - // return false; - // } - // } - // - // public bool SaveQueueCleanerConfig(QueueCleanerConfig config) - // { - // try - // { - // config.Validate(); - // return _configProvider.WriteConfiguration(_queueCleanerConfigFile, config); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "QueueCleaner configuration validation failed"); - // return false; - // } - // } - // - // public bool SaveDownloadCleanerConfig(DownloadCleanerConfig config) - // { - // try - // { - // config.Validate(); - // return _configProvider.WriteConfiguration(_downloadCleanerConfigFile, config); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "DownloadCleaner configuration validation failed"); - // return false; - // } - // } - // - // public bool SaveDownloadClientConfig(DownloadClientConfig config) - // { - // try - // { - // config.Validate(); - // return _configProvider.WriteConfiguration(_downloadClientConfigFile, config); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "DownloadClient configuration validation failed"); - // return false; - // } - // } - // - // public bool SaveIgnoredDownloadsConfig(IgnoredDownloadsConfig config) - // { - // try - // { - // return _configProvider.WriteConfiguration(_ignoredDownloadsConfigFile, config); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "IgnoredDownloads configuration save failed"); - // return false; - // } - // } - // - // public bool SaveNotificationsConfig(NotificationsConfig config) - // { - // try - // { - // return _configProvider.WriteConfiguration(_notificationsConfigFile, config); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "Notifications configuration save failed"); - // return false; - // } - // } } diff --git a/code/Infrastructure/Helpers/LogProperties.cs b/code/Infrastructure/Helpers/LogProperties.cs new file mode 100644 index 00000000..10f260e5 --- /dev/null +++ b/code/Infrastructure/Helpers/LogProperties.cs @@ -0,0 +1,7 @@ +namespace Infrastructure.Helpers; + +public static class LogProperties +{ + public const string Category = "Category"; + public const string JobName = "JobName"; +} \ No newline at end of file diff --git a/code/Infrastructure/Http/DynamicHttpClientProvider.cs b/code/Infrastructure/Http/DynamicHttpClientProvider.cs index c2adea96..ea1eef43 100644 --- a/code/Infrastructure/Http/DynamicHttpClientProvider.cs +++ b/code/Infrastructure/Http/DynamicHttpClientProvider.cs @@ -77,7 +77,7 @@ public class DynamicHttpClientProvider : IDynamicHttpClientProvider { ServerCertificateCustomValidationCallback = (sender, certificate, chain, sslPolicyErrors) => _certificateValidationService.ShouldByPassValidationError( - httpConfig.CertificateValidation, + httpConfig.HttpCertificateValidation, sender, certificate, chain, diff --git a/code/Infrastructure/Logging/LoggingCategoryConstants.cs b/code/Infrastructure/Logging/LoggingCategoryConstants.cs deleted file mode 100644 index b3ac74c8..00000000 --- a/code/Infrastructure/Logging/LoggingCategoryConstants.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace Infrastructure.Logging; - -/// -/// Standard logging categories used throughout the application -/// -public static class LoggingCategoryConstants -{ - /// - /// System-level logs (startup, configuration, etc.) - /// - public const string System = "SYSTEM"; - - /// - /// API-related logs (requests, responses, etc.) - /// - public const string Api = "API"; - - /// - /// Job-related logs (scheduled tasks, background operations) - /// - public const string Jobs = "JOBS"; - - /// - /// Notification-related logs (user alerts, warnings) - /// - public const string Notifications = "NOTIFICATIONS"; - - /// - /// Sonarr-related logs - /// - public const string Sonarr = "SONARR"; - - /// - /// Radarr-related logs - /// - public const string Radarr = "RADARR"; - - /// - /// Lidarr-related logs - /// - public const string Lidarr = "LIDARR"; -} diff --git a/code/Infrastructure/Logging/LoggingExtensions.cs b/code/Infrastructure/Logging/LoggingExtensions.cs deleted file mode 100644 index 25e8f362..00000000 --- a/code/Infrastructure/Logging/LoggingExtensions.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Microsoft.Extensions.Logging; -using Serilog; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace Infrastructure.Logging; - -/// -/// Extension methods for contextual logging -/// -public static class LoggingExtensions -{ - /// - /// Adds a category to log messages - /// - /// The logger to enrich - /// The log category - /// Enriched logger instance - public static ILogger WithCategory(this ILogger logger, string category) - { - logger.BeginScope(new Dictionary { ["Category"] = category }); - return logger; - } - - /// - /// Adds a job name to log messages - /// - /// The logger to enrich - /// The job name - /// Enriched logger instance - public static ILogger WithJob(this ILogger logger, string jobName) - { - logger.BeginScope(new Dictionary { ["JobName"] = jobName }); - return logger; - } - - /// - /// Adds an instance name to log messages - /// - /// The logger to enrich - /// The instance name - /// Enriched logger instance - public static ILogger WithInstance(this ILogger logger, string instanceName) - { - logger.BeginScope(new Dictionary { ["InstanceName"] = instanceName }); - return logger; - } - - /// - /// Adds a category to Serilog log messages - /// - /// The Serilog logger - /// The log category - /// Enriched logger instance - public static Serilog.ILogger WithCategory(this Serilog.ILogger logger, string category) - { - return logger.ForContext("Category", category); - } - - /// - /// Adds a job name to Serilog log messages - /// - /// The Serilog logger - /// The job name - /// Enriched logger instance - public static Serilog.ILogger WithJob(this Serilog.ILogger logger, string jobName) - { - return logger.ForContext("JobName", jobName); - } - - /// - /// Adds an instance name to Serilog log messages - /// - /// The Serilog logger - /// The instance name - /// Enriched logger instance - public static Serilog.ILogger WithInstance(this Serilog.ILogger logger, string instanceName) - { - return logger.ForContext("InstanceName", instanceName); - } -} diff --git a/code/Infrastructure/Logging/LoggingInitializer.cs b/code/Infrastructure/Logging/LoggingInitializer.cs index a2deebee..f9b8f110 100644 --- a/code/Infrastructure/Logging/LoggingInitializer.cs +++ b/code/Infrastructure/Logging/LoggingInitializer.cs @@ -1,7 +1,9 @@ using Data.Enums; using Infrastructure.Events; +using Infrastructure.Helpers; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Serilog.Context; namespace Infrastructure.Logging; @@ -10,6 +12,7 @@ public class LoggingInitializer : BackgroundService { private readonly ILogger _logger; private readonly EventPublisher _eventPublisher; + private readonly Random random = new(); public LoggingInitializer(ILogger logger, EventPublisher eventPublisher) { @@ -21,10 +24,13 @@ public class LoggingInitializer : BackgroundService { while (true) { + using var _ = LogContext.PushProperty(LogProperties.Category, + random.Next(0, 100) > 50 ? InstanceType.Sonarr.ToString() : InstanceType.Radarr.ToString()); try { + await _eventPublisher.PublishAsync( - EventType.SlowSpeedStrike, + random.Next(0, 100) > 50 ? EventType.DownloadCleaned : EventType.StalledStrike, "test", EventSeverity.Important, data: new { Hash = "hash", Name = "name", StrikeCount = "1", Type = "stalled" }); @@ -39,7 +45,7 @@ public class LoggingInitializer : BackgroundService _logger.LogError(exception, "test"); } - await Task.Delay(30000, stoppingToken); + await Task.Delay(10000, stoppingToken); } } } diff --git a/code/Infrastructure/Logging/SignalRLogSink.cs b/code/Infrastructure/Logging/SignalRLogSink.cs index c0e0105c..f4de35dd 100644 --- a/code/Infrastructure/Logging/SignalRLogSink.cs +++ b/code/Infrastructure/Logging/SignalRLogSink.cs @@ -43,9 +43,8 @@ public class SignalRLogSink : ILogEventSink Level = logEvent.Level.ToString(), Message = stringWriter.ToString(), Exception = logEvent.Exception?.ToString(), - Category = GetPropertyValue(logEvent, "Category", "SYSTEM"), JobName = GetPropertyValue(logEvent, "JobName"), - InstanceName = GetPropertyValue(logEvent, "InstanceName") + Category = GetPropertyValue(logEvent, "Category", "SYSTEM"), }; // Add to buffer for new clients diff --git a/code/Infrastructure/Verticals/ContentBlocker/ContentBlocker.cs b/code/Infrastructure/Verticals/ContentBlocker/ContentBlocker.cs index c9b27df2..722597e3 100644 --- a/code/Infrastructure/Verticals/ContentBlocker/ContentBlocker.cs +++ b/code/Infrastructure/Verticals/ContentBlocker/ContentBlocker.cs @@ -76,7 +76,7 @@ public sealed class ContentBlocker : GenericHandler { IReadOnlyList ignoredDownloads = await _ignoredDownloadsService.GetIgnoredDownloadsAsync(); - using var _ = LogContext.PushProperty("InstanceName", instanceType.ToString()); + using var _ = LogContext.PushProperty(LogProperties.Category, instanceType.ToString()); IArrClient arrClient = _arrClientFactory.GetClient(instanceType); BlocklistType blocklistType = _blocklistProvider.GetBlocklistType(instanceType); diff --git a/code/Infrastructure/Verticals/DownloadCleaner/DownloadCleaner.cs b/code/Infrastructure/Verticals/DownloadCleaner/DownloadCleaner.cs index 1da08fd4..df64e53b 100644 --- a/code/Infrastructure/Verticals/DownloadCleaner/DownloadCleaner.cs +++ b/code/Infrastructure/Verticals/DownloadCleaner/DownloadCleaner.cs @@ -4,6 +4,7 @@ using Common.Configuration.DownloadClient; using Data.Enums; using Data.Models.Arr.Queue; using Infrastructure.Configuration; +using Infrastructure.Helpers; using Infrastructure.Services; using Infrastructure.Verticals.Arr; using Infrastructure.Verticals.Arr.Interfaces; @@ -248,7 +249,7 @@ public sealed class DownloadCleaner : GenericHandler protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType, ArrConfig config) { - using var _ = LogContext.PushProperty("InstanceName", instanceType.ToString()); + using var _ = LogContext.PushProperty(LogProperties.Category, instanceType.ToString()); IArrClient arrClient = _arrClientFactory.GetClient(instanceType); diff --git a/code/Infrastructure/Verticals/QueueCleaner/QueueCleaner.cs b/code/Infrastructure/Verticals/QueueCleaner/QueueCleaner.cs index 981848f6..9e91aa10 100644 --- a/code/Infrastructure/Verticals/QueueCleaner/QueueCleaner.cs +++ b/code/Infrastructure/Verticals/QueueCleaner/QueueCleaner.cs @@ -58,7 +58,7 @@ public sealed class QueueCleaner : GenericHandler { IReadOnlyList ignoredDownloads = await _ignoredDownloadsService.GetIgnoredDownloadsAsync(); - using var _ = LogContext.PushProperty("InstanceName", instanceType.ToString()); + using var _ = LogContext.PushProperty(LogProperties.Category, instanceType.ToString()); IArrClient arrClient = _arrClientFactory.GetClient(instanceType); diff --git a/code/UI/src/app/core/models/event.models.ts b/code/UI/src/app/core/models/event.models.ts index da424ab5..07377f7c 100644 --- a/code/UI/src/app/core/models/event.models.ts +++ b/code/UI/src/app/core/models/event.models.ts @@ -1,6 +1,6 @@ -export interface Event { +export interface AppEvent { id: string; - timestamp: string; + timestamp: Date; eventType: string; message: string; data?: string; diff --git a/code/UI/src/app/core/pipes/utc-date.pipe.ts b/code/UI/src/app/core/pipes/utc-date.pipe.ts new file mode 100644 index 00000000..e69de29b diff --git a/code/UI/src/app/core/services/event-hub.service.ts b/code/UI/src/app/core/services/event-hub.service.ts index f5bc76df..ca2a9e2f 100644 --- a/code/UI/src/app/core/services/event-hub.service.ts +++ b/code/UI/src/app/core/services/event-hub.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { BaseSignalRService } from './base-signalr.service'; -import { Event } from '../models/event.models'; +import { AppEvent } from '../models/event.models'; import { SignalRHubConfig } from '../models/signalr.models'; import { environment } from '../../../environments/environment'; import * as signalR from '@microsoft/signalr'; @@ -11,7 +11,7 @@ import * as signalR from '@microsoft/signalr'; @Injectable({ providedIn: 'root' }) -export class EventHubService extends BaseSignalRService { +export class EventHubService extends BaseSignalRService { constructor() { // Configuration for the events hub const config: SignalRHubConfig = { @@ -33,7 +33,7 @@ export class EventHubService extends BaseSignalRService { super.registerSignalREvents(); // Handle recent events response (bulk load) - this.hubConnection.on('RecentEventsReceived', (events: Event[]) => { + this.hubConnection.on('RecentEventsReceived', (events: AppEvent[]) => { this.messageSubject.next(events); console.log(`Received ${events.length} recent events`); }); @@ -60,7 +60,7 @@ export class EventHubService extends BaseSignalRService { /** * Get the buffered events */ - public getBufferedEvents(): Event[] { + public getBufferedEvents(): AppEvent[] { return this.getBufferedMessages(); } diff --git a/code/UI/src/app/dashboard/dashboard-page/dashboard-page.component.ts b/code/UI/src/app/dashboard/dashboard-page/dashboard-page.component.ts index 53c0f195..b4015dfa 100644 --- a/code/UI/src/app/dashboard/dashboard-page/dashboard-page.component.ts +++ b/code/UI/src/app/dashboard/dashboard-page/dashboard-page.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit, OnDestroy, signal, computed, inject } from '@angular/core'; import { CommonModule, NgClass, DatePipe } from '@angular/common'; import { RouterLink } from '@angular/router'; -import { Subject, takeUntil } from 'rxjs'; +import { Subject, takeUntil, throttleTime } from 'rxjs'; // PrimeNG Components import { CardModule } from 'primeng/card'; @@ -14,7 +14,7 @@ import { ProgressSpinnerModule } from 'primeng/progressspinner'; import { LogHubService } from '../../core/services/log-hub.service'; import { EventHubService } from '../../core/services/event-hub.service'; import { LogEntry } from '../../core/models/signalr.models'; -import { Event as EventModel } from '../../core/models/event.models'; +import { AppEvent } from '../../core/models/event.models'; @Component({ selector: 'app-dashboard-page', @@ -40,7 +40,7 @@ export class DashboardPageComponent implements OnInit, OnDestroy { // Signals for reactive state recentLogs = signal([]); - recentEvents = signal([]); + recentEvents = signal([]); logsConnected = signal(false); eventsConnected = signal(false); @@ -72,9 +72,12 @@ export class DashboardPageComponent implements OnInit, OnDestroy { this.logHubService.startConnection() .catch((error: Error) => console.error('Failed to connect to log hub:', error)); - // Subscribe to logs + // Subscribe to logs with throttling to prevent UI overwhelming this.logHubService.getLogs() - .pipe(takeUntil(this.destroy$)) + .pipe( + takeUntil(this.destroy$), + throttleTime(1000) // Max 1 update per second + ) .subscribe((logs: LogEntry[]) => { this.recentLogs.set(logs); }); @@ -92,10 +95,13 @@ export class DashboardPageComponent implements OnInit, OnDestroy { this.eventHubService.startConnection() .catch((error: Error) => console.error('Failed to connect to event hub:', error)); - // Subscribe to events + // Subscribe to events with throttling this.eventHubService.getEvents() - .pipe(takeUntil(this.destroy$)) - .subscribe((events: EventModel[]) => { + .pipe( + takeUntil(this.destroy$), + throttleTime(1000) // Max 1 update per second + ) + .subscribe((events: AppEvent[]) => { this.recentEvents.set(events); }); @@ -221,6 +227,7 @@ export class DashboardPageComponent implements OnInit, OnDestroy { // Other events get standard severity coloring switch (normalizedSeverity) { case 'error': + case 'important': return 'event-icon-error'; case 'warning': return 'event-icon-warning'; diff --git a/code/UI/src/app/events/events-viewer/events-viewer.component.ts b/code/UI/src/app/events/events-viewer/events-viewer.component.ts index 1b3ff931..f1fa2475 100644 --- a/code/UI/src/app/events/events-viewer/events-viewer.component.ts +++ b/code/UI/src/app/events/events-viewer/events-viewer.component.ts @@ -20,7 +20,7 @@ import { MenuItem } from 'primeng/api'; // Services & Models import { EventHubService } from '../../core/services/event-hub.service'; -import { Event as EventModel } from '../../core/models/event.models'; +import { AppEvent } from '../../core/models/event.models'; @Component({ selector: 'app-events-viewer', @@ -28,7 +28,6 @@ import { Event as EventModel } from '../../core/models/event.models'; imports: [ NgFor, NgIf, - NgClass, DatePipe, FormsModule, TableModule, @@ -57,7 +56,7 @@ export class EventsViewerComponent implements OnInit, OnDestroy { @ViewChild('exportMenu') exportMenu: any; // Signals for reactive state - events = signal([]); + events = signal([]); isConnected = signal(false); autoScroll = signal(true); expandedEvents: { [key: number]: boolean } = {}; @@ -118,7 +117,7 @@ export class EventsViewerComponent implements OnInit, OnDestroy { // Subscribe to events this.eventHubService.getEvents() .pipe(takeUntil(this.destroy$)) - .subscribe((events: EventModel[]) => { + .subscribe((events: AppEvent[]) => { this.events.set(events); if (this.autoScroll()) { this.scrollToBottom(); @@ -218,7 +217,7 @@ export class EventsViewerComponent implements OnInit, OnDestroy { /** * Copy a specific event entry to clipboard */ - copyEventEntry(event: EventModel, domEvent: MouseEvent): void { + copyEventEntry(event: AppEvent, domEvent: MouseEvent): void { domEvent.stopPropagation(); const timestamp = new Date(event.timestamp).toISOString();