Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a callback on the reader options to expose the log level switches #352

Merged
merged 1 commit into from
Mar 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,22 @@ You can also declare `LoggingLevelSwitch`-es in custom section and reference the

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

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:

```csharp
var allSwitches = new Dictionary<string, LoggingLevelSwitch>();
var options = new ConfigurationReaderOptions
{
OnLevelSwitchCreated = (switchName, levelSwitch) => allSwitches[switchName] = levelSwitch
};

var logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration, options)
.CreateLogger();

LoggingLevelSwitch controlSwitch = allSwitches["$controlSwitch"];
```

### WriteTo, Enrich, AuditTo, Destructure sections

These sections support simplified syntax, for example the following is valid if no arguments are needed by the sinks:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ public static LoggerConfiguration ConfigurationSection(
new ConfigurationReader(
configSection,
assemblyFinder,
configuration: null,
formatProvider: null));
new ConfigurationReaderOptions { FormatProvider = null },
configuration: null));
}

/// <summary>
Expand Down Expand Up @@ -164,7 +164,7 @@ public static LoggerConfiguration ConfigurationSection(

var assemblyFinder = AssemblyFinder.ForSource(configurationAssemblySource);

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

/// <summary>
Expand Down Expand Up @@ -229,19 +229,19 @@ static ConfigurationReader GetConfigurationReader(IConfiguration configuration,
{
var assemblyFinder = dependencyContext == null ? AssemblyFinder.Auto() : AssemblyFinder.ForDependencyContext(dependencyContext);
var section = configuration.GetSection(readerOptions.SectionName);
return new ConfigurationReader(section, assemblyFinder, readerOptions.FormatProvider, configuration);
return new ConfigurationReader(section, assemblyFinder, readerOptions, configuration);
}

static ConfigurationReader GetConfigurationReader(IConfiguration configuration, ConfigurationReaderOptions readerOptions, ConfigurationAssemblySource source)
{
var assemblyFinder = AssemblyFinder.ForSource(source);
var section = configuration.GetSection(readerOptions.SectionName);
return new ConfigurationReader(section, assemblyFinder, readerOptions.FormatProvider, configuration);
return new ConfigurationReader(section, assemblyFinder, readerOptions, configuration);
}

static ConfigurationReader GetConfigurationReader(IConfiguration configuration, ConfigurationReaderOptions readerOptions, IReadOnlyCollection<Assembly> assemblies)
{
var section = configuration.GetSection(readerOptions.SectionName);
return new ConfigurationReader(section, assemblies, new ResolutionContext(configuration, readerOptions.FormatProvider));
return new ConfigurationReader(section, assemblies, new ResolutionContext(configuration, readerOptions));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ class ConfigurationReader : IConfigurationReader
readonly ResolutionContext _resolutionContext;
readonly IConfigurationRoot _configurationRoot;

public ConfigurationReader(IConfigurationSection configSection, AssemblyFinder assemblyFinder, IFormatProvider formatProvider, IConfiguration configuration = null)
public ConfigurationReader(IConfigurationSection configSection, AssemblyFinder assemblyFinder, ConfigurationReaderOptions readerOptions, IConfiguration configuration = null)
{
_section = configSection ?? throw new ArgumentNullException(nameof(configSection));
_configurationAssemblies = LoadConfigurationAssemblies(_section, assemblyFinder);
_resolutionContext = new ResolutionContext(configuration, formatProvider);
_resolutionContext = new ResolutionContext(configuration, readerOptions);
_configurationRoot = configuration as IConfigurationRoot;
}

Expand Down Expand Up @@ -136,7 +136,8 @@ void ProcessLevelSwitchDeclarations()
SubscribeToLoggingLevelChanges(levelSwitchDeclaration, newSwitch);

// make them available later on when resolving argument values
_resolutionContext.AddLevelSwitch(switchName, newSwitch);
var referenceName = _resolutionContext.AddLevelSwitch(switchName, newSwitch);
_resolutionContext.ReaderOptions.OnLevelSwitchCreated?.Invoke(referenceName, newSwitch);
}
}

Expand Down Expand Up @@ -164,7 +165,11 @@ void ApplyMinimumLevel(LoggerConfiguration loggerConfiguration)
var overridenLevelOrSwitch = overrideDirective.Value;
if (Enum.TryParse(overridenLevelOrSwitch, out LogEventLevel _))
{
ApplyMinimumLevelConfiguration(overrideDirective, (configuration, levelSwitch) => configuration.Override(overridePrefix, levelSwitch));
ApplyMinimumLevelConfiguration(overrideDirective, (configuration, levelSwitch) =>
{
configuration.Override(overridePrefix, levelSwitch);
_resolutionContext.ReaderOptions.OnLevelSwitchCreated?.Invoke(overridePrefix, levelSwitch);
});
}
else
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Globalization;
using System.Reflection;
using Microsoft.Extensions.DependencyModel;
using Serilog.Core;

namespace Serilog.Settings.Configuration;

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

/// <summary>
/// Called when a log level switch is created while reading the configuration.
/// 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).
/// <list type="bullet">
/// <item>For declared switches, the switch name includes the leading <c>$</c> character.</item>
/// <item>For minimum level override switches, the switch name is the (partial) namespace or type name of the override.</item>
/// </list>
/// </summary>
public Action<string, LoggingLevelSwitch> OnLevelSwitchCreated { get; init; }

internal Assembly[] Assemblies { get; }
internal DependencyContext DependencyContext { get; }
internal ConfigurationAssemblySource? ConfigurationAssemblySource { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ sealed class ResolutionContext
readonly IDictionary<string, LoggingFilterSwitchProxy> _declaredFilterSwitches;
readonly IConfiguration _appConfiguration;

public ResolutionContext(IConfiguration appConfiguration = null, IFormatProvider formatProvider = null)
public ResolutionContext(IConfiguration appConfiguration = null, ConfigurationReaderOptions readerOptions = null)
{
_declaredLevelSwitches = new Dictionary<string, LoggingLevelSwitch>();
_declaredFilterSwitches = new Dictionary<string, LoggingFilterSwitchProxy>();
_appConfiguration = appConfiguration;
FormatProvider = formatProvider;
ReaderOptions = readerOptions ?? new ConfigurationReaderOptions();
}

public IFormatProvider FormatProvider { get; }
public ConfigurationReaderOptions ReaderOptions { get; }

/// <summary>
/// Looks up a switch in the declared LoggingLevelSwitches
Expand Down Expand Up @@ -64,18 +64,22 @@ public IConfiguration AppConfiguration
}
}

