Skip to content

Commit d7bde06

Browse files
committed
Add a callback on the reader options to expose the log level switches
Fixes serilog#206
1 parent 0fc8b57 commit d7bde06

File tree

8 files changed

+98
-29
lines changed

8 files changed

+98
-29
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,22 @@ You can also declare `LoggingLevelSwitch`-es in custom section and reference the
186186

187187
Level updates to switches are also respected for a dynamic update.
188188

189+
Since version 4.0.0, both declared switches (i.e. `Serilog:LevelSwitches` section) and minimum level override switches (i.e. `Serilog:MinimumLevel:Override` section) are exposed through a callback on the reader options so that a reference can be kept:
190+
191+
```csharp
192+
var allSwitches = new Dictionary<string, LoggingLevelSwitch>();
193+
var options = new ConfigurationReaderOptions
194+
{
195+
OnAddedLevelSwitch = (switchName, logLevelSwitch) => allSwitches[switchName] = logLevelSwitch
196+
};
197+
198+
var logger = new LoggerConfiguration()
199+
.ReadFrom.Configuration(configuration, options)
200+
.CreateLogger();
201+
202+
LoggingLevelSwitch controlSwitch = allSwitches["$controlSwitch"];
203+
```
204+
189205
### WriteTo, Enrich, AuditTo, Destructure sections
190206

191207
These sections support simplified syntax, for example the following is valid if no arguments are needed by the sinks:

src/Serilog.Settings.Configuration/ConfigurationLoggerConfigurationExtensions.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ public static LoggerConfiguration ConfigurationSection(
100100
new ConfigurationReader(
101101
configSection,
102102
assemblyFinder,
103-
configuration: null,
104-
formatProvider: null));
103+
new ConfigurationReaderOptions { FormatProvider = null },
104+
configuration: null));
105105
}
106106

107107
/// <summary>
@@ -164,7 +164,7 @@ public static LoggerConfiguration ConfigurationSection(
164164

165165
var assemblyFinder = AssemblyFinder.ForSource(configurationAssemblySource);
166166

167-
return settingConfiguration.Settings(new ConfigurationReader(configSection, assemblyFinder, configuration: null, formatProvider: null));
167+
return settingConfiguration.Settings(new ConfigurationReader(configSection, assemblyFinder, new ConfigurationReaderOptions { FormatProvider = null }, configuration: null));
168168
}
169169

170170
/// <summary>
@@ -229,19 +229,19 @@ static ConfigurationReader GetConfigurationReader(IConfiguration configuration,
229229
{
230230
var assemblyFinder = dependencyContext == null ? AssemblyFinder.Auto() : AssemblyFinder.ForDependencyContext(dependencyContext);
231231
var section = configuration.GetSection(readerOptions.SectionName);
232-
return new ConfigurationReader(section, assemblyFinder, readerOptions.FormatProvider, configuration);
232+
return new ConfigurationReader(section, assemblyFinder, readerOptions, configuration);
233233
}
234234

235235
static ConfigurationReader GetConfigurationReader(IConfiguration configuration, ConfigurationReaderOptions readerOptions, ConfigurationAssemblySource source)
236236
{
237237
var assemblyFinder = AssemblyFinder.ForSource(source);
238238
var section = configuration.GetSection(readerOptions.SectionName);
239-
return new ConfigurationReader(section, assemblyFinder, readerOptions.FormatProvider, configuration);
239+
return new ConfigurationReader(section, assemblyFinder, readerOptions, configuration);
240240
}
241241

242242
static ConfigurationReader GetConfigurationReader(IConfiguration configuration, ConfigurationReaderOptions readerOptions, IReadOnlyCollection<Assembly> assemblies)
243243
{
244244
var section = configuration.GetSection(readerOptions.SectionName);
245-
return new ConfigurationReader(section, assemblies, new ResolutionContext(configuration, readerOptions.FormatProvider));
245+
return new ConfigurationReader(section, assemblies, new ResolutionContext(configuration, readerOptions));
246246
}
247247
}

src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ class ConfigurationReader : IConfigurationReader
2222
readonly ResolutionContext _resolutionContext;
2323
readonly IConfigurationRoot _configurationRoot;
2424

