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();