Skip to content

Commit 39a65cc

Browse files
committed
Refactor settings and logging configuration to use IOptions pattern, streamline logging initialization, and introduce post-configuration validation for ServiceBus and Primary options.
1 parent 5c537ff commit 39a65cc

18 files changed

+376
-322
lines changed

src/ServiceControl.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,19 @@ async Task InitializeServiceControl(ScenarioContext context)
5050
{
5151
var logPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
5252
Directory.CreateDirectory(logPath);
53-
var loggingSettings = new LoggingSettings
54-
{
55-
LogLevel = LogLevel.Debug,
56-
LogPath = logPath
57-
};
53+
var logLevel = LogLevel.Debug;
5854
LoggerUtil.ActiveLoggers = Loggers.Test;
5955

6056
PersistenceFactory.AssemblyLoadContextResolver = static _ => AssemblyLoadContext.Default;
6157

6258

6359
var settings = new Settings
6460
{
65-
Logging = loggingSettings,
61+
Logging =
62+
{
63+
LogLevel = logLevel.ToString(),
64+
LogPath = logPath
65+
},
6666
ServiceControl =
6767
{
6868
TransportType = transportToUse.TypeName,
@@ -80,7 +80,7 @@ async Task InitializeServiceControl(ScenarioContext context)
8080
{
8181
var headers = messageContext.Headers;
8282
var id = messageContext.NativeMessageId;
83-
var logger = LoggerUtil.CreateStaticLogger<ServiceControlComponentRunner>(loggingSettings.LogLevel);
83+
var logger = LoggerUtil.CreateStaticLogger<ServiceControlComponentRunner>(logLevel);
8484
headers.TryGetValue(Headers.MessageId, out var originalMessageId);
8585
logger.LogDebug("OnMessage for message '{MessageId}'({OriginalMessageId})", id, originalMessageId ?? string.Empty);
8686

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
namespace ServiceControl.Infrastructure;
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.IO;
6+
using Microsoft.Extensions.Logging;
7+
8+
public static class LoggerOptionsExtensions
9+
{
10+
public static LoggingSettings ToLoggingSettings(this LoggingOptions src)
11+
{
12+
LoggingSettings dst = new();
13+
14+
var activeLoggers = Loggers.None;
15+
if (src.LoggingProviders.Contains("NLog"))
16+
{
17+
activeLoggers |= Loggers.NLog;
18+
}
19+
20+
if (src.LoggingProviders.Contains("Seq"))
21+
{
22+
activeLoggers |= Loggers.Seq;
23+
if (!string.IsNullOrWhiteSpace(src.SeqAddress))
24+
{
25+
LoggerUtil.SeqAddress = src.SeqAddress;
26+
}
27+
}
28+
29+
if (src.LoggingProviders.Contains("Otlp"))
30+
{
31+
activeLoggers |= Loggers.Otlp;
32+
}
33+
34+
//this defaults to NLog because historically that was the default, and we don't want to break existing installs that don't have the config key to define loggingProviders
35+
LoggerUtil.ActiveLoggers = activeLoggers == Loggers.None ? Loggers.NLog : activeLoggers;
36+
37+
dst.LogLevel = InitializeLogLevel(src.LogLevel, dst.LogLevel);
38+
dst.LogPath = Environment.ExpandEnvironmentVariables(src.LogPath ?? DefaultLogLocation());
39+
40+
static LogLevel InitializeLogLevel(string levelText, LogLevel defaultLevel)
41+
{
42+
if (string.IsNullOrWhiteSpace(levelText))
43+
{
44+
return defaultLevel;
45+
}
46+
47+
return ParseLogLevel(levelText, defaultLevel);
48+
}
49+
50+
return dst;
51+
}
52+
53+
// SC installer always populates LogPath in app.config on installation/change/upgrade so this will only be used when
54+
// debugging or if the entry is removed manually. In those circumstances default to the folder containing the exe
55+
static string DefaultLogLocation() => Path.Combine(AppContext.BaseDirectory, ".logs");
56+
57+
// This is not a complete mapping of NLog levels, just the ones that are different.
58+
static readonly Dictionary<string, LogLevel> NLogAliases = new(StringComparer.OrdinalIgnoreCase)
59+
{
60+
["info"] = LogLevel.Information,
61+
["warn"] = LogLevel.Warning,
62+
["fatal"] = LogLevel.Critical,
63+
["off"] = LogLevel.None
64+
};
65+
66+
static LogLevel ParseLogLevel(string value, LogLevel defaultLevel)
67+
{
68+
if (Enum.TryParse(value, ignoreCase: true, out LogLevel parsedLevel))
69+
{
70+
return parsedLevel;
71+
}
72+
73+
if (NLogAliases.TryGetValue(value.Trim(), out parsedLevel))
74+
{
75+
return parsedLevel;
76+
}
77+
78+
LoggerUtil.CreateStaticLogger<LoggingSettings>().LogWarning("Failed to parse {LogLevelKey} setting. Defaulting to {DefaultLevel}", nameof(LoggingOptions.LogLevel), defaultLevel);
79+
80+
return defaultLevel;
81+
}
82+
}

src/ServiceControl.Infrastructure/LoggingConfigurator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public static class LoggingConfigurator
1616
{
1717
public static void ConfigureLogging(LoggingOptions options)
1818
{
19-
var loggingSettings = LoggingOptionsToSettings.Map(options);
19+
var loggingSettings = options.ToLoggingSettings();
2020

2121
//used for loggers outside of ServiceControl (i.e. transports and core) to use the logger factory defined here
2222
LogManager.UseFactory(new ExtensionsLoggerFactory(LoggerFactory.Create(configure => configure.ConfigureLogging(loggingSettings.LogLevel))));

src/ServiceControl.Infrastructure/LoggingSettings.cs

Lines changed: 1 addition & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,15 @@
11
namespace ServiceControl.Infrastructure;
22

3-
using System;
4-
using System.Collections.Generic;
5-
using System.IO;
6-
using Microsoft.Extensions.Configuration;
73
using Microsoft.Extensions.Logging;
8-
using Microsoft.Extensions.Options;
94

105
public record LoggingOptions
116
{
12-
public string LogLevel { get; set; }
7+
public string LogLevel { get; set; } = "Information";
138
public string LogPath { get; set; }
149
public string LoggingProviders { get; set; }
1510
public string SeqAddress { get; set; }
1611
}
1712

18-
public static class LoggingOptionsToSettings
19-
{
20-
public static LoggingSettings Map(LoggingOptions src)
21-
{
22-
LoggingSettings dst = new();
23-
24-
var activeLoggers = Loggers.None;
25-
if (src.LoggingProviders.Contains("NLog"))
26-
{
27-
activeLoggers |= Loggers.NLog;
28-
}
29-
30-
if (src.LoggingProviders.Contains("Seq"))
31-
{
32-
activeLoggers |= Loggers.Seq;
33-
if (!string.IsNullOrWhiteSpace(src.SeqAddress))
34-
{
35-
LoggerUtil.SeqAddress = src.SeqAddress;
36-
}
37-
}
38-
39-
if (src.LoggingProviders.Contains("Otlp"))
40-
{
41-
activeLoggers |= Loggers.Otlp;
42-
}
43-
44-
//this defaults to NLog because historically that was the default, and we don't want to break existing installs that don't have the config key to define loggingProviders
45-
LoggerUtil.ActiveLoggers = activeLoggers == Loggers.None ? Loggers.NLog : activeLoggers;
46-
47-
dst.LogLevel = InitializeLogLevel(src.LogLevel, dst.LogLevel);
48-
dst.LogPath = Environment.ExpandEnvironmentVariables(src.LogPath ?? DefaultLogLocation());
49-
50-
static LogLevel InitializeLogLevel(string levelText, LogLevel defaultLevel)
51-
{
52-
if (string.IsNullOrWhiteSpace(levelText))
53-
{
54-
return defaultLevel;
55-
}
56-
57-
return ParseLogLevel(levelText, defaultLevel);
58-
}
59-
60-
return dst;
61-
}
62-
63-
// SC installer always populates LogPath in app.config on installation/change/upgrade so this will only be used when
64-
// debugging or if the entry is removed manually. In those circumstances default to the folder containing the exe
65-
static string DefaultLogLocation() => Path.Combine(AppContext.BaseDirectory, ".logs");
66-
67-
// This is not a complete mapping of NLog levels, just the ones that are different.
68-
static readonly Dictionary<string, LogLevel> NLogAliases = new(StringComparer.OrdinalIgnoreCase)
69-
{
70-
["info"] = LogLevel.Information,
71-
["warn"] = LogLevel.Warning,
72-
["fatal"] = LogLevel.Critical,
73-
["off"] = LogLevel.None
74-
};
75-
76-
static LogLevel ParseLogLevel(string value, LogLevel defaultLevel)
77-
{
78-
if (Enum.TryParse(value, ignoreCase: true, out LogLevel parsedLevel))
79-
{
80-
return parsedLevel;
81-
}
82-
83-
if (NLogAliases.TryGetValue(value.Trim(), out parsedLevel))
84-
{
85-
return parsedLevel;
86-
}
87-
88-
LoggerUtil.CreateStaticLogger<LoggingSettings>().LogWarning("Failed to parse {LogLevelKey} setting. Defaulting to {DefaultLevel}", nameof(LoggingOptions.LogLevel), defaultLevel);
89-
90-
return defaultLevel;
91-
}
92-
}
93-
94-
9513
public class LoggingSettings // TODO: Register
9614
{
9715
public LogLevel LogLevel { get; set; } = LogLevel.Information;

src/ServiceControl/HostApplicationBuilderExtensions.cs

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ namespace Particular.ServiceControl
33
using System;
44
using System.Diagnostics;
55
using System.Runtime.InteropServices;
6+
using System.Threading;
7+
using System.Threading.Tasks;
68
using global::ServiceControl.CustomChecks;
79
using global::ServiceControl.ExternalIntegrations;
810
using global::ServiceControl.Hosting;
@@ -17,13 +19,16 @@ namespace Particular.ServiceControl
1719
using global::ServiceControl.Transports;
1820
using Licensing;
1921
using Microsoft.AspNetCore.HttpLogging;
22+
using Microsoft.Extensions.Configuration;
2023
using Microsoft.Extensions.DependencyInjection;
2124
using Microsoft.Extensions.Hosting;
2225
using Microsoft.Extensions.Hosting.WindowsServices;
2326
using Microsoft.Extensions.Logging;
27+
using Microsoft.Extensions.Options;
2428
using NServiceBus;
2529
using NServiceBus.Configuration.AdvancedExtensibility;
2630
using NServiceBus.Transport;
31+
using NuGet.Versioning;
2732
using ServiceBus.Management.Infrastructure;
2833
using ServiceBus.Management.Infrastructure.Installers;
2934
using ServiceBus.Management.Infrastructure.Settings;
@@ -33,20 +38,19 @@ static class HostApplicationBuilderExtensions
3338
public static void AddServiceControl(
3439
this IHostApplicationBuilder hostBuilder,
3540
Settings settings,
36-
EndpointConfiguration configuration
41+
EndpointConfiguration endpointConfiguration
3742
)
3843
{
39-
ArgumentNullException.ThrowIfNull(configuration);
4044

41-
RecordStartup(settings, configuration);
45+
AddVersion(hostBuilder);
4246

4347
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Environment.UserInteractive && Debugger.IsAttached)
4448
{
4549
EventSourceCreator.Create();
4650
}
4751

4852
hostBuilder.Logging.ClearProviders();
49-
hostBuilder.Logging.ConfigureLogging(settings.Logging.LogLevel);
53+
hostBuilder.Logging.ConfigureLogging(settings.Logging.ToLoggingSettings().LogLevel);
5054

5155
var services = hostBuilder.Services;
5256
var transportSettings = settings.ServiceControl.ToTransportSettings();
@@ -82,8 +86,12 @@ EndpointConfiguration configuration
8286

8387
services.AddMetrics(settings.ServiceControl.PrintMetrics);
8488

85-
NServiceBusFactory.Configure(settings, transportCustomization, transportSettings, configuration);
86-
hostBuilder.UseNServiceBus(configuration);
89+
NServiceBusFactory.Configure(settings, transportCustomization, transportSettings, endpointConfiguration);
90+
endpointConfiguration.GetSettings().AddStartupDiagnosticsSection("Startup", new
91+
{
92+
Settings = settings,
93+
});
94+
hostBuilder.UseNServiceBus(endpointConfiguration);
8795

8896
if (!settings.ServiceControl.DisableExternalIntegrationsPublishing)
8997
{
@@ -108,35 +116,41 @@ EndpointConfiguration configuration
108116
hostBuilder.AddServiceControlComponents(settings, transportCustomization, ServiceControlMainInstance.Components);
109117
}
110118

119+
static void AddVersion(IHostApplicationBuilder hostBuilder) => hostBuilder.Services.AddSingleton(NuGetVersion.Parse(FileVersionInfo.GetVersionInfo(typeof(HostApplicationBuilderExtensions).Assembly.Location).ProductVersion));
120+
111121
public static void AddServiceControlInstallers(this IHostApplicationBuilder hostApplicationBuilder)
112122
{
113123
var persistence = PersistenceFactory.Create(hostApplicationBuilder.Configuration);
114124
persistence.AddInstaller(hostApplicationBuilder.Services);
115125
}
126+
}
116127

117-
// TODO: Move to start
118-
static void RecordStartup(Settings settings, EndpointConfiguration endpointConfiguration)
128+
public class RecordStartup(
129+
ILogger<RecordStartup> logger,
130+
IOptions<PrimaryOptions> primaryOptions,
131+
IOptions<LoggingOptions> loggingOptions,
132+
NuGetVersion version
133+
) : BackgroundService
134+
{
135+
protected override Task ExecuteAsync(CancellationToken stoppingToken)
119136
{
120-
var version = FileVersionInfo.GetVersionInfo(typeof(HostApplicationBuilderExtensions).Assembly.Location).ProductVersion;
137+
var primary = primaryOptions.Value;
138+
var logging = loggingOptions.Value;
121139

122140
var startupMessage = $"""
123141
-------------------------------------------------------------
124142
ServiceControl Version: {version}
125-
Audit Retention Period (optional): {settings.ServiceControl.AuditRetentionPeriod}
126-
Error Retention Period: {settings.ServiceControl.ErrorRetentionPeriod}
127-
Ingest Error Messages: {settings.ServiceControl.IngestErrorMessages}
128-
Forwarding Error Messages: {settings.ServiceControl.ForwardErrorMessages}
129-
ServiceControl Logging Level: {settings.Logging.LogLevel}
130-
Selected Transport Customization: {settings.ServiceControl.TransportType}
143+
Audit Retention Period (optional): {primary.AuditRetentionPeriod}
144+
Error Retention Period: {primary.ErrorRetentionPeriod}
145+
Ingest Error Messages: {primary.IngestErrorMessages}
146+
Forwarding Error Messages: {primary.ForwardErrorMessages}
147+
ServiceControl Logging Level: {logging.LogLevel}
148+
Selected Transport Customization: {primary.TransportType}
131149
------------------------------------------------------------
132150
""";
133151

134-
var logger = LoggerUtil.CreateStaticLogger(typeof(HostApplicationBuilderExtensions), settings.Logging.LogLevel);
135152
logger.LogInformation(startupMessage);
136-
endpointConfiguration.GetSettings().AddStartupDiagnosticsSection("Startup", new
137-
{
138-
Settings = settings,
139-
});
153+
return Task.CompletedTask;
140154
}
141155
}
142156
}

src/ServiceControl/Hosting/Commands/ImportFailedErrorsCommand.cs

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
using Microsoft.Extensions.DependencyInjection;
99
using Microsoft.Extensions.Hosting;
1010
using Microsoft.Extensions.Logging;
11-
using Microsoft.Extensions.Options;
1211
using NServiceBus;
1312
using Operations;
1413
using Particular.ServiceControl;
@@ -57,7 +56,6 @@ public override async Task Execute(HostArguments args)
5756
}
5857
}
5958

60-
6159
protected virtual EndpointConfiguration CreateEndpointConfiguration(Settings settings)
6260
{
6361
var endpointConfiguration = new EndpointConfiguration(settings.ServiceControl.InstanceName);
@@ -67,25 +65,4 @@ protected virtual EndpointConfiguration CreateEndpointConfiguration(Settings set
6765
return endpointConfiguration;
6866
}
6967
}
70-
71-
public static class HostBuilderExt
72-
{
73-
public static void SetupApplicationConfiguration(this IHostApplicationBuilder hostBuilder)
74-
{
75-
hostBuilder.Configuration
76-
.SetBasePath(AppContext.BaseDirectory)
77-
.AddLegacyAppSettings()
78-
.AddEnvironmentVariables();
79-
80-
// TODO: Add LoggingOptions/Settings
81-
hostBuilder.Services.AddOptions<PrimaryOptions>()
82-
.Bind(hostBuilder.Configuration.GetSection(PrimaryOptions.SectionName))
83-
.ValidateDataAnnotations()
84-
.ValidateOnStart();
85-
hostBuilder.Services.AddOptions<ServiceBusOptions>()
86-
.Bind(hostBuilder.Configuration.GetSection(ServiceBusOptions.SectionName))
87-
.ValidateDataAnnotations()
88-
.ValidateOnStart();
89-
}
90-
}
9168
}

0 commit comments

Comments
 (0)