25-
public ConfigurationReader(IConfigurationSection configSection, AssemblyFinder assemblyFinder, IFormatProvider formatProvider, IConfiguration configuration = null)
25+
public ConfigurationReader(IConfigurationSection configSection, AssemblyFinder assemblyFinder, ConfigurationReaderOptions readerOptions, IConfiguration configuration = null)
2626
{
2727
_section = configSection ?? throw new ArgumentNullException(nameof(configSection));
2828
_configurationAssemblies = LoadConfigurationAssemblies(_section, assemblyFinder);
29-
_resolutionContext = new ResolutionContext(configuration, formatProvider);
29+
_resolutionContext = new ResolutionContext(configuration, readerOptions);
3030
_configurationRoot = configuration as IConfigurationRoot;
3131
}
3232

@@ -136,7 +136,8 @@ void ProcessLevelSwitchDeclarations()
136136
SubscribeToLoggingLevelChanges(levelSwitchDeclaration, newSwitch);
137137

138138
// make them available later on when resolving argument values
139-
_resolutionContext.AddLevelSwitch(switchName, newSwitch);
139+
var referenceName = _resolutionContext.AddLevelSwitch(switchName, newSwitch);
140+
_resolutionContext.ReaderOptions.OnAddedLevelSwitch?.Invoke(referenceName, newSwitch);
140141
}
141142
}
142143

@@ -164,7 +165,11 @@ void ApplyMinimumLevel(LoggerConfiguration loggerConfiguration)
164165
var overridenLevelOrSwitch = overrideDirective.Value;
165166
if (Enum.TryParse(overridenLevelOrSwitch, out LogEventLevel _))
166167
{
167-
ApplyMinimumLevelConfiguration(overrideDirective, (configuration, levelSwitch) => configuration.Override(overridePrefix, levelSwitch));
168+
ApplyMinimumLevelConfiguration(overrideDirective, (configuration, levelSwitch) =>
169+
{
170+
configuration.Override(overridePrefix, levelSwitch);
171+
_resolutionContext.ReaderOptions.OnAddedLevelSwitch?.Invoke(overridePrefix, levelSwitch);
172+
});
168173
}
169174
else
170175
{

src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReaderOptions.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Globalization;
22
using System.Reflection;
33
using Microsoft.Extensions.DependencyModel;
4+
using Serilog.Core;
45

56
namespace Serilog.Settings.Configuration;
67

@@ -56,6 +57,16 @@ public ConfigurationReaderOptions() : this(dependencyContext: null)
5657
/// </summary>
5758
public IFormatProvider FormatProvider { get; init; } = CultureInfo.InvariantCulture;
5859

60+
/// <summary>
61+
/// Called when a log level switch is created while reading the configuration.
62+
/// Log level switches are created either from the <c>Serilog:LevelSwitches</c> section (declared switches) or the <c>Serilog:MinimumLevel:Override</c> section (minimum level override switches).
63+
/// <list type="bullet">
64+
/// <item>For declared switches, the switch name includes the leading <c>$</c> character.</item>
65+
/// <item>For minimum level override switches, the switch name is the (partial) namespace or type name of the override.</item>
66+
/// </list>
67+
/// </summary>
68+
public Action<string, LoggingLevelSwitch> OnAddedLevelSwitch { get; init; }
69+
5970
internal Assembly[] Assemblies { get; }
6071
internal DependencyContext DependencyContext { get; }
6172
internal ConfigurationAssemblySource? ConfigurationAssemblySource { get; }

src/Serilog.Settings.Configuration/Settings/Configuration/ResolutionContext.cs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ sealed class ResolutionContext
1313
readonly IDictionary<string, LoggingFilterSwitchProxy> _declaredFilterSwitches;
1414
readonly IConfiguration _appConfiguration;
1515

16-
public ResolutionContext(IConfiguration appConfiguration = null, IFormatProvider formatProvider = null)
16+
public ResolutionContext(IConfiguration appConfiguration = null, ConfigurationReaderOptions readerOptions = null)
1717
{
1818
_declaredLevelSwitches = new Dictionary<string, LoggingLevelSwitch>();
1919
_declaredFilterSwitches = new Dictionary<string, LoggingFilterSwitchProxy>();
2020
_appConfiguration = appConfiguration;
21-
FormatProvider = formatProvider;
21+
ReaderOptions = readerOptions ?? new ConfigurationReaderOptions();
2222
}
2323

24-
public IFormatProvider FormatProvider { get; }
24+
public ConfigurationReaderOptions ReaderOptions { get; }
2525

2626
/// <summary>
2727
/// Looks up a switch in the declared LoggingLevelSwitches
@@ -64,18 +64,22 @@ public IConfiguration AppConfiguration
6464
}
6565
}
6666

