Skip to content

Commit b483da5

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

File tree

8 files changed

+99
-30
lines changed

8 files changed

+99
-30
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+
OnLevelSwitchCreated = (switchName, levelSwitch) => allSwitches[switchName] = levelSwitch
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.OnLevelSwitchCreated?.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.OnLevelSwitchCreated?.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> OnLevelSwitchCreated { 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: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Globalization;
1+
using System.Globalization;
22
using System.Reflection;
33
using Microsoft.Extensions.Configuration;
44
using Serilog.Events;
@@ -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]
@@ -197,7 +197,7 @@ public void MethodsAreSelectedBasedOnCountOfMatchedArgumentsAndThenStringType()
197197
[MemberData(nameof(FlatMinimumLevel))]
198198
public void FlatMinimumLevelCorrectOneIsEnabledOnLogger(IConfigurationRoot root, LogEventLevel expectedMinimumLevel)
199199
{
200-
var reader = new ConfigurationReader(root.GetSection("Serilog"), AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), CultureInfo.InvariantCulture, root);
200+
var reader = new ConfigurationReader(root.GetSection("Serilog"), AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), new ConfigurationReaderOptions(), root);
201201
var loggerConfig = new LoggerConfiguration();
202202

203203
reader.Configure(loggerConfig);
@@ -221,7 +221,7 @@ public void FlatMinimumLevelCorrectOneIsEnabledOnLogger(IConfigurationRoot root,
221221
[MemberData(nameof(ObjectMinimumLevel))]
222222
public void ObjectMinimumLevelCorrectOneIsEnabledOnLogger(IConfigurationRoot root, LogEventLevel expectedMinimumLevel)
223223
{
224-
var reader = new ConfigurationReader(root.GetSection("Serilog"), AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), CultureInfo.InvariantCulture, root);
224+
var reader = new ConfigurationReader(root.GetSection("Serilog"), AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), new ConfigurationReaderOptions(), root);
225225
var loggerConfig = new LoggerConfiguration();
226226

227227
reader.Configure(loggerConfig);
@@ -263,7 +263,7 @@ public void ObjectMinimumLevelCorrectOneIsEnabledOnLogger(IConfigurationRoot roo
263263
[MemberData(nameof(MixedMinimumLevel))]
264264
public void MixedMinimumLevelCorrectOneIsEnabledOnLogger(IConfigurationRoot root, LogEventLevel expectedMinimumLevel)
265265
{
266-
var reader = new ConfigurationReader(root.GetSection("Serilog"), AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), CultureInfo.InvariantCulture, root);
266+
var reader = new ConfigurationReader(root.GetSection("Serilog"), AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), new ConfigurationReaderOptions(), root);
267267
var loggerConfig = new LoggerConfiguration();
268268

269269
reader.Configure(loggerConfig);
@@ -275,7 +275,7 @@ public void MixedMinimumLevelCorrectOneIsEnabledOnLogger(IConfigurationRoot root
275275
public void NoConfigurationRootUsedStillValid()
276276
{
277277
var section = JsonStringConfigSource.LoadSection("{ 'Nest': { 'Serilog': { 'MinimumLevel': 'Error' } } }", "Nest");
278-
var reader = new ConfigurationReader(section.GetSection("Serilog"), AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), CultureInfo.InvariantCulture, section);
278+
var reader = new ConfigurationReader(section.GetSection("Serilog"), AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), new ConfigurationReaderOptions(), section);
279279
var loggerConfig = new LoggerConfiguration();
280280

281281
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]
@@ -1345,4 +1345,37 @@ public void FilterWithIsAppliedWithCustomFilter()
13451345
log.ForContext("User", "the user").Write(Some.InformationEvent());
13461346
Assert.NotNull(evt);
13471347
}
1348+
1349+
[Theory]
1350+
[InlineData("$switch1")]
1351+
[InlineData("switch1")]
1352+
public void TestLogLevelSwitchesCallback(string switchName)
1353+
{
1354+
var json = $@"{{
1355+
'Serilog': {{
1356+
'LevelSwitches': {{ '{switchName}': 'Information' }},
1357+
'MinimumLevel': {{
1358+
'Override': {{
1359+
'System': 'Warning',
1360+
'System.Threading': 'Debug'
1361+
}}
1362+
}}
1363+
}}
1364+
}}";
1365+
1366+
IDictionary<string, LoggingLevelSwitch> switches = new Dictionary<string, LoggingLevelSwitch>();
1367+
var readerOptions = new ConfigurationReaderOptions { OnLevelSwitchCreated = (name, levelSwitch) => switches[name] = levelSwitch };
1368+
ConfigFromJson(json, options: readerOptions);
1369+
1370+
Assert.Equal(3, switches.Count);
1371+
1372+
var switch1 = Assert.Contains("$switch1", switches);
1373+
Assert.Equal(LogEventLevel.Information, switch1.MinimumLevel);
1374+
1375+
var system = Assert.Contains("System", switches);
1376+
Assert.Equal(LogEventLevel.Warning, system.MinimumLevel);
1377+
1378+
var systemThreading = Assert.Contains("System.Threading", switches);
1379+
Assert.Equal(LogEventLevel.Debug, systemThreading.MinimumLevel);
1380+
}
13481381
}

0 commit comments

Comments
 (0)