public void AddLevelSwitch(string levelSwitchName, LoggingLevelSwitch levelSwitch)
public string AddLevelSwitch(string levelSwitchName, LoggingLevelSwitch levelSwitch)
{
if (levelSwitchName == null) throw new ArgumentNullException(nameof(levelSwitchName));
if (levelSwitch == null) throw new ArgumentNullException(nameof(levelSwitch));
_declaredLevelSwitches[ToSwitchReference(levelSwitchName)] = levelSwitch;
var referenceName = ToSwitchReference(levelSwitchName);
_declaredLevelSwitches[referenceName] = levelSwitch;
return referenceName;
}

public void AddFilterSwitch(string filterSwitchName, LoggingFilterSwitchProxy filterSwitch)
public string AddFilterSwitch(string filterSwitchName, LoggingFilterSwitchProxy filterSwitch)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The result of this method is not used. Is it specially done?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was done in anticipation of #366 where the result is now used.

{
if (filterSwitchName == null) throw new ArgumentNullException(nameof(filterSwitchName));
if (filterSwitch == null) throw new ArgumentNullException(nameof(filterSwitch));
_declaredFilterSwitches[ToSwitchReference(filterSwitchName)] = filterSwitch;
var referenceName = ToSwitchReference(filterSwitchName);
_declaredFilterSwitches[referenceName] = filterSwitch;
return referenceName;
}

string ToSwitchReference(string switchName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ public object ConvertTo(Type toType, ResolutionContext resolutionContext)
}
}

return Convert.ChangeType(argumentValue, toType, resolutionContext.FormatProvider);
return Convert.ChangeType(argumentValue, toType, resolutionContext.ReaderOptions.FormatProvider);
}

