From 66aaa0225579fce5193958868a94003f123b153a Mon Sep 17 00:00:00 2001 From: JasonTaylorDev Date: Tue, 10 Jun 2025 10:34:26 +1000 Subject: [PATCH 1/8] Switch to .NET logging --- .../CustomChecks/CheckDirtyMemory.cs | 14 ++++---- .../CustomChecks/CheckFreeDiskSpace.cs | 32 ++++++++--------- ...CheckMinimumStorageRequiredForIngestion.cs | 35 +++++++++---------- .../CustomChecks/CheckRavenDBIndexLag.cs | 20 ++++++----- .../RavenEmbeddedPersistenceLifecycle.cs | 8 ++--- .../RavenPersistenceConfiguration.cs | 22 +++++++----- .../LoggerUtil.cs | 27 ++++++++++++++ .../ServiceControlComponentRunner.cs | 3 +- .../Hosting/Commands/SetupCommand.cs | 8 ++--- .../MonitoredEndpointMessageTypeParser.cs | 7 ++-- .../Licensing/ActiveLicense.cs | 14 +++++--- .../Licensing/LicenseCheckHostedService.cs | 7 ++-- src/ServiceControl.Monitoring/Program.cs | 11 +++--- .../LegacyQueueLengthReportHandler.cs | 15 ++------ .../ServiceControl.Monitoring.csproj | 1 - 15 files changed, 118 insertions(+), 106 deletions(-) create mode 100644 src/ServiceControl.Infrastructure/LoggerUtil.cs diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs index 37731858d8..409b3d6df1 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs @@ -3,26 +3,24 @@ namespace ServiceControl.Audit.Persistence.RavenDB.CustomChecks; using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using NServiceBus.CustomChecks; -using NServiceBus.Logging; -class CheckDirtyMemory(MemoryInformationRetriever memoryInformationRetriever) : CustomCheck("RavenDB dirty memory", "ServiceControl.Audit Health", TimeSpan.FromMinutes(5)) +class CheckDirtyMemory(MemoryInformationRetriever memoryInformationRetriever, ILogger logger) : CustomCheck("RavenDB dirty memory", "ServiceControl.Audit Health", TimeSpan.FromMinutes(5)) { + public override async Task PerformCheck(CancellationToken cancellationToken = default) { var (isHighDirty, dirtyMemory) = await memoryInformationRetriever.GetMemoryInformation(cancellationToken); - Log.Debug($"RavenDB dirty memory value: {dirtyMemory}."); + logger.LogDebug("RavenDB dirty memory value: {DirtyMemory}.", dirtyMemory); if (isHighDirty) { - var message = $"There is a high level of RavenDB dirty memory ({dirtyMemory}). See https://docs.particular.net/servicecontrol/troubleshooting#ravendb-dirty-memory for guidance on how to mitigate the issue."; - Log.Warn(message); - return CheckResult.Failed(message); + logger.LogWarning("There is a high level of RavenDB dirty memory ({DirtyMemory}). See https://docs.particular.net/servicecontrol/troubleshooting#ravendb-dirty-memory for guidance on how to mitigate the issue.", dirtyMemory); + return CheckResult.Failed($"There is a high level of RavenDB dirty memory ({dirtyMemory}). See https://docs.particular.net/servicecontrol/troubleshooting#ravendb-dirty-memory for guidance on how to mitigate the issue."); } return CheckResult.Pass; } - - static readonly ILog Log = LogManager.GetLogger(); } \ No newline at end of file diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckFreeDiskSpace.cs b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckFreeDiskSpace.cs index 18705af5b9..923b6208d6 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckFreeDiskSpace.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckFreeDiskSpace.cs @@ -5,17 +5,18 @@ using System.IO; using System.Threading; using System.Threading.Tasks; + using Microsoft.Extensions.Logging; using NServiceBus.CustomChecks; - using NServiceBus.Logging; using RavenDB; + using ServiceControl.Infrastructure; - class CheckFreeDiskSpace(DatabaseConfiguration databaseConfiguration) : CustomCheck("ServiceControl.Audit database", "Storage space", TimeSpan.FromMinutes(5)) + class CheckFreeDiskSpace(DatabaseConfiguration databaseConfiguration, ILogger logger) : CustomCheck("ServiceControl.Audit database", "Storage space", TimeSpan.FromMinutes(5)) { public override Task PerformCheck(CancellationToken cancellationToken = default) { - if (Logger.IsDebugEnabled) + if (logger.IsEnabled(LogLevel.Debug)) { - Logger.Debug($"Check ServiceControl data drive space remaining custom check starting. Threshold {percentageThreshold:P0}"); + logger.LogDebug("Check ServiceControl data drive space remaining custom check starting. Threshold {PercentageThreshold:P0}", percentageThreshold); } if (!databaseConfiguration.ServerConfiguration.UseEmbeddedServer) @@ -34,9 +35,9 @@ public override Task PerformCheck(CancellationToken cancellationTok var percentRemaining = (decimal)dataDriveInfo.AvailableFreeSpace / dataDriveInfo.TotalSize; - if (Logger.IsDebugEnabled) + if (logger.IsEnabled(LogLevel.Debug)) { - Logger.Debug($"Free space: {availableFreeSpace:N0}B | Total: {totalSpace:N0}B | Percent remaining {percentRemaining:P1}"); + logger.LogDebug("Free space: {AvailableFreeSpace:N0}B | Total: {TotalSpace:N0}B | Percent remaining {PercentRemaining:P1}", availableFreeSpace, totalSpace, percentRemaining); } return percentRemaining > percentageThreshold @@ -51,26 +52,22 @@ public static int Parse(IDictionary settings) thresholdValue = $"{DataSpaceRemainingThresholdDefault}"; } - string message; if (!int.TryParse(thresholdValue, out var threshold)) { - message = $"{RavenPersistenceConfiguration.DataSpaceRemainingThresholdKey} must be an integer."; - Logger.Fatal(message); - throw new Exception(message); + Logger.LogCritical("{RavenPersistenceConfigurationDataSpaceRemainingThresholdKey} must be an integer.", RavenPersistenceConfiguration.DataSpaceRemainingThresholdKey); + throw new Exception($"{RavenPersistenceConfiguration.DataSpaceRemainingThresholdKey} must be an integer."); } if (threshold < 0) { - message = $"{RavenPersistenceConfiguration.DataSpaceRemainingThresholdKey} is invalid, minimum value is 0."; - Logger.Fatal(message); - throw new Exception(message); + Logger.LogCritical("{RavenPersistenceConfigurationDataSpaceRemainingThresholdKey} is invalid, minimum value is 0.", RavenPersistenceConfiguration.DataSpaceRemainingThresholdKey); + throw new Exception($"{RavenPersistenceConfiguration.DataSpaceRemainingThresholdKey} is invalid, minimum value is 0."); } if (threshold > 100) { - message = $"{RavenPersistenceConfiguration.DataSpaceRemainingThresholdKey} is invalid, maximum value is 100."; - Logger.Fatal(message); - throw new Exception(message); + Logger.LogCritical("{RavenPersistenceConfigurationDataSpaceRemainingThresholdKey} is invalid, maximum value is 100.", RavenPersistenceConfiguration.DataSpaceRemainingThresholdKey); + throw new Exception($"{RavenPersistenceConfiguration.DataSpaceRemainingThresholdKey} is invalid, maximum value is 100."); } return threshold; @@ -78,8 +75,7 @@ public static int Parse(IDictionary settings) readonly string dataPathRoot = Path.GetPathRoot(databaseConfiguration.ServerConfiguration.DbPath); readonly decimal percentageThreshold = databaseConfiguration.DataSpaceRemainingThreshold / 100m; - + static readonly ILogger Logger = LoggerUtil.CreateStaticLogger(); public const int DataSpaceRemainingThresholdDefault = 20; - static readonly ILog Logger = LogManager.GetLogger(typeof(CheckFreeDiskSpace)); } } \ No newline at end of file diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckMinimumStorageRequiredForIngestion.cs b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckMinimumStorageRequiredForIngestion.cs index 6d2fccbc38..56153b3169 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckMinimumStorageRequiredForIngestion.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckMinimumStorageRequiredForIngestion.cs @@ -5,19 +5,20 @@ using System.IO; using System.Threading; using System.Threading.Tasks; + using Microsoft.Extensions.Logging; using NServiceBus.CustomChecks; - using NServiceBus.Logging; using RavenDB; + using ServiceControl.Infrastructure; - class CheckMinimumStorageRequiredForIngestion(MinimumRequiredStorageState stateHolder, DatabaseConfiguration databaseConfiguration) : CustomCheck("Audit Message Ingestion Process", "ServiceControl.Audit Health", TimeSpan.FromSeconds(5)) + class CheckMinimumStorageRequiredForIngestion(MinimumRequiredStorageState stateHolder, DatabaseConfiguration databaseConfiguration, ILogger logger) : CustomCheck("Audit Message Ingestion Process", "ServiceControl.Audit Health", TimeSpan.FromSeconds(5)) { public override Task PerformCheck(CancellationToken cancellationToken = default) { var percentageThreshold = databaseConfiguration.MinimumStorageLeftRequiredForIngestion / 100m; - if (Logger.IsDebugEnabled) + if (logger.IsEnabled(LogLevel.Debug)) { - Logger.Debug($"Check ServiceControl data drive space starting. Threshold {percentageThreshold:P0}"); + logger.LogDebug("Check ServiceControl data drive space starting. Threshold {PercentageThreshold:P0}", percentageThreshold); } // Should be checking UseEmbeddedServer but need to check DbPath instead for the ATT hack to work @@ -35,9 +36,9 @@ public override Task PerformCheck(CancellationToken cancellationTok var percentRemaining = (decimal)dataDriveInfo.AvailableFreeSpace / dataDriveInfo.TotalSize; - if (Logger.IsDebugEnabled) + if (logger.IsEnabled(LogLevel.Debug)) { - Logger.Debug($"Free space: {availableFreeSpace} | Total: {totalSpace} | Percent remaining {percentRemaining:P0}"); + logger.LogDebug("Free space: {AvailableFreeSpace} | Total: {TotalSpace} | Percent remaining {PercentRemaining:P0}", availableFreeSpace, totalSpace, percentRemaining); } if (percentRemaining > percentageThreshold) @@ -46,10 +47,9 @@ public override Task PerformCheck(CancellationToken cancellationTok return SuccessResult; } - var message = $"Audit message ingestion stopped! {percentRemaining:P0} disk space remaining on data drive '{dataDriveInfo.VolumeLabel} ({dataDriveInfo.RootDirectory})' on '{Environment.MachineName}'. This is less than {percentageThreshold}% - the minimal required space configured. The threshold can be set using the {RavenPersistenceConfiguration.MinimumStorageLeftRequiredForIngestionKey} configuration setting."; - Logger.Warn(message); + logger.LogWarning("Audit message ingestion stopped! {PercentRemaining:P0} disk space remaining on data drive '{DataDriveInfoVolumeLabel} ({DataDriveInfoRootDirectory})' on '{EnvironmentMachineName}'. This is less than {PercentageThreshold}% - the minimal required space configured. The threshold can be set using the {RavenPersistenceConfigurationMinimumStorageLeftRequiredForIngestionKey} configuration setting.", percentRemaining, dataDriveInfo.VolumeLabel, dataDriveInfo.RootDirectory, Environment.MachineName, percentageThreshold, RavenPersistenceConfiguration.MinimumStorageLeftRequiredForIngestionKey); stateHolder.CanIngestMore = false; - return CheckResult.Failed(message); + return CheckResult.Failed($"Audit message ingestion stopped! {percentRemaining:P0} disk space remaining on data drive '{dataDriveInfo.VolumeLabel} ({dataDriveInfo.RootDirectory})' on '{Environment.MachineName}'. This is less than {percentageThreshold}% - the minimal required space configured. The threshold can be set using the {RavenPersistenceConfiguration.MinimumStorageLeftRequiredForIngestionKey} configuration setting."); } public static int Parse(IDictionary settings) @@ -61,23 +61,20 @@ public static int Parse(IDictionary settings) if (!int.TryParse(thresholdValue, out var threshold)) { - var message = $"{RavenPersistenceConfiguration.MinimumStorageLeftRequiredForIngestionKey} must be an integer."; - Logger.Fatal(message); - throw new Exception(message); + Logger.LogCritical("{RavenPersistenceConfigurationMinimumStorageLeftRequiredForIngestionKey} must be an integer.", RavenPersistenceConfiguration.MinimumStorageLeftRequiredForIngestionKey); + throw new Exception($"{RavenPersistenceConfiguration.MinimumStorageLeftRequiredForIngestionKey} must be an integer."); } if (threshold < 0) { - var message = $"{RavenPersistenceConfiguration.MinimumStorageLeftRequiredForIngestionKey} is invalid, minimum value is 0."; - Logger.Fatal(message); - throw new Exception(message); + Logger.LogCritical("{RavenPersistenceConfigurationMinimumStorageLeftRequiredForIngestionKey} is invalid, minimum value is 0.", RavenPersistenceConfiguration.MinimumStorageLeftRequiredForIngestionKey); + throw new Exception($"{RavenPersistenceConfiguration.MinimumStorageLeftRequiredForIngestionKey} is invalid, minimum value is 0."); } if (threshold > 100) { - var message = $"{RavenPersistenceConfiguration.MinimumStorageLeftRequiredForIngestionKey} is invalid, maximum value is 100."; - Logger.Fatal(message); - throw new Exception(message); + Logger.LogCritical("{RavenPersistenceConfigurationMinimumStorageLeftRequiredForIngestionKey} is invalid, maximum value is 100.", RavenPersistenceConfiguration.MinimumStorageLeftRequiredForIngestionKey); + throw new Exception($"{RavenPersistenceConfiguration.MinimumStorageLeftRequiredForIngestionKey} is invalid, maximum value is 100."); } return threshold; @@ -85,6 +82,6 @@ public static int Parse(IDictionary settings) public const int MinimumStorageLeftRequiredForIngestionDefault = 5; static readonly Task SuccessResult = Task.FromResult(CheckResult.Pass); - static readonly ILog Logger = LogManager.GetLogger(typeof(CheckMinimumStorageRequiredForIngestion)); + static readonly ILogger Logger = LoggerUtil.CreateStaticLogger(); } } \ No newline at end of file diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckRavenDBIndexLag.cs b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckRavenDBIndexLag.cs index 41f4347a71..193e384f2d 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckRavenDBIndexLag.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckRavenDBIndexLag.cs @@ -5,12 +5,13 @@ using System.Text; using System.Threading; using System.Threading.Tasks; + using Microsoft.Extensions.Logging; using NServiceBus.CustomChecks; - using NServiceBus.Logging; using Raven.Client.Documents.Operations; using ServiceControl.Audit.Persistence.RavenDB; + using ServiceControl.Infrastructure; - class CheckRavenDBIndexLag(IRavenDocumentStoreProvider documentStoreProvider) : CustomCheck("Audit Database Index Lag", "ServiceControl.Audit Health", TimeSpan.FromMinutes(5)) + class CheckRavenDBIndexLag(IRavenDocumentStoreProvider documentStoreProvider, ILogger logger) : CustomCheck("Audit Database Index Lag", "ServiceControl.Audit Health", TimeSpan.FromMinutes(5)) { public override async Task PerformCheck(CancellationToken cancellationToken = default) { @@ -20,7 +21,7 @@ public override async Task PerformCheck(CancellationToken cancellat CreateDiagnosticsLogEntry(statistics, indexes); - var indexCountWithTooMuchLag = CheckAndReportIndexesWithTooMuchIndexLag(indexes); + var indexCountWithTooMuchLag = CheckAndReportIndexesWithTooMuchIndexLag(indexes, logger); if (indexCountWithTooMuchLag > 0) { @@ -30,7 +31,7 @@ public override async Task PerformCheck(CancellationToken cancellat return CheckResult.Pass; } - static int CheckAndReportIndexesWithTooMuchIndexLag(IndexInformation[] indexes) + static int CheckAndReportIndexesWithTooMuchIndexLag(IndexInformation[] indexes, ILogger logger) { int indexCountWithTooMuchLag = 0; @@ -43,12 +44,12 @@ static int CheckAndReportIndexesWithTooMuchIndexLag(IndexInformation[] indexes) if (indexLag > IndexLagThresholdError) { indexCountWithTooMuchLag++; - Log.Error($"Index [{indexStats.Name}] IndexingLag {indexLag} is above error threshold ({IndexLagThresholdError}). Launch in maintenance mode to let indexes catch up."); + logger.LogError("Index [{IndexStatsName}] IndexingLag {IndexLag} is above error threshold ({IndexLagThresholdError}). Launch in maintenance mode to let indexes catch up.", indexStats.Name, indexLag, IndexLagThresholdError); } else if (indexLag > IndexLagThresholdWarning) { indexCountWithTooMuchLag++; - Log.Warn($"Index [{indexStats.Name}] IndexingLag {indexLag} is above warning threshold ({IndexLagThresholdWarning}). Launch in maintenance mode to let indexes catch up."); + logger.LogWarning("Index [{IndexStatsName}] IndexingLag {IndexLag} is above warning threshold ({IndexLagThresholdWarning}). Launch in maintenance mode to let indexes catch up.", indexStats.Name, indexLag, IndexLagThresholdWarning); } } } @@ -58,7 +59,7 @@ static int CheckAndReportIndexesWithTooMuchIndexLag(IndexInformation[] indexes) static void CreateDiagnosticsLogEntry(DatabaseStatistics statistics, IndexInformation[] indexes) { - if (!Log.IsDebugEnabled) + if (!Logger.IsEnabled(LogLevel.Debug)) { return; } @@ -72,11 +73,12 @@ static void CreateDiagnosticsLogEntry(DatabaseStatistics statistics, IndexInform { report.AppendLine($"- Index [{indexStats.Name,-44}] State: {indexStats.State}, Stale: {indexStats.IsStale,-5}, Priority: {indexStats.Priority,-6}, LastIndexingTime: {indexStats.LastIndexingTime:u}"); } - Log.Debug(report.ToString()); + + Logger.LogDebug(report.ToString()); } static readonly TimeSpan IndexLagThresholdWarning = TimeSpan.FromMinutes(1); static readonly TimeSpan IndexLagThresholdError = TimeSpan.FromMinutes(10); - static readonly ILog Log = LogManager.GetLogger(); + static readonly ILogger Logger = LoggerUtil.CreateStaticLogger(); } } \ No newline at end of file diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/RavenEmbeddedPersistenceLifecycle.cs b/src/ServiceControl.Audit.Persistence.RavenDB/RavenEmbeddedPersistenceLifecycle.cs index f7f1ecee16..80bf05b742 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/RavenEmbeddedPersistenceLifecycle.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/RavenEmbeddedPersistenceLifecycle.cs @@ -6,12 +6,12 @@ namespace ServiceControl.Audit.Persistence.RavenDB using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; - using NServiceBus.Logging; + using Microsoft.Extensions.Logging; using Raven.Client.Documents; using Raven.Client.Exceptions.Database; using ServiceControl.RavenDB; - sealed class RavenEmbeddedPersistenceLifecycle(DatabaseConfiguration databaseConfiguration, IHostApplicationLifetime lifetime) : IRavenPersistenceLifecycle, IRavenDocumentStoreProvider, IDisposable + sealed class RavenEmbeddedPersistenceLifecycle(DatabaseConfiguration databaseConfiguration, IHostApplicationLifetime lifetime, ILogger logger) : IRavenPersistenceLifecycle, IRavenDocumentStoreProvider, IDisposable { public async ValueTask GetDocumentStore(CancellationToken cancellationToken = default) { @@ -61,7 +61,7 @@ public async Task Initialize(CancellationToken cancellationToken = default) } catch (DatabaseLoadTimeoutException e) { - Log.Warn("Connecting to the embedded RavenDB database timed out. Retrying in 500ms...", e); + logger.LogWarning(e, "Connecting to the embedded RavenDB database timed out. Retrying in 500ms..."); await Task.Delay(500, cancellationToken); } } @@ -83,7 +83,5 @@ public void Dispose() IDocumentStore? documentStore; EmbeddedDatabase? database; readonly SemaphoreSlim initializeSemaphore = new(1, 1); - - static readonly ILog Log = LogManager.GetLogger(typeof(RavenEmbeddedPersistenceLifecycle)); } } diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/RavenPersistenceConfiguration.cs b/src/ServiceControl.Audit.Persistence.RavenDB/RavenPersistenceConfiguration.cs index b22003ceae..fc8aa2f281 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/RavenPersistenceConfiguration.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/RavenPersistenceConfiguration.cs @@ -5,7 +5,8 @@ using System.IO; using System.Reflection; using CustomChecks; - using NServiceBus.Logging; + using Microsoft.Extensions.Logging; + using ServiceControl.Infrastructure; public class RavenPersistenceConfiguration : IPersistenceConfiguration { @@ -141,15 +142,17 @@ static int GetExpirationProcessTimerInSeconds(PersistenceSettings settings) expirationProcessTimerInSeconds = int.Parse(expirationProcessTimerInSecondsString); } + var maxExpirationProcessTimerInSeconds = TimeSpan.FromHours(3).TotalSeconds; + if (expirationProcessTimerInSeconds < 0) { - Logger.Error($"ExpirationProcessTimerInSeconds cannot be negative. Defaulting to {ExpirationProcessTimerInSecondsDefault}"); + Logger.LogError("ExpirationProcessTimerInSeconds cannot be negative. Defaulting to {ExpirationProcessTimerInSecondsDefault}", ExpirationProcessTimerInSecondsDefault); return ExpirationProcessTimerInSecondsDefault; } - if (expirationProcessTimerInSeconds > TimeSpan.FromHours(3).TotalSeconds) + if (expirationProcessTimerInSeconds > maxExpirationProcessTimerInSeconds) { - Logger.Error($"ExpirationProcessTimerInSeconds cannot be larger than {TimeSpan.FromHours(3).TotalSeconds}. Defaulting to {ExpirationProcessTimerInSecondsDefault}"); + Logger.LogError("ExpirationProcessTimerInSeconds cannot be larger than {MaxExpirationProcessTimerInSeconds}. Defaulting to {ExpirationProcessTimerInSecondsDefault}", maxExpirationProcessTimerInSeconds, ExpirationProcessTimerInSecondsDefault); return ExpirationProcessTimerInSecondsDefault; } @@ -165,15 +168,17 @@ static int GetBulkInsertCommitTimeout(PersistenceSettings settings) bulkInsertCommitTimeoutInSeconds = int.Parse(bulkInsertCommitTimeoutString); } + var maxBulkInsertCommitTimeoutInSeconds = TimeSpan.FromHours(1).TotalSeconds; + if (bulkInsertCommitTimeoutInSeconds < 0) { - Logger.Error($"BulkInsertCommitTimeout cannot be negative. Defaulting to {BulkInsertCommitTimeoutInSecondsDefault}"); + Logger.LogError("BulkInsertCommitTimeout cannot be negative. Defaulting to {BulkInsertCommitTimeoutInSecondsDefault}", BulkInsertCommitTimeoutInSecondsDefault); return BulkInsertCommitTimeoutInSecondsDefault; } - if (bulkInsertCommitTimeoutInSeconds > TimeSpan.FromHours(1).TotalSeconds) + if (bulkInsertCommitTimeoutInSeconds > maxBulkInsertCommitTimeoutInSeconds) { - Logger.Error($"BulkInsertCommitTimeout cannot be larger than {TimeSpan.FromHours(1).TotalSeconds}. Defaulting to {BulkInsertCommitTimeoutInSecondsDefault}"); + Logger.LogError("BulkInsertCommitTimeout cannot be larger than {MaxBulkInsertCommitTimeoutInSeconds}. Defaulting to {BulkInsertCommitTimeoutInSecondsDefault}", maxBulkInsertCommitTimeoutInSeconds, BulkInsertCommitTimeoutInSecondsDefault); return BulkInsertCommitTimeoutInSecondsDefault; } @@ -193,9 +198,8 @@ static string GetLogPath(PersistenceSettings settings) return logPath; } - static readonly ILog Logger = LogManager.GetLogger(typeof(RavenPersistenceConfiguration)); - const int ExpirationProcessTimerInSecondsDefault = 600; const int BulkInsertCommitTimeoutInSecondsDefault = 60; + static readonly ILogger Logger = LoggerUtil.CreateStaticLogger(); } } diff --git a/src/ServiceControl.Infrastructure/LoggerUtil.cs b/src/ServiceControl.Infrastructure/LoggerUtil.cs new file mode 100644 index 0000000000..98aeb7d865 --- /dev/null +++ b/src/ServiceControl.Infrastructure/LoggerUtil.cs @@ -0,0 +1,27 @@ +namespace ServiceControl.Infrastructure +{ + using System; + using Microsoft.Extensions.Logging; + using NLog.Extensions.Logging; + + public static class LoggerUtil + { + public static void BuildLogger(this ILoggingBuilder loggingBuilder, LogLevel level) + { + loggingBuilder.AddNLog(); + loggingBuilder.SetMinimumLevel(level); + } + + public static ILogger CreateStaticLogger(LogLevel level = LogLevel.Information) + { + var factory = LoggerFactory.Create(configure => configure.BuildLogger(level)); + return factory.CreateLogger(); + } + + public static ILogger CreateStaticLogger(Type type, LogLevel level = LogLevel.Information) + { + var factory = LoggerFactory.Create(configure => configure.BuildLogger(level)); + return factory.CreateLogger(type); + } + } +} \ No newline at end of file diff --git a/src/ServiceControl.Monitoring.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs b/src/ServiceControl.Monitoring.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs index debc3ed629..212f1e03a1 100644 --- a/src/ServiceControl.Monitoring.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs +++ b/src/ServiceControl.Monitoring.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs @@ -18,6 +18,7 @@ namespace ServiceControl.Monitoring.AcceptanceTests.TestSupport using NServiceBus.AcceptanceTesting; using NServiceBus.AcceptanceTesting.Support; using NServiceBus.Logging; + using ServiceControl.Infrastructure; class ServiceControlComponentRunner( ITransportIntegration transportToUse, @@ -72,7 +73,7 @@ async Task InitializeServiceControl(ScenarioContext context) using (new DiagnosticTimer($"Creating infrastructure for {settings.InstanceName}")) { - var setupCommand = new SetupCommand(); + var setupCommand = new SetupCommand(LoggerUtil.CreateStaticLogger()); await setupCommand.Execute(new HostArguments([]), settings); } diff --git a/src/ServiceControl.Monitoring/Hosting/Commands/SetupCommand.cs b/src/ServiceControl.Monitoring/Hosting/Commands/SetupCommand.cs index 3bd7830438..d585434197 100644 --- a/src/ServiceControl.Monitoring/Hosting/Commands/SetupCommand.cs +++ b/src/ServiceControl.Monitoring/Hosting/Commands/SetupCommand.cs @@ -1,16 +1,16 @@ namespace ServiceControl.Monitoring { using System.Threading.Tasks; - using NServiceBus.Logging; + using Microsoft.Extensions.Logging; using Transports; - class SetupCommand : AbstractCommand + class SetupCommand(ILogger logger) : AbstractCommand { public override Task Execute(HostArguments args, Settings settings) { if (args.SkipQueueCreation) { - Logger.Info("Skipping queue creation"); + logger.LogInformation("Skipping queue creation"); return Task.CompletedTask; } @@ -19,7 +19,5 @@ public override Task Execute(HostArguments args, Settings settings) var transportCustomization = TransportFactory.Create(transportSettings); return transportCustomization.ProvisionQueues(transportSettings, []); } - - static readonly ILog Logger = LogManager.GetLogger(); } } \ No newline at end of file diff --git a/src/ServiceControl.Monitoring/Http/Diagrams/MonitoredEndpointMessageTypeParser.cs b/src/ServiceControl.Monitoring/Http/Diagrams/MonitoredEndpointMessageTypeParser.cs index 3faa1e81c7..f089104b4f 100644 --- a/src/ServiceControl.Monitoring/Http/Diagrams/MonitoredEndpointMessageTypeParser.cs +++ b/src/ServiceControl.Monitoring/Http/Diagrams/MonitoredEndpointMessageTypeParser.cs @@ -3,7 +3,8 @@ namespace ServiceControl.Monitoring.Http.Diagrams using System; using System.Linq; using System.Reflection; - using NServiceBus.Logging; + using Microsoft.Extensions.Logging; + using ServiceControl.Infrastructure; public static class MonitoredEndpointMessageTypeParser { @@ -40,7 +41,7 @@ public static MonitoredEndpointMessageType Parse(string typeName) } catch (Exception e) { - Logger.Warn($"Error parsing message type: {typeName}.", e); + LoggerUtil.CreateStaticLogger(typeof(MonitoredEndpointMessageTypeParser)).LogWarning(e, "Error parsing message type: {typeName}.", typeName); } } @@ -50,7 +51,5 @@ public static MonitoredEndpointMessageType Parse(string typeName) TypeName = typeName }; } - - static readonly ILog Logger = LogManager.GetLogger(typeof(MonitoredEndpointMessageTypeParser)); } } \ No newline at end of file diff --git a/src/ServiceControl.Monitoring/Licensing/ActiveLicense.cs b/src/ServiceControl.Monitoring/Licensing/ActiveLicense.cs index a3a001e0ad..f5ca3be8bc 100644 --- a/src/ServiceControl.Monitoring/Licensing/ActiveLicense.cs +++ b/src/ServiceControl.Monitoring/Licensing/ActiveLicense.cs @@ -1,11 +1,15 @@ namespace ServiceControl.Monitoring.Licensing { - using global::ServiceControl.LicenseManagement; - using NServiceBus.Logging; + using ServiceControl.LicenseManagement; + using Microsoft.Extensions.Logging; public class ActiveLicense { - public ActiveLicense() => Refresh(); + public ActiveLicense(ILogger logger) + { + this.logger = logger; + Refresh(); + } public bool IsValid { get; set; } @@ -13,7 +17,7 @@ public class ActiveLicense public void Refresh() { - Logger.Debug("Refreshing ActiveLicense"); + logger.LogDebug("Refreshing ActiveLicense"); var detectedLicense = LicenseManager.FindLicense(); @@ -22,6 +26,6 @@ public void Refresh() Details = detectedLicense.Details; } - static readonly ILog Logger = LogManager.GetLogger(typeof(ActiveLicense)); + readonly ILogger logger; } } \ No newline at end of file diff --git a/src/ServiceControl.Monitoring/Licensing/LicenseCheckHostedService.cs b/src/ServiceControl.Monitoring/Licensing/LicenseCheckHostedService.cs index a5b10f73b5..b29e47fd05 100644 --- a/src/ServiceControl.Monitoring/Licensing/LicenseCheckHostedService.cs +++ b/src/ServiceControl.Monitoring/Licensing/LicenseCheckHostedService.cs @@ -4,10 +4,10 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; - using NServiceBus.Logging; + using Microsoft.Extensions.Logging; using ServiceControl.Infrastructure.BackgroundTasks; - class LicenseCheckHostedService(ActiveLicense activeLicense, IAsyncTimer scheduler) : IHostedService + class LicenseCheckHostedService(ActiveLicense activeLicense, IAsyncTimer scheduler, ILogger logger) : IHostedService { public Task StartAsync(CancellationToken cancellationToken) { @@ -16,7 +16,7 @@ public Task StartAsync(CancellationToken cancellationToken) { activeLicense.Refresh(); return ScheduleNextExecutionTask; - }, due, due, ex => Logger.Error("Unhandled error while refreshing the license.", ex)); + }, due, due, ex => logger.LogError(ex, "Unhandled error while refreshing the license.")); return Task.CompletedTask; } @@ -24,7 +24,6 @@ public Task StartAsync(CancellationToken cancellationToken) TimerJob timer; - static readonly ILog Logger = LogManager.GetLogger(); static readonly Task ScheduleNextExecutionTask = Task.FromResult(TimerJobExecutionResult.ScheduleNextExecution); } } \ No newline at end of file diff --git a/src/ServiceControl.Monitoring/Program.cs b/src/ServiceControl.Monitoring/Program.cs index 8620eba3cc..fa34bf4978 100644 --- a/src/ServiceControl.Monitoring/Program.cs +++ b/src/ServiceControl.Monitoring/Program.cs @@ -1,13 +1,13 @@ using System; using System.Reflection; -using NServiceBus.Logging; +using Microsoft.Extensions.Logging; using ServiceControl.Configuration; using ServiceControl.Infrastructure; using ServiceControl.Monitoring; try { - AppDomain.CurrentDomain.UnhandledException += (s, e) => LogManager.GetLogger(typeof(Program)).Error("Unhandled exception was caught.", e.ExceptionObject as Exception); + AppDomain.CurrentDomain.UnhandledException += (s, e) => LoggerUtil.CreateStaticLogger().LogError(e.ExceptionObject as Exception, "Unhandled exception was caught."); // Hack: See https://github.com/Particular/ServiceControl/issues/4392 var exitCode = await IntegratedSetup.Run(); @@ -32,12 +32,11 @@ } catch (Exception ex) { - NLog.LogManager.GetCurrentClassLogger().Fatal(ex, "Unrecoverable error"); + LoggerUtil.CreateStaticLogger().LogCritical(ex, "Unrecoverable error"); throw; } finally { // The following log statement is meant to leave a trail in the logs to determine if the process was killed - NLog.LogManager.GetCurrentClassLogger().Info("Shutdown complete"); - NLog.LogManager.Shutdown(); -} \ No newline at end of file + LoggerUtil.CreateStaticLogger().LogInformation("Shutdown complete"); +} diff --git a/src/ServiceControl.Monitoring/QueueLength/LegacyQueueLengthReportHandler.cs b/src/ServiceControl.Monitoring/QueueLength/LegacyQueueLengthReportHandler.cs index 82a72e4a8b..93d905558d 100644 --- a/src/ServiceControl.Monitoring/QueueLength/LegacyQueueLengthReportHandler.cs +++ b/src/ServiceControl.Monitoring/QueueLength/LegacyQueueLengthReportHandler.cs @@ -5,33 +5,24 @@ using System.Threading; using System.Threading.Tasks; using Infrastructure; + using Microsoft.Extensions.Logging; using NServiceBus; - using NServiceBus.Logging; using NServiceBus.Metrics; - class LegacyQueueLengthReportHandler : IHandleMessages + class LegacyQueueLengthReportHandler(LegacyQueueLengthReportHandler.LegacyQueueLengthEndpoints legacyEndpoints, ILogger logger) : IHandleMessages { - public LegacyQueueLengthReportHandler(LegacyQueueLengthEndpoints legacyEndpoints) - { - this.legacyEndpoints = legacyEndpoints; - } - public Task Handle(MetricReport message, IMessageHandlerContext context) { var endpointInstanceId = EndpointInstanceId.From(context.MessageHeaders); if (legacyEndpoints.TryAdd(endpointInstanceId.InstanceId)) { - Logger.Warn($"Legacy queue length report received from {endpointInstanceId.InstanceName} instance of {endpointInstanceId.EndpointName}"); + logger.LogWarning("Legacy queue length report received from {EndpointInstanceIdInstanceName} instance of {EndpointInstanceIdEndpointName}", endpointInstanceId.InstanceName, endpointInstanceId.EndpointName); } return Task.CompletedTask; } - LegacyQueueLengthEndpoints legacyEndpoints; - - static readonly ILog Logger = LogManager.GetLogger(typeof(LegacyQueueLengthReportHandler)); - public class LegacyQueueLengthEndpoints { public bool TryAdd(string id) diff --git a/src/ServiceControl.Monitoring/ServiceControl.Monitoring.csproj b/src/ServiceControl.Monitoring/ServiceControl.Monitoring.csproj index 35246ea1b8..1a9b705469 100644 --- a/src/ServiceControl.Monitoring/ServiceControl.Monitoring.csproj +++ b/src/ServiceControl.Monitoring/ServiceControl.Monitoring.csproj @@ -24,7 +24,6 @@ - From e2196e6fbb2f591e82a276e2687acd6ca070022e Mon Sep 17 00:00:00 2001 From: JasonTaylorDev Date: Thu, 12 Jun 2025 17:08:39 +1000 Subject: [PATCH 2/8] Work in progress --- src/Directory.Packages.props | 4 +- .../ServiceControlComponentRunner.cs | 2 +- .../ServiceControlComponentRunner.cs | 3 +- .../CustomChecks/CheckFreeDiskSpace.cs | 10 ++-- .../RavenPersistenceConfiguration.cs | 2 +- .../HostApplicationBuilderExtensions.cs | 2 +- .../Infrastructure/WebApi/RootController.cs | 2 +- .../LoggerUtil.cs | 5 +- .../LoggingConfigurator.cs | 10 +++- .../LoggingSettings.cs | 55 +++++++++++-------- .../ServiceControl.Infrastructure.csproj | 2 + .../ServiceControlComponentRunner.cs | 3 +- .../HostApplicationBuilderExtensions.cs | 6 +- .../Hosting/Commands/SetupCommand.cs | 5 +- src/ServiceControl.Monitoring/Program.cs | 8 ++- .../ServiceControl.Monitoring.csproj | 1 + .../HostApplicationBuilderExtensions.cs | 2 +- .../Infrastructure/Api/ConfigurationApi.cs | 2 +- 18 files changed, 72 insertions(+), 52 deletions(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 60eb7072c9..dfd35a5f78 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -24,6 +24,7 @@ + @@ -63,6 +64,7 @@ + @@ -91,4 +93,4 @@ - + \ No newline at end of file diff --git a/src/ServiceControl.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs b/src/ServiceControl.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs index 19bbe6b57a..f1b3e58a01 100644 --- a/src/ServiceControl.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs +++ b/src/ServiceControl.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs @@ -15,7 +15,7 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; - using NLog; + using Microsoft.Extensions.Logging; using NServiceBus; using NServiceBus.AcceptanceTesting; using NServiceBus.AcceptanceTesting.Support; diff --git a/src/ServiceControl.Audit.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs b/src/ServiceControl.Audit.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs index 0866b504db..ce71680748 100644 --- a/src/ServiceControl.Audit.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs +++ b/src/ServiceControl.Audit.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs @@ -17,6 +17,7 @@ namespace ServiceControl.Audit.AcceptanceTests.TestSupport using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Hosting; + using Microsoft.Extensions.Logging; using NServiceBus; using NServiceBus.AcceptanceTesting; using NServiceBus.AcceptanceTesting.Support; @@ -43,7 +44,7 @@ async Task InitializeServiceControl(ScenarioContext context) var logPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Directory.CreateDirectory(logPath); - var loggingSettings = new LoggingSettings(Settings.SettingsRootNamespace, defaultLevel: NLog.LogLevel.Debug, logPath: logPath); + var loggingSettings = new LoggingSettings(Settings.SettingsRootNamespace, defaultLevel: LogLevel.Debug, logPath: logPath); settings = new Settings(transportToUse.TypeName, persistenceToUse.PersistenceType, loggingSettings) { diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckFreeDiskSpace.cs b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckFreeDiskSpace.cs index 923b6208d6..45efbac8e0 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckFreeDiskSpace.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckFreeDiskSpace.cs @@ -8,7 +8,6 @@ using Microsoft.Extensions.Logging; using NServiceBus.CustomChecks; using RavenDB; - using ServiceControl.Infrastructure; class CheckFreeDiskSpace(DatabaseConfiguration databaseConfiguration, ILogger logger) : CustomCheck("ServiceControl.Audit database", "Storage space", TimeSpan.FromMinutes(5)) { @@ -45,7 +44,7 @@ public override Task PerformCheck(CancellationToken cancellationTok : CheckResult.Failed($"{percentRemaining:P0} disk space remaining on data drive '{dataDriveInfo.VolumeLabel} ({dataDriveInfo.RootDirectory})' on '{Environment.MachineName}'."); } - public static int Parse(IDictionary settings) + public static int Parse(IDictionary settings, ILogger logger) { if (!settings.TryGetValue(RavenPersistenceConfiguration.DataSpaceRemainingThresholdKey, out var thresholdValue)) { @@ -54,19 +53,19 @@ public static int Parse(IDictionary settings) if (!int.TryParse(thresholdValue, out var threshold)) { - Logger.LogCritical("{RavenPersistenceConfigurationDataSpaceRemainingThresholdKey} must be an integer.", RavenPersistenceConfiguration.DataSpaceRemainingThresholdKey); + logger.LogCritical("{RavenPersistenceConfigurationDataSpaceRemainingThresholdKey} must be an integer.", RavenPersistenceConfiguration.DataSpaceRemainingThresholdKey); throw new Exception($"{RavenPersistenceConfiguration.DataSpaceRemainingThresholdKey} must be an integer."); } if (threshold < 0) { - Logger.LogCritical("{RavenPersistenceConfigurationDataSpaceRemainingThresholdKey} is invalid, minimum value is 0.", RavenPersistenceConfiguration.DataSpaceRemainingThresholdKey); + logger.LogCritical("{RavenPersistenceConfigurationDataSpaceRemainingThresholdKey} is invalid, minimum value is 0.", RavenPersistenceConfiguration.DataSpaceRemainingThresholdKey); throw new Exception($"{RavenPersistenceConfiguration.DataSpaceRemainingThresholdKey} is invalid, minimum value is 0."); } if (threshold > 100) { - Logger.LogCritical("{RavenPersistenceConfigurationDataSpaceRemainingThresholdKey} is invalid, maximum value is 100.", RavenPersistenceConfiguration.DataSpaceRemainingThresholdKey); + logger.LogCritical("{RavenPersistenceConfigurationDataSpaceRemainingThresholdKey} is invalid, maximum value is 100.", RavenPersistenceConfiguration.DataSpaceRemainingThresholdKey); throw new Exception($"{RavenPersistenceConfiguration.DataSpaceRemainingThresholdKey} is invalid, maximum value is 100."); } @@ -75,7 +74,6 @@ public static int Parse(IDictionary settings) readonly string dataPathRoot = Path.GetPathRoot(databaseConfiguration.ServerConfiguration.DbPath); readonly decimal percentageThreshold = databaseConfiguration.DataSpaceRemainingThreshold / 100m; - static readonly ILogger Logger = LoggerUtil.CreateStaticLogger(); public const int DataSpaceRemainingThresholdDefault = 20; } } \ No newline at end of file diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/RavenPersistenceConfiguration.cs b/src/ServiceControl.Audit.Persistence.RavenDB/RavenPersistenceConfiguration.cs index fc8aa2f281..3611a3f646 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/RavenPersistenceConfiguration.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/RavenPersistenceConfiguration.cs @@ -114,7 +114,7 @@ internal static DatabaseConfiguration GetDatabaseConfiguration(PersistenceSettin serverConfiguration = new ServerConfiguration(dbPath, serverUrl, logPath, logsMode); } - var dataSpaceRemainingThreshold = CheckFreeDiskSpace.Parse(settings.PersisterSpecificSettings); + var dataSpaceRemainingThreshold = CheckFreeDiskSpace.Parse(settings.PersisterSpecificSettings, Logger); var minimumStorageLeftRequiredForIngestion = CheckMinimumStorageRequiredForIngestion.Parse(settings.PersisterSpecificSettings); var expirationProcessTimerInSeconds = GetExpirationProcessTimerInSeconds(settings); diff --git a/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs b/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs index ccacf5db1c..f1bbebb1d7 100644 --- a/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs +++ b/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs @@ -41,7 +41,7 @@ public static void AddServiceControlAudit(this IHostApplicationBuilder builder, builder.Logging.ClearProviders(); builder.Logging.AddNLog(); - builder.Logging.SetMinimumLevel(settings.LoggingSettings.ToHostLogLevel()); + builder.Logging.SetMinimumLevel(settings.LoggingSettings.LogLevel); var services = builder.Services; var transportSettings = settings.ToTransportSettings(); diff --git a/src/ServiceControl.Audit/Infrastructure/WebApi/RootController.cs b/src/ServiceControl.Audit/Infrastructure/WebApi/RootController.cs index 42c53825d3..159e8c6d49 100644 --- a/src/ServiceControl.Audit/Infrastructure/WebApi/RootController.cs +++ b/src/ServiceControl.Audit/Infrastructure/WebApi/RootController.cs @@ -51,7 +51,7 @@ public OkObjectResult Config() Logging = new { settings.LoggingSettings.LogPath, - LoggingLevel = settings.LoggingSettings.LogLevel.Name + LoggingLevel = settings.LoggingSettings.LogLevel } }, DataRetention = new diff --git a/src/ServiceControl.Infrastructure/LoggerUtil.cs b/src/ServiceControl.Infrastructure/LoggerUtil.cs index 98aeb7d865..3fac64d05a 100644 --- a/src/ServiceControl.Infrastructure/LoggerUtil.cs +++ b/src/ServiceControl.Infrastructure/LoggerUtil.cs @@ -9,13 +9,16 @@ public static class LoggerUtil public static void BuildLogger(this ILoggingBuilder loggingBuilder, LogLevel level) { loggingBuilder.AddNLog(); + loggingBuilder.AddSeq(); loggingBuilder.SetMinimumLevel(level); } public static ILogger CreateStaticLogger(LogLevel level = LogLevel.Information) { var factory = LoggerFactory.Create(configure => configure.BuildLogger(level)); - return factory.CreateLogger(); + var logger = factory.CreateLogger(); + logger.LogError("JTD: This is a test log message."); + return logger; } public static ILogger CreateStaticLogger(Type type, LogLevel level = LogLevel.Information) diff --git a/src/ServiceControl.Infrastructure/LoggingConfigurator.cs b/src/ServiceControl.Infrastructure/LoggingConfigurator.cs index f32c84918c..1e505963da 100644 --- a/src/ServiceControl.Infrastructure/LoggingConfigurator.cs +++ b/src/ServiceControl.Infrastructure/LoggingConfigurator.cs @@ -60,11 +60,15 @@ public static void ConfigureLogging(LoggingSettings loggingSettings) nlogConfig.LoggingRules.Add(aspNetCoreRule); nlogConfig.LoggingRules.Add(httpClientRule); - nlogConfig.LoggingRules.Add(new LoggingRule("*", loggingSettings.LogLevel, consoleTarget)); + // HACK: Fix LogLevel to Info for testing purposes only. + // nlogConfig.LoggingRules.Add(new LoggingRule("*", loggingSettings.LogLevel, consoleTarget)); + nlogConfig.LoggingRules.Add(new LoggingRule("*", LogLevel.Info, consoleTarget)); if (!AppEnvironment.RunningInContainer) { - nlogConfig.LoggingRules.Add(new LoggingRule("*", loggingSettings.LogLevel, fileTarget)); + // HACK: Fix LogLevel to Info for testing purposes only. + //nlogConfig.LoggingRules.Add(new LoggingRule("*", loggingSettings.LogLevel, fileTarget)); + nlogConfig.LoggingRules.Add(new LoggingRule("*", LogLevel.Info, fileTarget)); } NLog.LogManager.Configuration = nlogConfig; @@ -74,7 +78,7 @@ public static void ConfigureLogging(LoggingSettings loggingSettings) var logger = LogManager.GetLogger("LoggingConfiguration"); var logEventInfo = new LogEventInfo { TimeStamp = DateTime.UtcNow }; var loggingTo = AppEnvironment.RunningInContainer ? "console" : fileTarget.FileName.Render(logEventInfo); - logger.InfoFormat("Logging to {0} with LogLevel '{1}'", loggingTo, loggingSettings.LogLevel.Name); + logger.InfoFormat("Logging to {0} with LogLevel '{1}'", loggingTo, LogLevel.Info.Name); } const long megaByte = 1024 * 1024; diff --git a/src/ServiceControl.Infrastructure/LoggingSettings.cs b/src/ServiceControl.Infrastructure/LoggingSettings.cs index 55282759a3..dd9fc18008 100644 --- a/src/ServiceControl.Infrastructure/LoggingSettings.cs +++ b/src/ServiceControl.Infrastructure/LoggingSettings.cs @@ -1,21 +1,19 @@ namespace ServiceControl.Infrastructure; using System; +using System.Collections.Generic; using System.IO; -using NLog; -using NLog.Common; +using Microsoft.Extensions.Logging; using ServiceControl.Configuration; -public class LoggingSettings(SettingsRootNamespace rootNamespace, LogLevel defaultLevel = null, string logPath = null) +public class LoggingSettings(SettingsRootNamespace rootNamespace, LogLevel defaultLevel = LogLevel.Information, string logPath = null) { public LogLevel LogLevel { get; } = InitializeLogLevel(rootNamespace, defaultLevel); - public string LogPath { get; } = SettingsReader.Read(rootNamespace, "LogPath", Environment.ExpandEnvironmentVariables(logPath ?? DefaultLogLocation())); + public string LogPath { get; } = SettingsReader.Read(rootNamespace, logPathKey, Environment.ExpandEnvironmentVariables(logPath ?? DefaultLogLocation())); static LogLevel InitializeLogLevel(SettingsRootNamespace rootNamespace, LogLevel defaultLevel) { - defaultLevel ??= LogLevel.Info; - var levelText = SettingsReader.Read(rootNamespace, logLevelKey); if (string.IsNullOrWhiteSpace(levelText)) @@ -23,31 +21,40 @@ static LogLevel InitializeLogLevel(SettingsRootNamespace rootNamespace, LogLevel return defaultLevel; } - try - { - return LogLevel.FromString(levelText); - } - catch - { - InternalLogger.Warn($"Failed to parse {logLevelKey} setting. Defaulting to {defaultLevel.Name}."); - return defaultLevel; - } + return ParseLogLevel(levelText, defaultLevel); } // SC installer always populates LogPath in app.config on installation/change/upgrade so this will only be used when // debugging or if the entry is removed manually. In those circumstances default to the folder containing the exe static string DefaultLogLocation() => Path.Combine(AppContext.BaseDirectory, ".logs"); - public Microsoft.Extensions.Logging.LogLevel ToHostLogLevel() => LogLevel switch + // This is not a complete mapping of NLog levels, just the ones that are different. + static readonly Dictionary NLogAliases = + new(StringComparer.OrdinalIgnoreCase) + { + ["info"] = LogLevel.Information, + ["warn"] = LogLevel.Warning, + ["fatal"] = LogLevel.Critical, + ["off"] = LogLevel.None + }; + + static LogLevel ParseLogLevel(string value, LogLevel defaultLevel) { - _ when LogLevel == LogLevel.Trace => Microsoft.Extensions.Logging.LogLevel.Trace, - _ when LogLevel == LogLevel.Debug => Microsoft.Extensions.Logging.LogLevel.Debug, - _ when LogLevel == LogLevel.Info => Microsoft.Extensions.Logging.LogLevel.Information, - _ when LogLevel == LogLevel.Warn => Microsoft.Extensions.Logging.LogLevel.Warning, - _ when LogLevel == LogLevel.Error => Microsoft.Extensions.Logging.LogLevel.Error, - _ when LogLevel == LogLevel.Fatal => Microsoft.Extensions.Logging.LogLevel.Critical, - _ => Microsoft.Extensions.Logging.LogLevel.None - }; + if (Enum.TryParse(value, ignoreCase: true, out LogLevel parsedLevel)) + { + return parsedLevel; + } + + if (NLogAliases.TryGetValue(value.Trim(), out parsedLevel)) + { + return parsedLevel; + } + + LoggerUtil.CreateStaticLogger().LogWarning("Failed to parse {LogLevelKey} setting. Defaulting to {DefaultLevel}.", logLevelKey, defaultLevel); + + return defaultLevel; + } const string logLevelKey = "LogLevel"; + const string logPathKey = "LogPath"; } \ No newline at end of file diff --git a/src/ServiceControl.Infrastructure/ServiceControl.Infrastructure.csproj b/src/ServiceControl.Infrastructure/ServiceControl.Infrastructure.csproj index ea352f1711..37332ed734 100644 --- a/src/ServiceControl.Infrastructure/ServiceControl.Infrastructure.csproj +++ b/src/ServiceControl.Infrastructure/ServiceControl.Infrastructure.csproj @@ -9,9 +9,11 @@ + + \ No newline at end of file diff --git a/src/ServiceControl.Monitoring.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs b/src/ServiceControl.Monitoring.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs index 212f1e03a1..debc3ed629 100644 --- a/src/ServiceControl.Monitoring.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs +++ b/src/ServiceControl.Monitoring.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs @@ -18,7 +18,6 @@ namespace ServiceControl.Monitoring.AcceptanceTests.TestSupport using NServiceBus.AcceptanceTesting; using NServiceBus.AcceptanceTesting.Support; using NServiceBus.Logging; - using ServiceControl.Infrastructure; class ServiceControlComponentRunner( ITransportIntegration transportToUse, @@ -73,7 +72,7 @@ async Task InitializeServiceControl(ScenarioContext context) using (new DiagnosticTimer($"Creating infrastructure for {settings.InstanceName}")) { - var setupCommand = new SetupCommand(LoggerUtil.CreateStaticLogger()); + var setupCommand = new SetupCommand(); await setupCommand.Execute(new HostArguments([]), settings); } diff --git a/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs b/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs index 90adce523d..7bda9a78ab 100644 --- a/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs +++ b/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs @@ -22,6 +22,7 @@ namespace ServiceControl.Monitoring; using NServiceBus.Features; using NServiceBus.Transport; using QueueLength; +using ServiceControl.Infrastructure; using Timings; using Transports; @@ -31,9 +32,8 @@ public static void AddServiceControlMonitoring(this IHostApplicationBuilder host Func onCriticalError, Settings settings, EndpointConfiguration endpointConfiguration) { - hostBuilder.Logging.ClearProviders(); - hostBuilder.Logging.AddNLog(); - hostBuilder.Logging.SetMinimumLevel(settings.LoggingSettings.ToHostLogLevel()); + hostBuilder.Services.AddLogging(); + hostBuilder.Logging.BuildLogger(settings.LoggingSettings.LogLevel); var services = hostBuilder.Services; diff --git a/src/ServiceControl.Monitoring/Hosting/Commands/SetupCommand.cs b/src/ServiceControl.Monitoring/Hosting/Commands/SetupCommand.cs index d585434197..22a84b27f7 100644 --- a/src/ServiceControl.Monitoring/Hosting/Commands/SetupCommand.cs +++ b/src/ServiceControl.Monitoring/Hosting/Commands/SetupCommand.cs @@ -2,15 +2,16 @@ namespace ServiceControl.Monitoring { using System.Threading.Tasks; using Microsoft.Extensions.Logging; + using ServiceControl.Infrastructure; using Transports; - class SetupCommand(ILogger logger) : AbstractCommand + class SetupCommand : AbstractCommand { public override Task Execute(HostArguments args, Settings settings) { if (args.SkipQueueCreation) { - logger.LogInformation("Skipping queue creation"); + LoggerUtil.CreateStaticLogger().LogInformation("Skipping queue creation"); return Task.CompletedTask; } diff --git a/src/ServiceControl.Monitoring/Program.cs b/src/ServiceControl.Monitoring/Program.cs index fa34bf4978..5aaa6f8c35 100644 --- a/src/ServiceControl.Monitoring/Program.cs +++ b/src/ServiceControl.Monitoring/Program.cs @@ -5,9 +5,11 @@ using ServiceControl.Infrastructure; using ServiceControl.Monitoring; +var logger = LoggerUtil.CreateStaticLogger(); + try { - AppDomain.CurrentDomain.UnhandledException += (s, e) => LoggerUtil.CreateStaticLogger().LogError(e.ExceptionObject as Exception, "Unhandled exception was caught."); + AppDomain.CurrentDomain.UnhandledException += (s, e) => logger.LogError(e.ExceptionObject as Exception, "Unhandled exception was caught."); // Hack: See https://github.com/Particular/ServiceControl/issues/4392 var exitCode = await IntegratedSetup.Run(); @@ -32,11 +34,11 @@ } catch (Exception ex) { - LoggerUtil.CreateStaticLogger().LogCritical(ex, "Unrecoverable error"); + logger.LogCritical(ex, "Unrecoverable error"); throw; } finally { // The following log statement is meant to leave a trail in the logs to determine if the process was killed - LoggerUtil.CreateStaticLogger().LogInformation("Shutdown complete"); + logger.LogInformation("Shutdown complete"); } diff --git a/src/ServiceControl.Monitoring/ServiceControl.Monitoring.csproj b/src/ServiceControl.Monitoring/ServiceControl.Monitoring.csproj index 1a9b705469..9fbcf67af7 100644 --- a/src/ServiceControl.Monitoring/ServiceControl.Monitoring.csproj +++ b/src/ServiceControl.Monitoring/ServiceControl.Monitoring.csproj @@ -25,6 +25,7 @@ + diff --git a/src/ServiceControl/HostApplicationBuilderExtensions.cs b/src/ServiceControl/HostApplicationBuilderExtensions.cs index 50d0733fb9..76140a95ea 100644 --- a/src/ServiceControl/HostApplicationBuilderExtensions.cs +++ b/src/ServiceControl/HostApplicationBuilderExtensions.cs @@ -46,7 +46,7 @@ public static void AddServiceControl(this IHostApplicationBuilder hostBuilder, S logging.ClearProviders(); //HINT: configuration used by NLog comes from LoggingConfigurator.cs logging.AddNLog(); - logging.SetMinimumLevel(settings.LoggingSettings.ToHostLogLevel()); + logging.SetMinimumLevel(settings.LoggingSettings.LogLevel); var services = hostBuilder.Services; var transportSettings = settings.ToTransportSettings(); diff --git a/src/ServiceControl/Infrastructure/Api/ConfigurationApi.cs b/src/ServiceControl/Infrastructure/Api/ConfigurationApi.cs index fa06e80bc9..f8af272eb4 100644 --- a/src/ServiceControl/Infrastructure/Api/ConfigurationApi.cs +++ b/src/ServiceControl/Infrastructure/Api/ConfigurationApi.cs @@ -60,7 +60,7 @@ public Task GetConfig(CancellationToken cancellationToken) Logging = new { settings.LoggingSettings.LogPath, - LoggingLevel = settings.LoggingSettings.LogLevel.Name + LoggingLevel = settings.LoggingSettings.LogLevel } }, DataRetention = new From e9f287e1af3e39087ca5332410898b6d714de941 Mon Sep 17 00:00:00 2001 From: JasonTaylorDev Date: Fri, 13 Jun 2025 11:45:22 +1000 Subject: [PATCH 3/8] Remove test code --- src/ServiceControl.Infrastructure/LoggerUtil.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ServiceControl.Infrastructure/LoggerUtil.cs b/src/ServiceControl.Infrastructure/LoggerUtil.cs index 3fac64d05a..623775e202 100644 --- a/src/ServiceControl.Infrastructure/LoggerUtil.cs +++ b/src/ServiceControl.Infrastructure/LoggerUtil.cs @@ -16,9 +16,7 @@ public static void BuildLogger(this ILoggingBuilder loggingBuilder, LogLevel lev public static ILogger CreateStaticLogger(LogLevel level = LogLevel.Information) { var factory = LoggerFactory.Create(configure => configure.BuildLogger(level)); - var logger = factory.CreateLogger(); - logger.LogError("JTD: This is a test log message."); - return logger; + return factory.CreateLogger(); } public static ILogger CreateStaticLogger(Type type, LogLevel level = LogLevel.Information) From 9b97c92d59daf0bc156f269bdc8ee47249e8552e Mon Sep 17 00:00:00 2001 From: JasonTaylorDev Date: Tue, 17 Jun 2025 10:06:13 +1000 Subject: [PATCH 4/8] Improve logging format for storage space details --- .../CustomChecks/CheckMinimumStorageRequiredForIngestion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckMinimumStorageRequiredForIngestion.cs b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckMinimumStorageRequiredForIngestion.cs index 56153b3169..033a99fbbe 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckMinimumStorageRequiredForIngestion.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckMinimumStorageRequiredForIngestion.cs @@ -38,7 +38,7 @@ public override Task PerformCheck(CancellationToken cancellationTok if (logger.IsEnabled(LogLevel.Debug)) { - logger.LogDebug("Free space: {AvailableFreeSpace} | Total: {TotalSpace} | Percent remaining {PercentRemaining:P0}", availableFreeSpace, totalSpace, percentRemaining); + logger.LogDebug("Free space: {AvailableFreeSpace:N0}B | Total: {TotalSpace:N0}B | Percent remaining {PercentRemaining:P0}", availableFreeSpace, totalSpace, percentRemaining); } if (percentRemaining > percentageThreshold) From 005968d492cc02852c676a1c41e146c52fe676bb Mon Sep 17 00:00:00 2001 From: JasonTaylorDev Date: Tue, 17 Jun 2025 10:10:46 +1000 Subject: [PATCH 5/8] Properly shutdown NLog in Program.cs --- src/ServiceControl.Monitoring/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ServiceControl.Monitoring/Program.cs b/src/ServiceControl.Monitoring/Program.cs index 5aaa6f8c35..7a618a381f 100644 --- a/src/ServiceControl.Monitoring/Program.cs +++ b/src/ServiceControl.Monitoring/Program.cs @@ -41,4 +41,5 @@ { // The following log statement is meant to leave a trail in the logs to determine if the process was killed logger.LogInformation("Shutdown complete"); + NLog.LogManager.Shutdown(); } From 49ba86c1c1af137403b646ebd559811a8bfabea5 Mon Sep 17 00:00:00 2001 From: JasonTaylorDev Date: Tue, 17 Jun 2025 10:52:10 +1000 Subject: [PATCH 6/8] Remove Seq logging and prepare for .NET logging migration --- src/ServiceControl.Infrastructure/LoggerUtil.cs | 1 - .../LoggingConfigurator.cs | 11 +++++++---- .../ServiceControl.Infrastructure.csproj | 1 - .../ServiceControl.Monitoring.csproj | 1 - 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ServiceControl.Infrastructure/LoggerUtil.cs b/src/ServiceControl.Infrastructure/LoggerUtil.cs index 623775e202..98aeb7d865 100644 --- a/src/ServiceControl.Infrastructure/LoggerUtil.cs +++ b/src/ServiceControl.Infrastructure/LoggerUtil.cs @@ -9,7 +9,6 @@ public static class LoggerUtil public static void BuildLogger(this ILoggingBuilder loggingBuilder, LogLevel level) { loggingBuilder.AddNLog(); - loggingBuilder.AddSeq(); loggingBuilder.SetMinimumLevel(level); } diff --git a/src/ServiceControl.Infrastructure/LoggingConfigurator.cs b/src/ServiceControl.Infrastructure/LoggingConfigurator.cs index 1e505963da..5ca1c06c9b 100644 --- a/src/ServiceControl.Infrastructure/LoggingConfigurator.cs +++ b/src/ServiceControl.Infrastructure/LoggingConfigurator.cs @@ -12,6 +12,7 @@ namespace ServiceControl.Infrastructure using LogManager = NServiceBus.Logging.LogManager; + // TODO: Migrate from NLog to .NET logging public static class LoggingConfigurator { public static void ConfigureLogging(LoggingSettings loggingSettings) @@ -60,14 +61,16 @@ public static void ConfigureLogging(LoggingSettings loggingSettings) nlogConfig.LoggingRules.Add(aspNetCoreRule); nlogConfig.LoggingRules.Add(httpClientRule); - // HACK: Fix LogLevel to Info for testing purposes only. - // nlogConfig.LoggingRules.Add(new LoggingRule("*", loggingSettings.LogLevel, consoleTarget)); + // HACK: Fixed LogLevel to Info for testing purposes only. + // Migrate to .NET logging and change back to loggingSettings.LogLevel. + // nlogConfig.LoggingRules.Add(new LoggingRule("*", loggingSettings.LogLevel, consoleTarget)); nlogConfig.LoggingRules.Add(new LoggingRule("*", LogLevel.Info, consoleTarget)); if (!AppEnvironment.RunningInContainer) { - // HACK: Fix LogLevel to Info for testing purposes only. - //nlogConfig.LoggingRules.Add(new LoggingRule("*", loggingSettings.LogLevel, fileTarget)); + // HACK: Fixed LogLevel to Info for testing purposes only. + // Migrate to .NET logging and change back to loggingSettings.LogLevel. + // nlogConfig.LoggingRules.Add(new LoggingRule("*", loggingSettings.LogLevel, fileTarget)); nlogConfig.LoggingRules.Add(new LoggingRule("*", LogLevel.Info, fileTarget)); } diff --git a/src/ServiceControl.Infrastructure/ServiceControl.Infrastructure.csproj b/src/ServiceControl.Infrastructure/ServiceControl.Infrastructure.csproj index 37332ed734..29f38a7373 100644 --- a/src/ServiceControl.Infrastructure/ServiceControl.Infrastructure.csproj +++ b/src/ServiceControl.Infrastructure/ServiceControl.Infrastructure.csproj @@ -13,7 +13,6 @@ - \ No newline at end of file diff --git a/src/ServiceControl.Monitoring/ServiceControl.Monitoring.csproj b/src/ServiceControl.Monitoring/ServiceControl.Monitoring.csproj index 9fbcf67af7..1a9b705469 100644 --- a/src/ServiceControl.Monitoring/ServiceControl.Monitoring.csproj +++ b/src/ServiceControl.Monitoring/ServiceControl.Monitoring.csproj @@ -25,7 +25,6 @@ - From 938c963fe4cdf413c91586365141edf193fd7db4 Mon Sep 17 00:00:00 2001 From: JasonTaylorDev Date: Tue, 17 Jun 2025 12:05:17 +1000 Subject: [PATCH 7/8] Update LogLevel format --- .../APIApprovals.PlatformSampleSettings.approved.txt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ServiceControl.Audit.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt b/src/ServiceControl.Audit.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt index f718f461aa..7113c06339 100644 --- a/src/ServiceControl.Audit.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt +++ b/src/ServiceControl.Audit.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt @@ -1,9 +1,6 @@ { "LoggingSettings": { - "LogLevel": { - "Name": "Info", - "Ordinal": 2 - }, + "LogLevel": "Information", "LogPath": "C:\\Logs" }, "MessageFilter": null, From fb7f77e2b255ca037398c6259dbced9678638096 Mon Sep 17 00:00:00 2001 From: JasonTaylorDev Date: Tue, 17 Jun 2025 12:22:18 +1000 Subject: [PATCH 8/8] Update LogLevel format in logging settings --- .../SettingsTests.PlatformSampleSettings.approved.txt | 5 +---- .../APIApprovals.PlatformSampleSettings.approved.txt | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/ServiceControl.Monitoring.UnitTests/ApprovalFiles/SettingsTests.PlatformSampleSettings.approved.txt b/src/ServiceControl.Monitoring.UnitTests/ApprovalFiles/SettingsTests.PlatformSampleSettings.approved.txt index 5d32f42fbb..070234384e 100644 --- a/src/ServiceControl.Monitoring.UnitTests/ApprovalFiles/SettingsTests.PlatformSampleSettings.approved.txt +++ b/src/ServiceControl.Monitoring.UnitTests/ApprovalFiles/SettingsTests.PlatformSampleSettings.approved.txt @@ -1,9 +1,6 @@ { "LoggingSettings": { - "LogLevel": { - "Name": "Info", - "Ordinal": 2 - }, + "LogLevel": "Information", "LogPath": "C:\\Logs" }, "InstanceName": "Particular.Monitoring", diff --git a/src/ServiceControl.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt b/src/ServiceControl.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt index a23480af26..246f3e5678 100644 --- a/src/ServiceControl.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt +++ b/src/ServiceControl.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt @@ -1,9 +1,6 @@ { "LoggingSettings": { - "LogLevel": { - "Name": "Info", - "Ordinal": 2 - }, + "LogLevel": "Information", "LogPath": "C:\\Logs" }, "NotificationsFilter": null,