From 7824d6b9b264fade6cd2f5aefb4581fff90de166 Mon Sep 17 00:00:00 2001 From: Brandon Ording Date: Fri, 22 Mar 2024 11:39:05 -0400 Subject: [PATCH] Logging fixes and improvements (#4019) * Sort usings * Prevent GetLogger from being called before NLog init * Align Monitoring default log level with other instances * Simplify and unify logging configuration * Pass LoggingSettings to CommandRunner * Align more logging settings * Expose LoggingSettings through Settings --- .../StartupModeTests.cs | 3 +- .../ServiceControlComponentRunner.cs | 14 ++-- .../ServiceControlComponentRunner.cs | 15 ++-- .../API/APIApprovals.cs | 5 +- ...rovals.PlatformSampleSettings.approved.txt | 7 ++ .../Infrastructure/When_instance_is_setup.cs | 3 +- .../Auditing/AuditIngestion.cs | 3 +- .../HostApplicationBuilderExtensions.cs | 18 ++--- .../Hosting/Commands/AbstractCommand.cs | 3 +- .../Hosting/Commands/CommandRunner.cs | 3 +- .../Commands/ImportFailedAuditsCommand.cs | 4 +- .../Commands/MaintenanceModeCommand.cs | 3 +- .../Hosting/Commands/RunCommand.cs | 4 +- .../Infrastructure/LoggingConfigurator.cs | 66 ++++------------- .../Infrastructure/NServiceBusFactory.cs | 7 +- .../Settings/LoggingSettings.cs | 68 +++++------------ .../Infrastructure/Settings/Settings.cs | 26 ++++--- .../Infrastructure/WebApi/RootController.cs | 9 +-- src/ServiceControl.Audit/Program.cs | 9 +-- .../PerformanceTests.cs | 1 - ...sTests.PlatformSampleSettings.approved.txt | 12 +-- .../app.config | 1 - .../HostApplicationBuilderExtensions.cs | 40 +--------- .../Hosting/Commands/CommandRunner.cs | 2 +- .../Hosting/Commands/SetupCommand.cs | 2 +- .../Infrastructure/LoggingConfigurator.cs | 74 ++++--------------- .../Licensing/LicenseManager.cs | 2 +- .../LoggingSettings.cs | 54 ++++++++++++++ src/ServiceControl.Monitoring/Program.cs | 21 ++---- src/ServiceControl.Monitoring/Settings.cs | 24 ++---- .../RetryStateTests.cs | 3 +- .../API/APIApprovals.cs | 1 - ...rovals.PlatformSampleSettings.approved.txt | 7 ++ .../HostApplicationBuilderExtensions.cs | 15 ++-- .../Commands/ImportFailedErrorsCommand.cs | 5 +- .../Hosting/Commands/RunCommand.cs | 6 +- .../Hosting/Commands/SetupCommand.cs | 4 +- .../Infrastructure/NServiceBusFactory.cs | 7 +- .../Settings/LoggingSettings.cs | 68 +++++------------ .../Infrastructure/Settings/Settings.cs | 9 ++- .../Infrastructure/WebApi/RootController.cs | 6 +- src/ServiceControl/LoggingConfigurator.cs | 66 ++++------------- .../Operations/ErrorIngestion.cs | 3 +- src/ServiceControl/Program.cs | 9 +-- 44 files changed, 266 insertions(+), 446 deletions(-) create mode 100644 src/ServiceControl.Monitoring/LoggingSettings.cs diff --git a/src/ServiceControl.AcceptanceTests.RavenDB/StartupModeTests.cs b/src/ServiceControl.AcceptanceTests.RavenDB/StartupModeTests.cs index 3f08cd46a0..1ad066e4c9 100644 --- a/src/ServiceControl.AcceptanceTests.RavenDB/StartupModeTests.cs +++ b/src/ServiceControl.AcceptanceTests.RavenDB/StartupModeTests.cs @@ -6,7 +6,6 @@ using Microsoft.Extensions.Hosting; using NServiceBus; using NUnit.Framework; - using Particular.ServiceControl; using Particular.ServiceControl.Hosting; using Persistence; using Persistence.RavenDB; @@ -63,7 +62,7 @@ protected override EndpointConfiguration CreateEndpointConfiguration(Settings se { var configuration = base.CreateEndpointConfiguration(settings); - //HINT: we want to exclude this assembly to prevent loading features that are part of the acceptance testing framework + //HINT: we want to exclude this assembly to prevent loading features that are part of the acceptance testing framework var thisAssembly = new[] { typeof(StartupModeTests).Assembly.GetName().Name }; configuration.AssemblyScanner().ExcludeAssemblies(thisAssembly); diff --git a/src/ServiceControl.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs b/src/ServiceControl.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs index f74b6e12e5..b055f4f24a 100644 --- a/src/ServiceControl.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs +++ b/src/ServiceControl.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs @@ -45,7 +45,11 @@ public ServiceControlComponentRunner(ITransportIntegration transportToUse, Accep async Task InitializeServiceControl(ScenarioContext context) { - var settings = new Settings(instanceName, transportToUse.TypeName, persistenceToUse.PersistenceType, forwardErrorMessages: false, errorRetentionPeriod: TimeSpan.FromDays(10)) + var logPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(logPath); + var loggingSettings = new LoggingSettings(defaultLevel: LogLevel.Debug, logPath: logPath); + + var settings = new Settings(instanceName, transportToUse.TypeName, persistenceToUse.PersistenceType, loggingSettings, forwardErrorMessages: false, errorRetentionPeriod: TimeSpan.FromDays(10)) { AllowMessageEditing = true, ForwardErrorMessages = false, @@ -95,7 +99,7 @@ async Task InitializeServiceControl(ScenarioContext context) using (new DiagnosticTimer($"Creating infrastructure for {instanceName}")) { var setupCommand = new SetupCommand(); - await setupCommand.Execute(new HostArguments(Array.Empty()), settings); + await setupCommand.Execute(new HostArguments([]), settings); } var configuration = new EndpointConfiguration(instanceName); @@ -105,16 +109,12 @@ async Task InitializeServiceControl(ScenarioContext context) using (new DiagnosticTimer($"Starting ServiceControl {instanceName}")) { - var logPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - Directory.CreateDirectory(logPath); - - var loggingSettings = new LoggingSettings(settings.ServiceName, defaultLevel: LogLevel.Debug, logPath: logPath); var hostBuilder = WebApplication.CreateBuilder(new WebApplicationOptions { // Force the DI container to run the dependency resolution check to verify all dependencies can be resolved EnvironmentName = Environments.Development }); - hostBuilder.AddServiceControl(settings, configuration, loggingSettings); + hostBuilder.AddServiceControl(settings, configuration); hostBuilder.AddServiceControlApi(); hostBuilder.AddServiceControlTesting(settings); diff --git a/src/ServiceControl.Audit.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs b/src/ServiceControl.Audit.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs index 7b4c6a0af8..f035a27fb0 100644 --- a/src/ServiceControl.Audit.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs +++ b/src/ServiceControl.Audit.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs @@ -38,7 +38,12 @@ public class ServiceControlComponentRunner( async Task InitializeServiceControl(ScenarioContext context) { - settings = new Settings(instanceName, transportToUse.TypeName, persistenceToUse.PersistenceType) + var logPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(logPath); + + var loggingSettings = new LoggingSettings(defaultLevel: NLog.LogLevel.Debug, logPath: logPath); + + settings = new Settings(instanceName, transportToUse.TypeName, persistenceToUse.PersistenceType, loggingSettings) { TransportConnectionString = transportToUse.ConnectionString, MaximumConcurrencyLevel = 2, @@ -91,7 +96,7 @@ async Task InitializeServiceControl(ScenarioContext context) using (new DiagnosticTimer($"Creating infrastructure for {instanceName}")) { var setupCommand = new SetupCommand(); - await setupCommand.Execute(new HostArguments(Array.Empty()), settings); + await setupCommand.Execute(new HostArguments([]), settings); } var configuration = new EndpointConfiguration(instanceName); @@ -101,10 +106,6 @@ async Task InitializeServiceControl(ScenarioContext context) using (new DiagnosticTimer($"Starting ServiceControl {instanceName}")) { - var logPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - Directory.CreateDirectory(logPath); - - var loggingSettings = new LoggingSettings(settings.ServiceName, defaultLevel: NLog.LogLevel.Debug, logPath: logPath); var hostBuilder = WebApplication.CreateBuilder(new WebApplicationOptions { // Force the DI container to run the dependency resolution check to verify all dependencies can be resolved @@ -121,7 +122,7 @@ async Task InitializeServiceControl(ScenarioContext context) }; context.Logs.Enqueue(logitem); return criticalErrorContext.Stop(cancellationToken); - }, settings, configuration, loggingSettings); + }, settings, configuration); hostBuilder.AddServiceControlAuditApi(); diff --git a/src/ServiceControl.Audit.UnitTests/API/APIApprovals.cs b/src/ServiceControl.Audit.UnitTests/API/APIApprovals.cs index cbc7925677..c2972faf54 100644 --- a/src/ServiceControl.Audit.UnitTests/API/APIApprovals.cs +++ b/src/ServiceControl.Audit.UnitTests/API/APIApprovals.cs @@ -30,10 +30,7 @@ public void RootPathValue() var settings = CreateTestSettings(); - var controller = new RootController( - new LoggingSettings("testEndpoint"), - settings - ) + var controller = new RootController(settings) { ControllerContext = controllerContext, Url = new UrlHelper(actionContext) diff --git a/src/ServiceControl.Audit.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt b/src/ServiceControl.Audit.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt index 5739f5dc9d..cec99da58f 100644 --- a/src/ServiceControl.Audit.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt +++ b/src/ServiceControl.Audit.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt @@ -1,4 +1,11 @@ { + "LoggingSettings": { + "LogLevel": { + "Name": "Info", + "Ordinal": 2 + }, + "LogPath": "C:\\Logs" + }, "MessageFilter": null, "ValidateConfiguration": true, "SkipQueueCreation": false, diff --git a/src/ServiceControl.Audit.UnitTests/Infrastructure/When_instance_is_setup.cs b/src/ServiceControl.Audit.UnitTests/Infrastructure/When_instance_is_setup.cs index 946384e43d..222eb6fdcc 100644 --- a/src/ServiceControl.Audit.UnitTests/Infrastructure/When_instance_is_setup.cs +++ b/src/ServiceControl.Audit.UnitTests/Infrastructure/When_instance_is_setup.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; - using Audit.Infrastructure; using Audit.Infrastructure.Hosting; using Audit.Infrastructure.Hosting.Commands; using Audit.Infrastructure.Settings; @@ -28,7 +27,7 @@ public async Task Should_provision_queues() }; var setupCommand = new SetupCommand(); - await setupCommand.Execute(new HostArguments(Array.Empty()), settings); + await setupCommand.Execute(new HostArguments([]), settings); CollectionAssert.AreEquivalent(new[] { diff --git a/src/ServiceControl.Audit/Auditing/AuditIngestion.cs b/src/ServiceControl.Audit/Auditing/AuditIngestion.cs index 3ef0f65df2..d4c64dc968 100644 --- a/src/ServiceControl.Audit/Auditing/AuditIngestion.cs +++ b/src/ServiceControl.Audit/Auditing/AuditIngestion.cs @@ -27,7 +27,6 @@ class AuditIngestion : IHostedService TransportSettings transportSettings, Metrics metrics, IFailedAuditStorage failedImportsStorage, - LoggingSettings loggingSettings, AuditIngestionCustomCheck.State ingestionState, AuditIngestor auditIngestor, IAuditIngestionUnitOfWorkFactory unitOfWorkFactory, @@ -53,7 +52,7 @@ class AuditIngestion : IHostedService FullMode = BoundedChannelFullMode.Wait }); - errorHandlingPolicy = new AuditIngestionFaultPolicy(failedImportsStorage, loggingSettings, OnCriticalError); + errorHandlingPolicy = new AuditIngestionFaultPolicy(failedImportsStorage, settings.LoggingSettings, OnCriticalError); watchdog = new Watchdog("audit message ingestion", EnsureStarted, EnsureStopped, ingestionState.ReportError, ingestionState.Clear, settings.TimeToRestartAuditIngestionAfterFailure, logger); diff --git a/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs b/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs index ca01e8368d..fac54507fc 100644 --- a/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs +++ b/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs @@ -2,7 +2,6 @@ namespace ServiceControl.Audit; using System; using System.Diagnostics; -using System.Net; using System.Threading; using System.Threading.Tasks; using Auditing; @@ -27,14 +26,13 @@ static class HostApplicationBuilderExtensions public static void AddServiceControlAudit(this IHostApplicationBuilder builder, Func onCriticalError, Settings settings, - EndpointConfiguration configuration, - LoggingSettings loggingSettings) + EndpointConfiguration configuration) { var persistenceConfiguration = PersistenceConfigurationFactory.LoadPersistenceConfiguration(settings.PersistenceType); var persistenceSettings = persistenceConfiguration.BuildPersistenceSettings(settings); - RecordStartup(settings, loggingSettings, configuration, persistenceConfiguration); + RecordStartup(settings, configuration, persistenceConfiguration); if (!string.IsNullOrWhiteSpace(settings.LicenseFileText)) { @@ -46,7 +44,7 @@ static class HostApplicationBuilderExtensions builder.Logging.ClearProviders(); builder.Logging.AddNLog(); - builder.Logging.SetMinimumLevel(loggingSettings.ToHostLogLevel()); + builder.Logging.SetMinimumLevel(settings.LoggingSettings.ToHostLogLevel()); var services = builder.Services; @@ -54,7 +52,6 @@ static class HostApplicationBuilderExtensions services.AddSingleton(transportSettings); services.AddSingleton(transportCustomization); - services.AddSingleton(loggingSettings); services.AddSingleton(settings); services.AddSingleton(); services.AddSingleton(); @@ -76,7 +73,7 @@ static class HostApplicationBuilderExtensions services.AddPersistence(persistenceSettings, persistenceConfiguration); - NServiceBusFactory.Configure(settings, transportCustomization, transportSettings, loggingSettings, onCriticalError, configuration); + NServiceBusFactory.Configure(settings, transportCustomization, transportSettings, onCriticalError, configuration); builder.UseNServiceBus(configuration); // Configure after the NServiceBus hosted service to ensure NServiceBus is already started @@ -99,7 +96,7 @@ static TransportSettings MapSettings(Settings settings) return transportSettings; } - static void RecordStartup(Settings settings, LoggingSettings loggingSettings, EndpointConfiguration endpointConfiguration, IPersistenceConfiguration persistenceConfiguration) + static void RecordStartup(Settings settings, EndpointConfiguration endpointConfiguration, IPersistenceConfiguration persistenceConfiguration) { var version = FileVersionInfo.GetVersionInfo(typeof(HostApplicationBuilderExtensions).Assembly.Location).ProductVersion; @@ -108,7 +105,7 @@ static void RecordStartup(Settings settings, LoggingSettings loggingSettings, En ServiceControl Audit Version: {version} Audit Retention Period: {settings.AuditRetentionPeriod} Forwarding Audit Messages: {settings.ForwardAuditMessages} -ServiceControl Logging Level: {loggingSettings.LoggingLevel} +ServiceControl Logging Level: {settings.LoggingSettings.LogLevel} Transport Customization: {settings.TransportType}, Persistence Customization: {settings.PersistenceType}, Persistence: {persistenceConfiguration.Name} @@ -118,8 +115,7 @@ static void RecordStartup(Settings settings, LoggingSettings loggingSettings, En logger.Info(startupMessage); endpointConfiguration.GetSettings().AddStartupDiagnosticsSection("Startup", new { - Settings = settings, - LoggingSettings = loggingSettings + Settings = settings }); } } \ No newline at end of file diff --git a/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/AbstractCommand.cs b/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/AbstractCommand.cs index 1f8aa11219..5f6de0478e 100644 --- a/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/AbstractCommand.cs +++ b/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/AbstractCommand.cs @@ -1,9 +1,10 @@ namespace ServiceControl.Audit.Infrastructure.Hosting.Commands { using System.Threading.Tasks; + using ServiceControl.Audit.Infrastructure.Settings; abstract class AbstractCommand { - public abstract Task Execute(HostArguments args, Settings.Settings settings); + public abstract Task Execute(HostArguments args, Settings settings); } } \ No newline at end of file diff --git a/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/CommandRunner.cs b/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/CommandRunner.cs index f70130f443..e4beac275b 100644 --- a/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/CommandRunner.cs +++ b/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/CommandRunner.cs @@ -3,10 +3,11 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; + using ServiceControl.Audit.Infrastructure.Settings; class CommandRunner(List commands) { - public async Task Execute(HostArguments args, Settings.Settings settings) + public async Task Execute(HostArguments args, Settings settings) { foreach (var commandType in commands) { diff --git a/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/ImportFailedAuditsCommand.cs b/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/ImportFailedAuditsCommand.cs index c80ecf579f..c56f5caad3 100644 --- a/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/ImportFailedAuditsCommand.cs +++ b/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/ImportFailedAuditsCommand.cs @@ -6,7 +6,6 @@ using Auditing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; - using NLog; using NServiceBus; using Settings; @@ -17,7 +16,6 @@ public override async Task Execute(HostArguments args, Settings settings) settings.IngestAuditMessages = false; var endpointConfiguration = new EndpointConfiguration(settings.ServiceName); - var loggingSettings = new LoggingSettings(settings.ServiceName, LogLevel.Info); using var tokenSource = new CancellationTokenSource(); @@ -26,7 +24,7 @@ public override async Task Execute(HostArguments args, Settings settings) { tokenSource.Cancel(); return Task.CompletedTask; - }, settings, endpointConfiguration, loggingSettings); + }, settings, endpointConfiguration); using var app = hostBuilder.Build(); await app.StartAsync(tokenSource.Token); diff --git a/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/MaintenanceModeCommand.cs b/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/MaintenanceModeCommand.cs index 979762caf5..a6ae0c2a2d 100644 --- a/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/MaintenanceModeCommand.cs +++ b/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/MaintenanceModeCommand.cs @@ -5,10 +5,11 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting.WindowsServices; using Persistence; + using ServiceControl.Audit.Infrastructure.Settings; class MaintenanceModeCommand : AbstractCommand { - public override async Task Execute(HostArguments args, Settings.Settings settings) + public override async Task Execute(HostArguments args, Settings settings) { var persistenceConfiguration = PersistenceConfigurationFactory.LoadPersistenceConfiguration(settings.PersistenceType); var persistenceSettings = persistenceConfiguration.BuildPersistenceSettings(settings); diff --git a/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/RunCommand.cs b/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/RunCommand.cs index e7275213df..1bcac6d8ea 100644 --- a/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/RunCommand.cs +++ b/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/RunCommand.cs @@ -14,14 +14,12 @@ public override async Task Execute(HostArguments args, Settings settings) var assemblyScanner = endpointConfiguration.AssemblyScanner(); assemblyScanner.ExcludeAssemblies("ServiceControl.Plugin"); - var loggingSettings = new LoggingSettings(settings.ServiceName); - var hostBuilder = WebApplication.CreateBuilder(); hostBuilder.AddServiceControlAudit((_, __) => { //Do nothing. The transports in NSB 8 are designed to handle broker outages. Audit ingestion will be paused when broker is unavailable. return Task.CompletedTask; - }, settings, endpointConfiguration, loggingSettings); + }, settings, endpointConfiguration); hostBuilder.AddServiceControlAuditApi(); var app = hostBuilder.Build(); diff --git a/src/ServiceControl.Audit/Infrastructure/LoggingConfigurator.cs b/src/ServiceControl.Audit/Infrastructure/LoggingConfigurator.cs index 0debc872ce..abbab552a9 100644 --- a/src/ServiceControl.Audit/Infrastructure/LoggingConfigurator.cs +++ b/src/ServiceControl.Audit/Infrastructure/LoggingConfigurator.cs @@ -1,18 +1,16 @@ namespace ServiceControl.Audit.Infrastructure { - using System.Diagnostics; + using System; using System.IO; - using System.Linq; + using NLog; using NLog.Config; + using NLog.Extensions.Logging; using NLog.Layouts; using NLog.Targets; using NServiceBus.Extensions.Logging; - using LogManager = NServiceBus.Logging.LogManager; using Settings; using LogLevel = NLog.LogLevel; - using NLog.Extensions.Logging; - using NLog; - using System; + using LogManager = NServiceBus.Logging.LogManager; static class LoggingConfigurator { @@ -23,81 +21,47 @@ public static void ConfigureLogging(LoggingSettings loggingSettings) return; } - var version = FileVersionInfo.GetVersionInfo(typeof(LoggingConfigurator).Assembly.Location).ProductVersion; var nlogConfig = new LoggingConfiguration(); var simpleLayout = new SimpleLayout("${longdate}|${threadid}|${level}|${logger}|${message}${onexception:|${exception:format=tostring}}"); - var header = $@"------------------------------------------------------------- -ServiceControl Audit Version: {version} --------------------------------------------------------------"; var fileTarget = new FileTarget { + Name = "file", ArchiveEvery = FileArchivePeriod.Day, FileName = Path.Combine(loggingSettings.LogPath, "logfile.${shortdate}.txt"), ArchiveFileName = Path.Combine(loggingSettings.LogPath, "logfile.{#}.txt"), ArchiveNumbering = ArchiveNumberingMode.DateAndSequence, Layout = simpleLayout, MaxArchiveFiles = 14, - ArchiveAboveSize = 30 * MegaByte - }; - - var ravenFileTarget = new FileTarget - { - ArchiveEvery = FileArchivePeriod.Day, - FileName = Path.Combine(loggingSettings.LogPath, "ravenlog.${shortdate}.txt"), - ArchiveFileName = Path.Combine(loggingSettings.LogPath, "ravenlog.{#}.txt"), - ArchiveNumbering = ArchiveNumberingMode.DateAndSequence, - Layout = simpleLayout, - MaxArchiveFiles = 14, - ArchiveAboveSize = 30 * MegaByte + ArchiveAboveSize = 30 * megaByte }; var consoleTarget = new ColoredConsoleTarget { + Name = "console", Layout = simpleLayout, + DetectConsoleAvailable = true, + DetectOutputRedirected = true, UseDefaultRowHighlightingRules = true }; - var nullTarget = new NullTarget(); - - nlogConfig.AddTarget("console", consoleTarget); - nlogConfig.AddTarget("debugger", fileTarget); - nlogConfig.AddTarget("raven", ravenFileTarget); - nlogConfig.AddTarget("null", nullTarget); - // Always want to see license logging regardless of default logging level nlogConfig.LoggingRules.Add(new LoggingRule("Particular.ServiceControl.Licensing.*", LogLevel.Info, fileTarget)); - nlogConfig.LoggingRules.Add(new LoggingRule("Particular.ServiceControl.Licensing.*", LogLevel.Info, consoleTarget) - { - Final = true - }); - + nlogConfig.LoggingRules.Add(new LoggingRule("Particular.ServiceControl.Licensing.*", LogLevel.Info, consoleTarget)); // Defaults - nlogConfig.LoggingRules.Add(new LoggingRule("*", loggingSettings.LoggingLevel, fileTarget)); - nlogConfig.LoggingRules.Add(new LoggingRule("*", loggingSettings.LoggingLevel < LogLevel.Info ? loggingSettings.LoggingLevel : LogLevel.Info, consoleTarget)); - - - if (!loggingSettings.LogToConsole) - { - foreach (var rule in nlogConfig.LoggingRules.Where(p => p.Targets.Contains(consoleTarget)).ToList()) - { - nlogConfig.LoggingRules.Remove(rule); - } - } + nlogConfig.LoggingRules.Add(new LoggingRule("*", loggingSettings.LogLevel, fileTarget)); + nlogConfig.LoggingRules.Add(new LoggingRule("*", loggingSettings.LogLevel < LogLevel.Info ? loggingSettings.LogLevel : LogLevel.Info, consoleTarget)); NLog.LogManager.Configuration = nlogConfig; LogManager.UseFactory(new ExtensionsLoggerFactory(new NLogLoggerFactory())); var logger = LogManager.GetLogger("LoggingConfiguration"); - var logEventInfo = new LogEventInfo - { - TimeStamp = DateTime.UtcNow - }; - logger.InfoFormat("Logging to {0} with LogLevel '{1}'", fileTarget.FileName.Render(logEventInfo), loggingSettings.LoggingLevel.Name); + var logEventInfo = new LogEventInfo { TimeStamp = DateTime.UtcNow }; + logger.InfoFormat("Logging to {0} with LogLevel '{1}'", fileTarget.FileName.Render(logEventInfo), loggingSettings.LogLevel.Name); } - const long MegaByte = 1024 * 1024; + const long megaByte = 1024 * 1024; } } \ No newline at end of file diff --git a/src/ServiceControl.Audit/Infrastructure/NServiceBusFactory.cs b/src/ServiceControl.Audit/Infrastructure/NServiceBusFactory.cs index 8bf8e234aa..f11c232ee8 100644 --- a/src/ServiceControl.Audit/Infrastructure/NServiceBusFactory.cs +++ b/src/ServiceControl.Audit/Infrastructure/NServiceBusFactory.cs @@ -9,13 +9,12 @@ namespace ServiceControl.Audit.Infrastructure using NServiceBus; using NServiceBus.Configuration.AdvancedExtensibility; using ServiceControl.Infrastructure; - using Settings; using Transports; static class NServiceBusFactory { public static void Configure(Settings.Settings settings, ITransportCustomization transportCustomization, - TransportSettings transportSettings, LoggingSettings loggingSettings, + TransportSettings transportSettings, Func onCriticalError, EndpointConfiguration configuration) { var endpointName = settings.ServiceName; @@ -47,8 +46,8 @@ static class NServiceBusFactory configuration.ReportCustomChecksTo(settings.ServiceControlQueueAddress); } - configuration.GetSettings().Set(loggingSettings); - configuration.SetDiagnosticsPath(loggingSettings.LogPath); + configuration.GetSettings().Set(settings.LoggingSettings); + configuration.SetDiagnosticsPath(settings.LoggingSettings.LogPath); configuration.UseSerialization(); diff --git a/src/ServiceControl.Audit/Infrastructure/Settings/LoggingSettings.cs b/src/ServiceControl.Audit/Infrastructure/Settings/LoggingSettings.cs index 4af1e31a60..e1f5c51d4c 100644 --- a/src/ServiceControl.Audit/Infrastructure/Settings/LoggingSettings.cs +++ b/src/ServiceControl.Audit/Infrastructure/Settings/LoggingSettings.cs @@ -2,29 +2,22 @@ namespace ServiceControl.Audit.Infrastructure.Settings { using System; using System.IO; - using System.Reflection; using Configuration; using NLog; using NLog.Common; - public class LoggingSettings + public class LoggingSettings(LogLevel defaultLevel = null, string logPath = null) { - public LoggingSettings(string serviceName, LogLevel defaultLevel = null, string logPath = null, bool logToConsole = true) - { - LoggingLevel = InitializeLevel("LogLevel", defaultLevel ?? LogLevel.Info); - LogPath = SettingsReader.Read(Settings.SettingsRootNamespace, "LogPath", Environment.ExpandEnvironmentVariables(logPath ?? DefaultLogLocation())); - LogToConsole = logToConsole; - } + public LogLevel LogLevel { get; } = InitializeLogLevel(defaultLevel); - public LogLevel LoggingLevel { get; } + public string LogPath { get; } = SettingsReader.Read(Settings.SettingsRootNamespace, "LogPath", Environment.ExpandEnvironmentVariables(logPath ?? DefaultLogLocation())); - public string LogPath { get; } + static LogLevel InitializeLogLevel(LogLevel defaultLevel) + { + defaultLevel ??= LogLevel.Info; - public bool LogToConsole { get; } + var levelText = SettingsReader.Read(Settings.SettingsRootNamespace, logLevelKey); - LogLevel InitializeLevel(string key, LogLevel defaultLevel) - { - var levelText = SettingsReader.Read(Settings.SettingsRootNamespace, key); if (string.IsNullOrWhiteSpace(levelText)) { return defaultLevel; @@ -36,47 +29,26 @@ LogLevel InitializeLevel(string key, LogLevel defaultLevel) } catch { - InternalLogger.Warn($"Failed to parse {key} setting. Defaulting to {defaultLevel.Name}."); + InternalLogger.Warn($"Failed to parse {logLevelKey} setting. Defaulting to {defaultLevel.Name}."); return 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() - { - var assemblyLocation = Assembly.GetExecutingAssembly().Location; - return Path.Combine(Path.GetDirectoryName(assemblyLocation), ".logs"); - } + static string DefaultLogLocation() => Path.Combine(AppContext.BaseDirectory, ".logs"); - public Microsoft.Extensions.Logging.LogLevel ToHostLogLevel() + public Microsoft.Extensions.Logging.LogLevel ToHostLogLevel() => LogLevel switch { - if (LoggingLevel == LogLevel.Debug) - { - return Microsoft.Extensions.Logging.LogLevel.Debug; - } - if (LoggingLevel == LogLevel.Error) - { - return Microsoft.Extensions.Logging.LogLevel.Error; - } - if (LoggingLevel == LogLevel.Fatal) - { - return Microsoft.Extensions.Logging.LogLevel.Critical; - } - if (LoggingLevel == LogLevel.Warn) - { - return Microsoft.Extensions.Logging.LogLevel.Warning; - } - if (LoggingLevel == LogLevel.Info) - { - return Microsoft.Extensions.Logging.LogLevel.Information; - } - if (LoggingLevel == LogLevel.Trace) - { - return Microsoft.Extensions.Logging.LogLevel.Trace; - } - - return Microsoft.Extensions.Logging.LogLevel.None; - } + _ 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 + }; + + const string logLevelKey = "LogLevel"; } } \ No newline at end of file diff --git a/src/ServiceControl.Audit/Infrastructure/Settings/Settings.cs b/src/ServiceControl.Audit/Infrastructure/Settings/Settings.cs index 9cffda9c3e..b79e582d66 100644 --- a/src/ServiceControl.Audit/Infrastructure/Settings/Settings.cs +++ b/src/ServiceControl.Audit/Infrastructure/Settings/Settings.cs @@ -10,17 +10,20 @@ public class Settings { - // Service name is what the user chose when installing the instance or is passing on the command line. - // We use this as the default endpoint name. - public static Settings FromConfiguration(string serviceName) => - new( - SettingsReader.Read(SettingsRootNamespace, "InternalQueueName", serviceName) // endpoint name can also be overriden via config - ); - - public Settings(string serviceName, string transportType = null, string persisterType = null) + public Settings(string serviceName = null, string transportType = null, string persisterType = null, LoggingSettings loggingSettings = null) { + LoggingSettings = loggingSettings ?? new(); + ServiceName = serviceName; + if (string.IsNullOrEmpty(serviceName)) + { + ServiceName = DEFAULT_SERVICE_NAME; + } + + // Overwrite the service name if it is specified in ENVVAR, reg, or config file + ServiceName = SettingsReader.Read(SettingsRootNamespace, "InternalQueueName", ServiceName); + TransportType = transportType ?? SettingsReader.Read(SettingsRootNamespace, "TransportType"); PersistenceType = persisterType ?? SettingsReader.Read(SettingsRootNamespace, "PersistenceType"); @@ -67,6 +70,8 @@ void LoadAuditQueueInformation() } } + public LoggingSettings LoggingSettings { get; } + //HINT: acceptance tests only public Func MessageFilter { get; set; } @@ -287,8 +292,11 @@ int GetDataSpaceRemainingThreshold() void TryLoadLicenseFromConfig() => LicenseFileText = SettingsReader.Read(SettingsRootNamespace, "LicenseText"); - ILog logger = LogManager.GetLogger(typeof(Settings)); + // logger is intentionally not static to prevent it from being initialized before LoggingConfigurator.ConfigureLogging has been called + readonly ILog logger = LogManager.GetLogger(typeof(Settings)); + int maxBodySizeToStore = SettingsReader.Read(SettingsRootNamespace, "MaxBodySizeToStore", MaxBodySizeToStoreDefault); + public const string DEFAULT_SERVICE_NAME = "Particular.ServiceControl.Audit"; public static readonly SettingsRootNamespace SettingsRootNamespace = new("ServiceControl.Audit"); diff --git a/src/ServiceControl.Audit/Infrastructure/WebApi/RootController.cs b/src/ServiceControl.Audit/Infrastructure/WebApi/RootController.cs index 770711b1e2..767e187e00 100644 --- a/src/ServiceControl.Audit/Infrastructure/WebApi/RootController.cs +++ b/src/ServiceControl.Audit/Infrastructure/WebApi/RootController.cs @@ -1,6 +1,5 @@ namespace ServiceControl.Audit.Infrastructure.WebApi { - using System; using Configuration; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; @@ -10,10 +9,9 @@ [Route("api")] public class RootController : ControllerBase { - public RootController(LoggingSettings loggingSettings, Settings settings) + public RootController(Settings settings) { this.settings = settings; - this.loggingSettings = loggingSettings; } [Route("")] @@ -52,8 +50,8 @@ public OkObjectResult Config() settings.ServiceName, Logging = new { - loggingSettings.LogPath, - LoggingLevel = loggingSettings.LoggingLevel.Name, + settings.LoggingSettings.LogPath, + LoggingLevel = settings.LoggingSettings.LogLevel.Name } }, DataRetention = new @@ -83,7 +81,6 @@ public OkObjectResult Config() return Ok(content); } - readonly LoggingSettings loggingSettings; readonly Settings settings; public class RootUrls diff --git a/src/ServiceControl.Audit/Program.cs b/src/ServiceControl.Audit/Program.cs index 90df7af3b6..31698b0565 100644 --- a/src/ServiceControl.Audit/Program.cs +++ b/src/ServiceControl.Audit/Program.cs @@ -9,7 +9,6 @@ using Infrastructure.Hosting; using Infrastructure.Hosting.Commands; using Infrastructure.Settings; - using Microsoft.Extensions.Hosting.WindowsServices; using NServiceBus.Logging; using ServiceControl.Audit.Persistence; using ServiceControl.Configuration; @@ -22,7 +21,7 @@ class Program static async Task Main(string[] args) { AssemblyLoadContext.Default.Resolving += ResolveAssembly; - AppDomain.CurrentDomain.UnhandledException += (s, e) => Logger.Error("Unhandled exception was caught.", e.ExceptionObject as Exception); + AppDomain.CurrentDomain.UnhandledException += (s, e) => LogManager.GetLogger(typeof(Program)).Error("Unhandled exception was caught.", e.ExceptionObject as Exception); ExeConfiguration.PopulateAppSettings(Assembly.GetExecutingAssembly()); @@ -34,10 +33,10 @@ static async Task Main(string[] args) return; } - var loggingSettings = new LoggingSettings(arguments.ServiceName, logToConsole: !WindowsServiceHelpers.IsWindowsService()); + var loggingSettings = new LoggingSettings(); LoggingConfigurator.ConfigureLogging(loggingSettings); - settings = Settings.FromConfiguration(arguments.ServiceName); + settings = new Settings(arguments.ServiceName, loggingSettings: loggingSettings); await new CommandRunner(arguments.Commands).Execute(arguments, settings); } @@ -75,7 +74,5 @@ static Assembly TryLoadAssembly(AssemblyLoadContext loadContext, string folderPa return null; } - - static readonly ILog Logger = LogManager.GetLogger(typeof(Program)); } } diff --git a/src/ServiceControl.Monitoring.AcceptanceTests/PerformanceTests.cs b/src/ServiceControl.Monitoring.AcceptanceTests/PerformanceTests.cs index e1a178b1a3..da241d643f 100644 --- a/src/ServiceControl.Monitoring.AcceptanceTests/PerformanceTests.cs +++ b/src/ServiceControl.Monitoring.AcceptanceTests/PerformanceTests.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.Dynamic; using System.Linq; - using System.Net.Http; using System.Threading; using System.Threading.Tasks; using HdrHistogram; diff --git a/src/ServiceControl.Monitoring.UnitTests/ApprovalFiles/SettingsTests.PlatformSampleSettings.approved.txt b/src/ServiceControl.Monitoring.UnitTests/ApprovalFiles/SettingsTests.PlatformSampleSettings.approved.txt index ffebf1d4d2..1cdd7259ab 100644 --- a/src/ServiceControl.Monitoring.UnitTests/ApprovalFiles/SettingsTests.PlatformSampleSettings.approved.txt +++ b/src/ServiceControl.Monitoring.UnitTests/ApprovalFiles/SettingsTests.PlatformSampleSettings.approved.txt @@ -1,15 +1,17 @@ { "EndpointName": "Particular.Monitoring", + "LoggingSettings": { + "LogLevel": { + "Name": "Info", + "Ordinal": 2 + }, + "LogPath": "C:\\Logs" + }, "Portable": false, "ServiceName": "Particular.Monitoring", "TransportType": "NServiceBus.ServiceControlLearningTransport, ServiceControl.Transports.LearningTransport", "ConnectionString": null, "ErrorQueue": "error", - "LogPath": "C:\\Logs", - "LogLevel": { - "Name": "Warn", - "Ordinal": 3 - }, "Username": null, "EnableInstallers": false, "HttpHostName": "localhost", diff --git a/src/ServiceControl.Monitoring.UnitTests/app.config b/src/ServiceControl.Monitoring.UnitTests/app.config index 58c2ee8213..ed4fa224e9 100644 --- a/src/ServiceControl.Monitoring.UnitTests/app.config +++ b/src/ServiceControl.Monitoring.UnitTests/app.config @@ -4,7 +4,6 @@ - \ No newline at end of file diff --git a/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs b/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs index 8c91ff96f6..c13bb689eb 100644 --- a/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs +++ b/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs @@ -33,9 +33,8 @@ public static class HostApplicationBuilderExtensions hostBuilder.Services.AddWindowsService(); hostBuilder.Logging.ClearProviders(); - //HINT: configuration used by NLog comes from MonitorLog.cs hostBuilder.Logging.AddNLog(); - hostBuilder.Logging.SetMinimumLevel(ToHostLogLevel(settings.LogLevel)); + hostBuilder.Logging.SetMinimumLevel(settings.LoggingSettings.ToHostLogLevel()); var services = hostBuilder.Services; services.AddSingleton(settings); @@ -91,7 +90,7 @@ internal static void ConfigureEndpoint(EndpointConfiguration config, Func(); @@ -131,39 +130,4 @@ static EndpointInputQueue ToQueueId(EndpointToQueueMapping endpointInputQueueDto static RawMessage.Entry ToEntry(QueueLengthEntry entryDto) => new RawMessage.Entry { DateTicks = entryDto.DateTicks, Value = entryDto.Value }; - - public static LogLevel ToHostLogLevel(NLog.LogLevel logLevel) - { - if (logLevel == NLog.LogLevel.Debug) - { - return LogLevel.Debug; - } - - if (logLevel == NLog.LogLevel.Error) - { - return LogLevel.Error; - } - - if (logLevel == NLog.LogLevel.Fatal) - { - return LogLevel.Critical; - } - - if (logLevel == NLog.LogLevel.Warn) - { - return LogLevel.Warning; - } - - if (logLevel == NLog.LogLevel.Info) - { - return LogLevel.Information; - } - - if (logLevel == NLog.LogLevel.Trace) - { - return LogLevel.Trace; - } - - return LogLevel.None; - } } \ No newline at end of file diff --git a/src/ServiceControl.Monitoring/Hosting/Commands/CommandRunner.cs b/src/ServiceControl.Monitoring/Hosting/Commands/CommandRunner.cs index 3d13530d10..eda0e9f620 100644 --- a/src/ServiceControl.Monitoring/Hosting/Commands/CommandRunner.cs +++ b/src/ServiceControl.Monitoring/Hosting/Commands/CommandRunner.cs @@ -11,7 +11,7 @@ public CommandRunner(List commands) this.commands = commands; } - public async Task Run(Settings settings) + public async Task Execute(Settings settings) { foreach (var commandType in commands) { diff --git a/src/ServiceControl.Monitoring/Hosting/Commands/SetupCommand.cs b/src/ServiceControl.Monitoring/Hosting/Commands/SetupCommand.cs index 9b49a38685..0d21dc42a3 100644 --- a/src/ServiceControl.Monitoring/Hosting/Commands/SetupCommand.cs +++ b/src/ServiceControl.Monitoring/Hosting/Commands/SetupCommand.cs @@ -34,7 +34,7 @@ public override Task Execute(Settings settings) return transportCustomization.ProvisionQueues(transportSettings, []); } - bool ValidateLicense(Monitoring.Settings settings) + bool ValidateLicense(Settings settings) { if (!string.IsNullOrWhiteSpace(settings.LicenseFileText)) { diff --git a/src/ServiceControl.Monitoring/Infrastructure/LoggingConfigurator.cs b/src/ServiceControl.Monitoring/Infrastructure/LoggingConfigurator.cs index bfe573bed1..46608e3697 100644 --- a/src/ServiceControl.Monitoring/Infrastructure/LoggingConfigurator.cs +++ b/src/ServiceControl.Monitoring/Infrastructure/LoggingConfigurator.cs @@ -1,12 +1,8 @@ namespace ServiceControl.Monitoring { using System; - using System.Diagnostics; using System.IO; - using System.Linq; - using Configuration; using NLog; - using NLog.Common; using NLog.Config; using NLog.Extensions.Logging; using NLog.Layouts; @@ -16,96 +12,54 @@ static class LoggingConfigurator { - public static void Configure(Settings settings, bool logToConsole) + public static void ConfigureLogging(LoggingSettings loggingSettings) { if (NLog.LogManager.Configuration != null) { return; } - var version = FileVersionInfo.GetVersionInfo(typeof(LoggingConfigurator).Assembly.Location).ProductVersion; var nlogConfig = new LoggingConfiguration(); var simpleLayout = new SimpleLayout("${longdate}|${threadid}|${level}|${logger}|${message}${onexception:|${exception:format=tostring}}"); - var header = $@"------------------------------------------------------------- -ServiceControl Monitoring Version: {version} -Selected Transport: {settings.TransportType} --------------------------------------------------------------"; var fileTarget = new FileTarget { + Name = "file", ArchiveEvery = FileArchivePeriod.Day, - FileName = Path.Combine(settings.LogPath, "logfile.${shortdate}.txt"), - ArchiveFileName = Path.Combine(settings.LogPath, "logfile.{#}.txt"), + FileName = Path.Combine(loggingSettings.LogPath, "logfile.${shortdate}.txt"), + ArchiveFileName = Path.Combine(loggingSettings.LogPath, "logfile.{#}.txt"), ArchiveNumbering = ArchiveNumberingMode.DateAndSequence, Layout = simpleLayout, MaxArchiveFiles = 14, - ArchiveAboveSize = 30 * MegaByte, - Header = new SimpleLayout(header) + ArchiveAboveSize = 30 * megaByte, }; var consoleTarget = new ColoredConsoleTarget { + Name = "console", Layout = simpleLayout, + DetectConsoleAvailable = true, + DetectOutputRedirected = true, UseDefaultRowHighlightingRules = true }; - var nullTarget = new NullTarget(); - - nlogConfig.AddTarget("console", consoleTarget); - nlogConfig.AddTarget("debugger", fileTarget); - nlogConfig.AddTarget("null", nullTarget); - - //Suppress NSB license logging since this will have it's own - nlogConfig.LoggingRules.Add(new LoggingRule("NServiceBus.LicenseManager", LogLevel.Info, nullTarget) { Final = true }); - // Always want to see license logging regardless of default logging level nlogConfig.LoggingRules.Add(new LoggingRule("ServiceControl.Monitoring.Licensing.*", LogLevel.Info, fileTarget)); - nlogConfig.LoggingRules.Add(new LoggingRule("ServiceControl.Monitoring.Licensing.*", LogLevel.Info, consoleTarget) - { - Final = true - }); + nlogConfig.LoggingRules.Add(new LoggingRule("ServiceControl.Monitoring.Licensing.*", LogLevel.Info, consoleTarget)); // Defaults - nlogConfig.LoggingRules.Add(new LoggingRule("*", settings.LogLevel, fileTarget)); - nlogConfig.LoggingRules.Add(new LoggingRule("*", settings.LogLevel < LogLevel.Info ? settings.LogLevel : LogLevel.Info, consoleTarget)); - - if (!logToConsole) - { - foreach (var rule in nlogConfig.LoggingRules.Where(p => p.Targets.Contains(consoleTarget)).ToList()) - { - nlogConfig.LoggingRules.Remove(rule); - } - } + nlogConfig.LoggingRules.Add(new LoggingRule("*", loggingSettings.LogLevel, fileTarget)); + nlogConfig.LoggingRules.Add(new LoggingRule("*", loggingSettings.LogLevel < LogLevel.Info ? loggingSettings.LogLevel : LogLevel.Info, consoleTarget)); NLog.LogManager.Configuration = nlogConfig; LogManager.UseFactory(new ExtensionsLoggerFactory(new NLogLoggerFactory())); var logger = LogManager.GetLogger("LoggingConfiguration"); - var logEventInfo = new LogEventInfo - { - TimeStamp = DateTime.UtcNow - }; - logger.InfoFormat("Logging to {0} with LogLevel '{1}'", fileTarget.FileName.Render(logEventInfo), settings.LogLevel.Name); + var logEventInfo = new LogEventInfo { TimeStamp = DateTime.UtcNow }; + logger.InfoFormat("Logging to {0} with LogLevel '{1}'", fileTarget.FileName.Render(logEventInfo), loggingSettings.LogLevel.Name); } - public static LogLevel InitializeLevel() - { - var level = LogLevel.Warn; - try - { - level = LogLevel.FromString(SettingsReader.Read(Settings.SettingsRootNamespace, LogLevelKey, LogLevel.Warn.Name)); - } - catch - { - InternalLogger.Warn($"Failed to parse {LogLevelKey} setting. Defaulting to Warn."); - } - - return level; - } - - const long MegaByte = 1024 * 1024; - - const string LogLevelKey = "LogLevel"; + const long megaByte = 1024 * 1024; } } \ No newline at end of file diff --git a/src/ServiceControl.Monitoring/Licensing/LicenseManager.cs b/src/ServiceControl.Monitoring/Licensing/LicenseManager.cs index 558e3b9fd6..680a6ea1a6 100644 --- a/src/ServiceControl.Monitoring/Licensing/LicenseManager.cs +++ b/src/ServiceControl.Monitoring/Licensing/LicenseManager.cs @@ -29,6 +29,6 @@ public void Refresh() Details = result.License; } - static readonly ILog Logger = LogManager.GetLogger(typeof(ActiveLicense)); + static readonly ILog Logger = LogManager.GetLogger(typeof(LicenseManager)); } } \ No newline at end of file diff --git a/src/ServiceControl.Monitoring/LoggingSettings.cs b/src/ServiceControl.Monitoring/LoggingSettings.cs new file mode 100644 index 0000000000..a230de9d06 --- /dev/null +++ b/src/ServiceControl.Monitoring/LoggingSettings.cs @@ -0,0 +1,54 @@ +namespace ServiceControl.Monitoring +{ + using System; + using System.IO; + using NLog; + using NLog.Common; + using ServiceControl.Configuration; + + public class LoggingSettings(LogLevel defaultLevel = null, string logPath = null) + { + public LogLevel LogLevel { get; } = InitializeLogLevel(defaultLevel); + + public string LogPath { get; } = SettingsReader.Read(Settings.SettingsRootNamespace, "LogPath", Environment.ExpandEnvironmentVariables(logPath ?? DefaultLogLocation())); + + static LogLevel InitializeLogLevel(LogLevel defaultLevel) + { + defaultLevel ??= LogLevel.Info; + + var levelText = SettingsReader.Read(Settings.SettingsRootNamespace, logLevelKey); + + if (string.IsNullOrWhiteSpace(levelText)) + { + return defaultLevel; + } + + try + { + return LogLevel.FromString(levelText); + } + catch + { + InternalLogger.Warn($"Failed to parse {logLevelKey} setting. Defaulting to {defaultLevel.Name}."); + return 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 + { + _ 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 + }; + + const string logLevelKey = "LogLevel"; + } +} diff --git a/src/ServiceControl.Monitoring/Program.cs b/src/ServiceControl.Monitoring/Program.cs index bddce791f3..761875972e 100644 --- a/src/ServiceControl.Monitoring/Program.cs +++ b/src/ServiceControl.Monitoring/Program.cs @@ -5,7 +5,6 @@ namespace ServiceControl.Monitoring using System.Reflection; using System.Runtime.Loader; using System.Threading.Tasks; - using Microsoft.Extensions.Hosting.WindowsServices; using NServiceBus.Logging; using ServiceControl.Configuration; using ServiceControl.Transports; @@ -17,25 +16,19 @@ static class Program static async Task Main(string[] args) { AssemblyLoadContext.Default.Resolving += ResolveAssembly; - AppDomain.CurrentDomain.UnhandledException += (s, e) => Logger.Error("Unhandled exception was caught.", e.ExceptionObject as Exception); + AppDomain.CurrentDomain.UnhandledException += (s, e) => LogManager.GetLogger(typeof(Program)).Error("Unhandled exception was caught.", e.ExceptionObject as Exception); ExeConfiguration.PopulateAppSettings(Assembly.GetExecutingAssembly()); var arguments = new HostArguments(args); - LoadSettings(arguments); + var loggingSettings = new LoggingSettings(); + LoggingConfigurator.ConfigureLogging(loggingSettings); - LoggingConfigurator.Configure(settings, !WindowsServiceHelpers.IsWindowsService()); + settings = new Settings(loggingSettings); + arguments.ApplyOverridesTo(settings); - await new CommandRunner(arguments.Commands) - .Run(settings); - } - - static void LoadSettings(HostArguments args) - { - var _settings = new Settings(); - args.ApplyOverridesTo(_settings); - settings = _settings; + await new CommandRunner(arguments.Commands).Execute(settings); } static Assembly ResolveAssembly(AssemblyLoadContext loadContext, AssemblyName assemblyName) @@ -65,7 +58,5 @@ static Assembly TryLoadAssembly(AssemblyLoadContext loadContext, string folderPa return null; } - - static readonly ILog Logger = LogManager.GetLogger(typeof(Program)); } } \ No newline at end of file diff --git a/src/ServiceControl.Monitoring/Settings.cs b/src/ServiceControl.Monitoring/Settings.cs index e2819417e5..3778297084 100644 --- a/src/ServiceControl.Monitoring/Settings.cs +++ b/src/ServiceControl.Monitoring/Settings.cs @@ -3,24 +3,21 @@ namespace ServiceControl.Monitoring using System; using System.Collections.Generic; using System.Configuration; - using System.IO; - using System.Reflection; using System.Threading.Tasks; using Configuration; - using NLog; using Transports; public class Settings { - public Settings() + public Settings(LoggingSettings loggingSettings = null) { + LoggingSettings = loggingSettings ?? new(); + TryLoadLicenseFromConfig(); TransportType = SettingsReader.Read(SettingsRootNamespace, "TransportType"); ConnectionString = GetConnectionString(); - LogLevel = LoggingConfigurator.InitializeLevel(); - LogPath = SettingsReader.Read(SettingsRootNamespace, "LogPath", DefaultLogLocation()); ErrorQueue = SettingsReader.Read(SettingsRootNamespace, "ErrorQueue", "error"); HttpHostName = SettingsReader.Read(SettingsRootNamespace, "HttpHostname"); HttpPort = SettingsReader.Read(SettingsRootNamespace, "HttpPort"); @@ -31,17 +28,16 @@ public Settings() public string EndpointName { - get { return endpointName ?? ServiceName; } - set { endpointName = value; } + get => endpointName ?? ServiceName; + set => endpointName = value; } + public LoggingSettings LoggingSettings { get; } public bool Portable { get; set; } = false; public string ServiceName { get; set; } = DEFAULT_ENDPOINT_NAME; public string TransportType { get; set; } public string ConnectionString { get; set; } public string ErrorQueue { get; set; } - public string LogPath { get; set; } - public LogLevel LogLevel { get; set; } public string Username { get; set; } public bool EnableInstallers { get; set; } public string HttpHostName { get; set; } @@ -52,14 +48,6 @@ public string EndpointName public int MaximumConcurrencyLevel { get; set; } public string LicenseFileText { get; set; } - // 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() - { - var assemblyLocation = Assembly.GetExecutingAssembly().Location; - return Path.Combine(Path.GetDirectoryName(assemblyLocation), ".logs"); - } - void TryLoadLicenseFromConfig() => LicenseFileText = SettingsReader.Read(SettingsRootNamespace, "LicenseText"); public ITransportCustomization LoadTransportCustomization() diff --git a/src/ServiceControl.Persistence.Tests/RetryStateTests.cs b/src/ServiceControl.Persistence.Tests/RetryStateTests.cs index ae7d721931..b867d22ce8 100644 --- a/src/ServiceControl.Persistence.Tests/RetryStateTests.cs +++ b/src/ServiceControl.Persistence.Tests/RetryStateTests.cs @@ -1,7 +1,6 @@ namespace ServiceControl.PersistenceTests { using System; - using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -277,7 +276,7 @@ class FakeApplicationLifetime : IHostApplicationLifetime class TestReturnToSenderDequeuer : ReturnToSenderDequeuer { public TestReturnToSenderDequeuer(ReturnToSender returnToSender, IErrorMessageDataStore store, IDomainEvents domainEvents, string endpointName) - : base(returnToSender, store, domainEvents, null, null, new Settings(endpointName)) + : base(returnToSender, store, domainEvents, null, null, new Settings(serviceName: endpointName)) { } diff --git a/src/ServiceControl.UnitTests/API/APIApprovals.cs b/src/ServiceControl.UnitTests/API/APIApprovals.cs index c4b20c7467..5f6360c861 100644 --- a/src/ServiceControl.UnitTests/API/APIApprovals.cs +++ b/src/ServiceControl.UnitTests/API/APIApprovals.cs @@ -30,7 +30,6 @@ public void RootPathValue() var controller = new RootController( new ActiveLicense { IsValid = true }, - new LoggingSettings("testEndpoint"), new Settings(), httpClientFactory: null ) diff --git a/src/ServiceControl.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt b/src/ServiceControl.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt index a345c39fa4..9db0b6043b 100644 --- a/src/ServiceControl.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt +++ b/src/ServiceControl.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt @@ -1,4 +1,11 @@ { + "LoggingSettings": { + "LogLevel": { + "Name": "Info", + "Ordinal": 2 + }, + "LogPath": "C:\\Logs" + }, "NotificationsFilter": null, "AllowMessageEditing": false, "MessageFilter": null, diff --git a/src/ServiceControl/HostApplicationBuilderExtensions.cs b/src/ServiceControl/HostApplicationBuilderExtensions.cs index 7cba64fd35..e4139bf50a 100644 --- a/src/ServiceControl/HostApplicationBuilderExtensions.cs +++ b/src/ServiceControl/HostApplicationBuilderExtensions.cs @@ -2,7 +2,6 @@ namespace Particular.ServiceControl { using System; using System.Diagnostics; - using System.Net; using System.Runtime.InteropServices; using global::ServiceControl.CustomChecks; using global::ServiceControl.ExternalIntegrations; @@ -32,11 +31,11 @@ namespace Particular.ServiceControl static class HostApplicationBuilderExtensions { - public static void AddServiceControl(this IHostApplicationBuilder hostBuilder, Settings settings, EndpointConfiguration configuration, LoggingSettings loggingSettings) + public static void AddServiceControl(this IHostApplicationBuilder hostBuilder, Settings settings, EndpointConfiguration configuration) { ArgumentNullException.ThrowIfNull(configuration); - RecordStartup(settings, loggingSettings, configuration); + RecordStartup(settings, configuration); if (!string.IsNullOrWhiteSpace(settings.LicenseFileText)) { @@ -55,7 +54,7 @@ public static void AddServiceControl(this IHostApplicationBuilder hostBuilder, S logging.ClearProviders(); //HINT: configuration used by NLog comes from LoggingConfigurator.cs logging.AddNLog(); - logging.SetMinimumLevel(loggingSettings.ToHostLogLevel()); + logging.SetMinimumLevel(settings.LoggingSettings.ToHostLogLevel()); var services = hostBuilder.Services; services.Configure(options => options.ShutdownTimeout = TimeSpan.FromSeconds(30)); @@ -64,7 +63,6 @@ public static void AddServiceControl(this IHostApplicationBuilder hostBuilder, S services.AddSingleton(transportCustomization); services.AddSingleton(); - services.AddSingleton(loggingSettings); services.AddSingleton(settings); services.AddHttpLogging(options => @@ -88,7 +86,7 @@ public static void AddServiceControl(this IHostApplicationBuilder hostBuilder, S services.AddPersistence(settings); services.AddMetrics(settings.PrintMetrics); - NServiceBusFactory.Configure(settings, transportCustomization, transportSettings, loggingSettings, configuration); + NServiceBusFactory.Configure(settings, transportCustomization, transportSettings, configuration); hostBuilder.UseNServiceBus(configuration); if (!settings.DisableExternalIntegrationsPublishing) @@ -132,7 +130,7 @@ static TransportSettings MapSettings(Settings settings) return transportSettings; } - static void RecordStartup(Settings settings, LoggingSettings loggingSettings, EndpointConfiguration endpointConfiguration) + static void RecordStartup(Settings settings, EndpointConfiguration endpointConfiguration) { var version = FileVersionInfo.GetVersionInfo(typeof(HostApplicationBuilderExtensions).Assembly.Location).ProductVersion; @@ -143,7 +141,7 @@ static void RecordStartup(Settings settings, LoggingSettings loggingSettings, En Error Retention Period: {settings.ErrorRetentionPeriod} Ingest Error Messages: {settings.IngestErrorMessages} Forwarding Error Messages: {settings.ForwardErrorMessages} -ServiceControl Logging Level: {loggingSettings.LoggingLevel} +ServiceControl Logging Level: {settings.LoggingSettings.LogLevel} Selected Transport Customization: {settings.TransportType} -------------------------------------------------------------"; @@ -152,7 +150,6 @@ static void RecordStartup(Settings settings, LoggingSettings loggingSettings, En endpointConfiguration.GetSettings().AddStartupDiagnosticsSection("Startup", new { Settings = settings, - LoggingSettings = loggingSettings }); } } diff --git a/src/ServiceControl/Hosting/Commands/ImportFailedErrorsCommand.cs b/src/ServiceControl/Hosting/Commands/ImportFailedErrorsCommand.cs index e27df6b8a5..55aa27aa8d 100644 --- a/src/ServiceControl/Hosting/Commands/ImportFailedErrorsCommand.cs +++ b/src/ServiceControl/Hosting/Commands/ImportFailedErrorsCommand.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; - using NLog; using NServiceBus; using Operations; using Particular.ServiceControl; @@ -22,10 +21,8 @@ public override async Task Execute(HostArguments args, Settings settings) EndpointConfiguration endpointConfiguration = CreateEndpointConfiguration(settings); - var loggingSettings = new LoggingSettings(settings.ServiceName, LogLevel.Info); - var hostBuilder = Host.CreateApplicationBuilder(); - hostBuilder.AddServiceControl(settings, endpointConfiguration, loggingSettings); + hostBuilder.AddServiceControl(settings, endpointConfiguration); using var app = hostBuilder.Build(); await app.StartAsync(); diff --git a/src/ServiceControl/Hosting/Commands/RunCommand.cs b/src/ServiceControl/Hosting/Commands/RunCommand.cs index 570c67383c..e53758a5dd 100644 --- a/src/ServiceControl/Hosting/Commands/RunCommand.cs +++ b/src/ServiceControl/Hosting/Commands/RunCommand.cs @@ -13,16 +13,14 @@ class RunCommand : AbstractCommand { public override async Task Execute(HostArguments args, Settings settings) { - var endpointConfiguration = new EndpointConfiguration(args.ServiceName); + var endpointConfiguration = new EndpointConfiguration(settings.ServiceName); var assemblyScanner = endpointConfiguration.AssemblyScanner(); assemblyScanner.ExcludeAssemblies("ServiceControl.Plugin"); settings.RunCleanupBundle = true; - var loggingSettings = new LoggingSettings(args.ServiceName); - var hostBuilder = WebApplication.CreateBuilder(); - hostBuilder.AddServiceControl(settings, endpointConfiguration, loggingSettings); + hostBuilder.AddServiceControl(settings, endpointConfiguration); hostBuilder.AddServiceControlApi(); var app = hostBuilder.Build(); diff --git a/src/ServiceControl/Hosting/Commands/SetupCommand.cs b/src/ServiceControl/Hosting/Commands/SetupCommand.cs index 54d5fd2586..346dc8e0c9 100644 --- a/src/ServiceControl/Hosting/Commands/SetupCommand.cs +++ b/src/ServiceControl/Hosting/Commands/SetupCommand.cs @@ -2,13 +2,13 @@ { using System.Runtime.InteropServices; using System.Threading.Tasks; + using LicenseManagement; using NServiceBus.Logging; using Particular.ServiceControl; using Particular.ServiceControl.Hosting; + using Persistence; using ServiceBus.Management.Infrastructure.Installers; using ServiceBus.Management.Infrastructure.Settings; - using LicenseManagement; - using Persistence; using Transports; class SetupCommand : AbstractCommand diff --git a/src/ServiceControl/Infrastructure/NServiceBusFactory.cs b/src/ServiceControl/Infrastructure/NServiceBusFactory.cs index 2203b27f4e..c0476c0563 100644 --- a/src/ServiceControl/Infrastructure/NServiceBusFactory.cs +++ b/src/ServiceControl/Infrastructure/NServiceBusFactory.cs @@ -9,12 +9,11 @@ namespace ServiceBus.Management.Infrastructure using ServiceControl.Notifications.Email; using ServiceControl.Operations; using ServiceControl.Transports; - using Settings; static class NServiceBusFactory { public static void Configure(Settings.Settings settings, ITransportCustomization transportCustomization, - TransportSettings transportSettings, LoggingSettings loggingSettings, EndpointConfiguration configuration) + TransportSettings transportSettings, EndpointConfiguration configuration) { if (configuration == null) { @@ -27,8 +26,8 @@ static class NServiceBusFactory transportCustomization.CustomizePrimaryEndpoint(configuration, transportSettings); - configuration.GetSettings().Set(loggingSettings); - configuration.SetDiagnosticsPath(loggingSettings.LogPath); + configuration.GetSettings().Set(settings.LoggingSettings); + configuration.SetDiagnosticsPath(settings.LoggingSettings.LogPath); if (settings.DisableExternalIntegrationsPublishing) { diff --git a/src/ServiceControl/Infrastructure/Settings/LoggingSettings.cs b/src/ServiceControl/Infrastructure/Settings/LoggingSettings.cs index 31ddb6ad2d..dc1b73e7e0 100644 --- a/src/ServiceControl/Infrastructure/Settings/LoggingSettings.cs +++ b/src/ServiceControl/Infrastructure/Settings/LoggingSettings.cs @@ -2,29 +2,22 @@ namespace ServiceBus.Management.Infrastructure.Settings { using System; using System.IO; - using System.Reflection; using NLog; using NLog.Common; using ServiceControl.Configuration; - public class LoggingSettings + public class LoggingSettings(LogLevel defaultLevel = null, string logPath = null) { - public LoggingSettings(string serviceName, LogLevel defaultLevel = null, string logPath = null, bool logToConsole = true) - { - LoggingLevel = InitializeLevel("LogLevel", defaultLevel ?? LogLevel.Info); - LogPath = SettingsReader.Read(Settings.SettingsRootNamespace, "LogPath", Environment.ExpandEnvironmentVariables(logPath ?? DefaultLogLocation())); - LogToConsole = logToConsole; - } + public LogLevel LogLevel { get; } = InitializeLogLevel(defaultLevel); - public LogLevel LoggingLevel { get; } + public string LogPath { get; } = SettingsReader.Read(Settings.SettingsRootNamespace, "LogPath", Environment.ExpandEnvironmentVariables(logPath ?? DefaultLogLocation())); - public string LogPath { get; } + static LogLevel InitializeLogLevel(LogLevel defaultLevel) + { + defaultLevel ??= LogLevel.Info; - public bool LogToConsole { get; } + var levelText = SettingsReader.Read(Settings.SettingsRootNamespace, logLevelKey); - LogLevel InitializeLevel(string key, LogLevel defaultLevel) - { - var levelText = SettingsReader.Read(Settings.SettingsRootNamespace, key); if (string.IsNullOrWhiteSpace(levelText)) { return defaultLevel; @@ -36,47 +29,26 @@ LogLevel InitializeLevel(string key, LogLevel defaultLevel) } catch { - InternalLogger.Warn($"Failed to parse {key} setting. Defaulting to {defaultLevel.Name}."); + InternalLogger.Warn($"Failed to parse {logLevelKey} setting. Defaulting to {defaultLevel.Name}."); return 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() - { - var assemblyLocation = Assembly.GetExecutingAssembly().Location; - return Path.Combine(Path.GetDirectoryName(assemblyLocation), ".logs"); - } + static string DefaultLogLocation() => Path.Combine(AppContext.BaseDirectory, ".logs"); - public Microsoft.Extensions.Logging.LogLevel ToHostLogLevel() + public Microsoft.Extensions.Logging.LogLevel ToHostLogLevel() => LogLevel switch { - if (LoggingLevel == LogLevel.Debug) - { - return Microsoft.Extensions.Logging.LogLevel.Debug; - } - if (LoggingLevel == LogLevel.Error) - { - return Microsoft.Extensions.Logging.LogLevel.Error; - } - if (LoggingLevel == LogLevel.Fatal) - { - return Microsoft.Extensions.Logging.LogLevel.Critical; - } - if (LoggingLevel == LogLevel.Warn) - { - return Microsoft.Extensions.Logging.LogLevel.Warning; - } - if (LoggingLevel == LogLevel.Info) - { - return Microsoft.Extensions.Logging.LogLevel.Information; - } - if (LoggingLevel == LogLevel.Trace) - { - return Microsoft.Extensions.Logging.LogLevel.Trace; - } - - return Microsoft.Extensions.Logging.LogLevel.None; - } + _ 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 + }; + + const string logLevelKey = "LogLevel"; } } \ No newline at end of file diff --git a/src/ServiceControl/Infrastructure/Settings/Settings.cs b/src/ServiceControl/Infrastructure/Settings/Settings.cs index adb3442378..a1489c7de3 100644 --- a/src/ServiceControl/Infrastructure/Settings/Settings.cs +++ b/src/ServiceControl/Infrastructure/Settings/Settings.cs @@ -20,10 +20,13 @@ public class Settings string serviceName = null, string transportType = null, string persisterType = null, + LoggingSettings loggingSettings = null, bool? forwardErrorMessages = default, TimeSpan? errorRetentionPeriod = default ) { + LoggingSettings = loggingSettings ?? new(); + ServiceName = serviceName; if (string.IsNullOrEmpty(serviceName)) @@ -57,6 +60,8 @@ public class Settings DisableExternalIntegrationsPublishing = SettingsReader.Read(SettingsRootNamespace, "DisableExternalIntegrationsPublishing", false); } + public LoggingSettings LoggingSettings { get; } + public string NotificationsFilter { get; set; } public bool AllowMessageEditing { get; set; } @@ -419,7 +424,9 @@ void LoadErrorIngestionSettings() void TryLoadLicenseFromConfig() => LicenseFileText = SettingsReader.Read(SettingsRootNamespace, "LicenseText"); - static readonly ILog logger = LogManager.GetLogger(typeof(Settings)); + // logger is intentionally not static to prevent it from being initialized before LoggingConfigurator.ConfigureLogging has been called + readonly ILog logger = LogManager.GetLogger(typeof(Settings)); + public const string DEFAULT_SERVICE_NAME = "Particular.ServiceControl"; public static readonly SettingsRootNamespace SettingsRootNamespace = new("ServiceControl"); diff --git a/src/ServiceControl/Infrastructure/WebApi/RootController.cs b/src/ServiceControl/Infrastructure/WebApi/RootController.cs index 137b65089e..69556f538d 100644 --- a/src/ServiceControl/Infrastructure/WebApi/RootController.cs +++ b/src/ServiceControl/Infrastructure/WebApi/RootController.cs @@ -6,7 +6,6 @@ using System.Text.Json.Nodes; using System.Threading.Tasks; using Configuration; - using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; using Particular.ServiceControl.Licensing; @@ -16,7 +15,6 @@ [Route("api")] public class RootController( ActiveLicense license, - LoggingSettings loggingSettings, Settings settings, IHttpClientFactory httpClientFactory) : ControllerBase @@ -67,8 +65,8 @@ public object Config() settings.ServiceName, Logging = new { - loggingSettings.LogPath, - LoggingLevel = loggingSettings.LoggingLevel.Name, + settings.LoggingSettings.LogPath, + LoggingLevel = settings.LoggingSettings.LogLevel.Name, } }, DataRetention = new diff --git a/src/ServiceControl/LoggingConfigurator.cs b/src/ServiceControl/LoggingConfigurator.cs index 59aee66cbb..37c6ab51bf 100644 --- a/src/ServiceControl/LoggingConfigurator.cs +++ b/src/ServiceControl/LoggingConfigurator.cs @@ -1,38 +1,32 @@ namespace Particular.ServiceControl { - using System.Diagnostics; + using System; using System.IO; - using System.Linq; + using NLog; using NLog.Config; + using NLog.Extensions.Logging; using NLog.Layouts; using NLog.Targets; using NServiceBus.Extensions.Logging; - using LogManager = NServiceBus.Logging.LogManager; - using LogLevel = NLog.LogLevel; - using NLog.Extensions.Logging; - using NLog; - using System; using ServiceBus.Management.Infrastructure.Settings; + using LogLevel = NLog.LogLevel; + using LogManager = NServiceBus.Logging.LogManager; static class LoggingConfigurator { public static void ConfigureLogging(LoggingSettings loggingSettings) { - const long megaByte = 1024 * 1024; if (NLog.LogManager.Configuration != null) { return; } - var version = FileVersionInfo.GetVersionInfo(typeof(LoggingConfigurator).Assembly.Location).ProductVersion; var nlogConfig = new LoggingConfiguration(); var simpleLayout = new SimpleLayout("${longdate}|${threadid}|${level}|${logger}|${message}${onexception:|${exception:format=tostring}}"); - var header = $@"------------------------------------------------------------- -ServiceControl Version: {version} --------------------------------------------------------------"; var fileTarget = new FileTarget { + Name = "file", ArchiveEvery = FileArchivePeriod.Day, FileName = Path.Combine(loggingSettings.LogPath, "logfile.${shortdate}.txt"), ArchiveFileName = Path.Combine(loggingSettings.LogPath, "logfile.{#}.txt"), @@ -42,60 +36,32 @@ public static void ConfigureLogging(LoggingSettings loggingSettings) ArchiveAboveSize = 30 * megaByte }; - var ravenFileTarget = new FileTarget - { - ArchiveEvery = FileArchivePeriod.Day, - FileName = Path.Combine(loggingSettings.LogPath, "ravenlog.${shortdate}.txt"), - ArchiveFileName = Path.Combine(loggingSettings.LogPath, "ravenlog.{#}.txt"), - ArchiveNumbering = ArchiveNumberingMode.DateAndSequence, - Layout = simpleLayout, - MaxArchiveFiles = 14, - ArchiveAboveSize = 30 * megaByte - }; - var consoleTarget = new ColoredConsoleTarget { + Name = "console", Layout = simpleLayout, + DetectConsoleAvailable = true, + DetectOutputRedirected = true, UseDefaultRowHighlightingRules = true }; - var nullTarget = new NullTarget(); - - // There lines don't appear to be necessary. The rules seem to work without implicitly adding the targets?!? - nlogConfig.AddTarget("console", consoleTarget); - nlogConfig.AddTarget("debugger", fileTarget); - nlogConfig.AddTarget("raven", ravenFileTarget); - nlogConfig.AddTarget("bitbucket", nullTarget); - // Always want to see license logging regardless of default logging level nlogConfig.LoggingRules.Add(new LoggingRule("Particular.ServiceControl.Licensing.*", LogLevel.Info, fileTarget)); - nlogConfig.LoggingRules.Add(new LoggingRule("Particular.ServiceControl.Licensing.*", LogLevel.Info, consoleTarget) - { - Final = true - }); + nlogConfig.LoggingRules.Add(new LoggingRule("Particular.ServiceControl.Licensing.*", LogLevel.Info, consoleTarget)); // Defaults - nlogConfig.LoggingRules.Add(new LoggingRule("*", loggingSettings.LoggingLevel, fileTarget)); - nlogConfig.LoggingRules.Add(new LoggingRule("*", loggingSettings.LoggingLevel < LogLevel.Info ? loggingSettings.LoggingLevel : LogLevel.Info, consoleTarget)); - - if (!loggingSettings.LogToConsole) - { - foreach (var rule in nlogConfig.LoggingRules.Where(p => p.Targets.Contains(consoleTarget)).ToList()) - { - nlogConfig.LoggingRules.Remove(rule); - } - } + nlogConfig.LoggingRules.Add(new LoggingRule("*", loggingSettings.LogLevel, fileTarget)); + nlogConfig.LoggingRules.Add(new LoggingRule("*", loggingSettings.LogLevel < LogLevel.Info ? loggingSettings.LogLevel : LogLevel.Info, consoleTarget)); NLog.LogManager.Configuration = nlogConfig; LogManager.UseFactory(new ExtensionsLoggerFactory(new NLogLoggerFactory())); var logger = LogManager.GetLogger("LoggingConfiguration"); - var logEventInfo = new LogEventInfo - { - TimeStamp = DateTime.UtcNow - }; - logger.InfoFormat("Logging to {0} with LogLevel '{1}'", fileTarget.FileName.Render(logEventInfo), loggingSettings.LoggingLevel.Name); + var logEventInfo = new LogEventInfo { TimeStamp = DateTime.UtcNow }; + logger.InfoFormat("Logging to {0} with LogLevel '{1}'", fileTarget.FileName.Render(logEventInfo), loggingSettings.LogLevel.Name); } + + const long megaByte = 1024 * 1024; } } \ No newline at end of file diff --git a/src/ServiceControl/Operations/ErrorIngestion.cs b/src/ServiceControl/Operations/ErrorIngestion.cs index 880f1bd8da..66ae2432ee 100644 --- a/src/ServiceControl/Operations/ErrorIngestion.cs +++ b/src/ServiceControl/Operations/ErrorIngestion.cs @@ -27,7 +27,6 @@ class ErrorIngestion : IHostedService TransportSettings transportSettings, Metrics metrics, IErrorMessageDataStore dataStore, - LoggingSettings loggingSettings, ErrorIngestionCustomCheck.State ingestionState, ErrorIngestor ingestor, IIngestionUnitOfWorkFactory unitOfWorkFactory, @@ -53,7 +52,7 @@ class ErrorIngestion : IHostedService FullMode = BoundedChannelFullMode.Wait }); - errorHandlingPolicy = new ErrorIngestionFaultPolicy(dataStore, loggingSettings, OnCriticalError); + errorHandlingPolicy = new ErrorIngestionFaultPolicy(dataStore, settings.LoggingSettings, OnCriticalError); watchdog = new Watchdog("failed message ingestion", EnsureStarted, EnsureStopped, ingestionState.ReportError, ingestionState.Clear, settings.TimeToRestartErrorIngestionAfterFailure, Logger); diff --git a/src/ServiceControl/Program.cs b/src/ServiceControl/Program.cs index 6ece6b1c0d..6a132acf73 100644 --- a/src/ServiceControl/Program.cs +++ b/src/ServiceControl/Program.cs @@ -10,7 +10,6 @@ using global::ServiceControl.Persistence; using global::ServiceControl.Transports; using Hosting; - using Microsoft.Extensions.Hosting.WindowsServices; using NServiceBus.Logging; using ServiceBus.Management.Infrastructure.Settings; @@ -21,7 +20,7 @@ class Program static async Task Main(string[] args) { AssemblyLoadContext.Default.Resolving += ResolveAssembly; - AppDomain.CurrentDomain.UnhandledException += (s, e) => Logger.Error("Unhandled exception was caught.", e.ExceptionObject as Exception); + AppDomain.CurrentDomain.UnhandledException += (s, e) => LogManager.GetLogger(typeof(Program)).Error("Unhandled exception was caught.", e.ExceptionObject as Exception); ExeConfiguration.PopulateAppSettings(Assembly.GetExecutingAssembly()); @@ -33,10 +32,10 @@ static async Task Main(string[] args) return; } - var loggingSettings = new LoggingSettings(arguments.ServiceName, logToConsole: !WindowsServiceHelpers.IsWindowsService()); + var loggingSettings = new LoggingSettings(); LoggingConfigurator.ConfigureLogging(loggingSettings); - settings = new Settings(arguments.ServiceName); + settings = new Settings(arguments.ServiceName, loggingSettings: loggingSettings); await new CommandRunner(arguments.Commands).Execute(arguments, settings); } @@ -74,7 +73,5 @@ static Assembly TryLoadAssembly(AssemblyLoadContext loadContext, string folderPa return null; } - - static readonly ILog Logger = LogManager.GetLogger(typeof(Program)); } } \ No newline at end of file