internal static Type FindType(string typeName)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Globalization;
using System.Globalization;
using System.Reflection;
using Microsoft.Extensions.Configuration;
using Serilog.Events;
Expand All @@ -18,7 +18,7 @@ public ConfigurationReaderTests()
_configurationReader = new ConfigurationReader(
JsonStringConfigSource.LoadSection("{ 'Serilog': { } }", "Serilog"),
AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies),
CultureInfo.InvariantCulture);
new ConfigurationReaderOptions());
}

[Fact]
Expand Down Expand Up @@ -197,7 +197,7 @@ public void MethodsAreSelectedBasedOnCountOfMatchedArgumentsAndThenStringType()
[MemberData(nameof(FlatMinimumLevel))]
public void FlatMinimumLevelCorrectOneIsEnabledOnLogger(IConfigurationRoot root, LogEventLevel expectedMinimumLevel)
{
var reader = new ConfigurationReader(root.GetSection("Serilog"), AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), CultureInfo.InvariantCulture, root);
var reader = new ConfigurationReader(root.GetSection("Serilog"), AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), new ConfigurationReaderOptions(), root);
var loggerConfig = new LoggerConfiguration();

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

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

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

reader.Configure(loggerConfig);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,24 @@ namespace Serilog.Settings.Configuration.Tests;

public class ConfigurationSettingsTests
{
static LoggerConfiguration ConfigFromJson(string jsonString, string secondJsonSource = null)
static LoggerConfiguration ConfigFromJson(string jsonString, string secondJsonSource = null, ConfigurationReaderOptions options = null)
{
return ConfigFromJson(jsonString, secondJsonSource, out _);
return ConfigFromJson(jsonString, secondJsonSource, out _, options);
}

static LoggerConfiguration ConfigFromJson(string jsonString, out IConfiguration configuration)
static LoggerConfiguration ConfigFromJson(string jsonString, out IConfiguration configuration, ConfigurationReaderOptions options = null)
{
return ConfigFromJson(jsonString, null, out configuration);
return ConfigFromJson(jsonString, null, out configuration, options);
}

static LoggerConfiguration ConfigFromJson(string jsonString, string secondJsonSource, out IConfiguration configuration)
static LoggerConfiguration ConfigFromJson(string jsonString, string secondJsonSource, out IConfiguration configuration, ConfigurationReaderOptions options)
{
var builder = new ConfigurationBuilder().AddJsonString(jsonString);
if (secondJsonSource != null)
builder.AddJsonString(secondJsonSource);
configuration = builder.Build();
return new LoggerConfiguration()
.ReadFrom.Configuration(configuration);
.ReadFrom.Configuration(configuration, options);
}

[Fact]
Expand Down Expand Up @@ -1345,4 +1345,37 @@ public void FilterWithIsAppliedWithCustomFilter()
log.ForContext("User", "the user").Write(Some.InformationEvent());
Assert.NotNull(evt);
}

[Theory]
[InlineData("$switch1")]
[InlineData("switch1")]
public void TestLogLevelSwitchesCallback(string switchName)
{
var json = $@"{{
0xced marked this conversation as resolved.
Show resolved Hide resolved
'Serilog': {{
'LevelSwitches': {{ '{switchName}': 'Information' }},
'MinimumLevel': {{
'Override': {{
'System': 'Warning',
'System.Threading': 'Debug'
}}
}}
}}
}}";

IDictionary<string, LoggingLevelSwitch> switches = new Dictionary<string, LoggingLevelSwitch>();
var readerOptions = new ConfigurationReaderOptions { OnLevelSwitchCreated = (name, levelSwitch) => switches[name] = levelSwitch };
ConfigFromJson(json, options: readerOptions);

Assert.Equal(3, switches.Count);

var switch1 = Assert.Contains("$switch1", switches);
Assert.Equal(LogEventLevel.Information, switch1.MinimumLevel);

var system = Assert.Contains("System", switches);
Assert.Equal(LogEventLevel.Warning, system.MinimumLevel);

var systemThreading = Assert.Contains("System.Threading", switches);
Assert.Equal(LogEventLevel.Debug, systemThreading.MinimumLevel);
}
}