67-
public void AddLevelSwitch(string levelSwitchName, LoggingLevelSwitch levelSwitch)
67+
public string AddLevelSwitch(string levelSwitchName, LoggingLevelSwitch levelSwitch)
6868
{
6969
if (levelSwitchName == null) throw new ArgumentNullException(nameof(levelSwitchName));
7070
if (levelSwitch == null) throw new ArgumentNullException(nameof(levelSwitch));
71-
_declaredLevelSwitches[ToSwitchReference(levelSwitchName)] = levelSwitch;
71+
var referenceName = ToSwitchReference(levelSwitchName);
72+
_declaredLevelSwitches[referenceName] = levelSwitch;
73+
return referenceName;
7274
}
7375

74-
public void AddFilterSwitch(string filterSwitchName, LoggingFilterSwitchProxy filterSwitch)
76+
public string AddFilterSwitch(string filterSwitchName, LoggingFilterSwitchProxy filterSwitch)
7577
{
7678
if (filterSwitchName == null) throw new ArgumentNullException(nameof(filterSwitchName));
7779
if (filterSwitch == null) throw new ArgumentNullException(nameof(filterSwitch));
78-
_declaredFilterSwitches[ToSwitchReference(filterSwitchName)] = filterSwitch;
80+
var referenceName = ToSwitchReference(filterSwitchName);
81+
_declaredFilterSwitches[referenceName] = filterSwitch;
82+
return referenceName;
7983
}
8084

8185
string ToSwitchReference(string switchName)

src/Serilog.Settings.Configuration/Settings/Configuration/StringArgumentValue.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public object ConvertTo(Type toType, ResolutionContext resolutionContext)
155155
}
156156
}
157157

158-
return Convert.ChangeType(argumentValue, toType, resolutionContext.FormatProvider);
158+
return Convert.ChangeType(argumentValue, toType, resolutionContext.ReaderOptions.FormatProvider);
159159
}
160160

161161
internal static Type FindType(string typeName)

test/Serilog.Settings.Configuration.Tests/ConfigurationReaderTests.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public ConfigurationReaderTests()
1818
_configurationReader = new ConfigurationReader(
1919
JsonStringConfigSource.LoadSection(@"{ 'Serilog': { } }", "Serilog"),
2020
AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies),
21-
CultureInfo.InvariantCulture);
21+
new ConfigurationReaderOptions());
2222
}
2323

2424
[Fact]
@@ -192,7 +192,7 @@ public void MethodsAreSelectedBasedOnCountOfMatchedArgumentsAndThenStringType()
192192
[MemberData(nameof(FlatMinimumLevel))]
193193
public void FlatMinimumLevelCorrectOneIsEnabledOnLogger(IConfigurationRoot root, LogEventLevel expectedMinimumLevel)
194194
{
195-
var reader = new ConfigurationReader(root.GetSection("Serilog"), AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), CultureInfo.InvariantCulture, root);
195+
var reader = new ConfigurationReader(root.GetSection("Serilog"), AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), new ConfigurationReaderOptions(), root);
196196
var loggerConfig = new LoggerConfiguration();
197197

198198
reader.Configure(loggerConfig);
@@ -216,7 +216,7 @@ public void FlatMinimumLevelCorrectOneIsEnabledOnLogger(IConfigurationRoot root,
216216
[MemberData(nameof(ObjectMinimumLevel))]
217217
public void ObjectMinimumLevelCorrectOneIsEnabledOnLogger(IConfigurationRoot root, LogEventLevel expectedMinimumLevel)
218218
{
219-
var reader = new ConfigurationReader(root.GetSection("Serilog"), AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), CultureInfo.InvariantCulture, root);
219+
var reader = new ConfigurationReader(root.GetSection("Serilog"), AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), new ConfigurationReaderOptions(), root);
220220
var loggerConfig = new LoggerConfiguration();
221221

222222
reader.Configure(loggerConfig);
@@ -258,7 +258,7 @@ public void ObjectMinimumLevelCorrectOneIsEnabledOnLogger(IConfigurationRoot roo
258258
[MemberData(nameof(MixedMinimumLevel))]
259259
public void MixedMinimumLevelCorrectOneIsEnabledOnLogger(IConfigurationRoot root, LogEventLevel expectedMinimumLevel)
260260
{
261-
var reader = new ConfigurationReader(root.GetSection("Serilog"), AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), CultureInfo.InvariantCulture, root);
261+
var reader = new ConfigurationReader(root.GetSection("Serilog"), AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), new ConfigurationReaderOptions(), root);
262262
var loggerConfig = new LoggerConfiguration();
263263

264264
reader.Configure(loggerConfig);
@@ -270,7 +270,7 @@ public void MixedMinimumLevelCorrectOneIsEnabledOnLogger(IConfigurationRoot root
270270
public void NoConfigurationRootUsedStillValid()
271271
{
272272
var section = JsonStringConfigSource.LoadSection(@"{ 'Nest': { 'Serilog': { 'MinimumLevel': 'Error' } } }", "Nest");
273-
var reader = new ConfigurationReader(section.GetSection("Serilog"), AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), CultureInfo.InvariantCulture, section);
273+
var reader = new ConfigurationReader(section.GetSection("Serilog"), AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), new ConfigurationReaderOptions(), section);
274274
var loggerConfig = new LoggerConfiguration();
275275

276276
reader.Configure(loggerConfig);

test/Serilog.Settings.Configuration.Tests/ConfigurationSettingsTests.cs

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,24 @@ namespace Serilog.Settings.Configuration.Tests;
1111

1212
public class ConfigurationSettingsTests
1313
{
14-
static LoggerConfiguration ConfigFromJson(string jsonString, string secondJsonSource = null)
14+
static LoggerConfiguration ConfigFromJson(string jsonString, string secondJsonSource = null, ConfigurationReaderOptions options = null)
1515
{
16-
return ConfigFromJson(jsonString, secondJsonSource, out _);
16+
return ConfigFromJson(jsonString, secondJsonSource, out _, options);
1717
}
1818

19-
static LoggerConfiguration ConfigFromJson(string jsonString, out IConfiguration configuration)
19+
static LoggerConfiguration ConfigFromJson(string jsonString, out IConfiguration configuration, ConfigurationReaderOptions options = null)
2020
{
21-
return ConfigFromJson(jsonString, null, out configuration);
21+
return ConfigFromJson(jsonString, null, out configuration, options);
2222
}
2323

24-
static LoggerConfiguration ConfigFromJson(string jsonString, string secondJsonSource, out IConfiguration configuration)
24+
static LoggerConfiguration ConfigFromJson(string jsonString, string secondJsonSource, out IConfiguration configuration, ConfigurationReaderOptions options)
2525
{
2626
var builder = new ConfigurationBuilder().AddJsonString(jsonString);
2727
if (secondJsonSource != null)
2828
builder.AddJsonString(secondJsonSource);
2929
configuration = builder.Build();
3030
return new LoggerConfiguration()
31-
.ReadFrom.Configuration(configuration);
31+
.ReadFrom.Configuration(configuration, options);
3232
}
3333

3434
[Fact]
@@ -1257,4 +1257,37 @@ public void FilterWithIsAppliedWithCustomFilter()
12571257
log.ForContext("User", "the user").Write(Some.InformationEvent());
12581258
Assert.NotNull(evt);
12591259
}
1260+
1261+
[Theory]
1262+
[InlineData("$switch1")]
1263+
[InlineData("switch1")]
1264+
public void TestLogLevelSwitchesCallback(string switchName)
1265+
{
1266+
var json = $@"{{
1267+
'Serilog': {{
1268+
'LevelSwitches': {{ '{switchName}': 'Information' }},
1269+
'MinimumLevel': {{
1270+
'Override': {{
1271+
'System': 'Warning',
1272+
'System.Threading': 'Debug'
1273+
}}
1274+
}}
1275+
}}
1276+
}}";
1277+
1278+
IDictionary<string, LoggingLevelSwitch> switches = new Dictionary<string, LoggingLevelSwitch>();
1279+
var readerOptions = new ConfigurationReaderOptions { OnAddedLevelSwitch = (name, levelSwitch) => switches[name] = levelSwitch };
1280+
ConfigFromJson(json, options: readerOptions);
1281+
1282+
Assert.Equal(3, switches.Count);
1283+
1284+
var switch1 = Assert.Contains("$switch1", switches);
1285+
Assert.Equal(LogEventLevel.Information, switch1.MinimumLevel);
1286+
1287+
var system = Assert.Contains("System", switches);
1288+
Assert.Equal(LogEventLevel.Warning, system.MinimumLevel);
1289+
1290+
var systemThreading = Assert.Contains("System.Threading", switches);
1291+
Assert.Equal(LogEventLevel.Debug, systemThreading.MinimumLevel);
1292+
}
12601293
}

0 commit comments

Comments
 (0)