Skip to content

Add a way to get the configured log level switches #347

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

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,28 @@ public static LoggerConfiguration Configuration(
IConfiguration configuration,
string sectionName,
DependencyContext dependencyContext = null)
{
return Configuration(settingConfiguration, configuration, sectionName, out _, dependencyContext);
}

/// <summary>
/// Reads logger settings from the provided configuration object using the provided section name. Generally this
/// is preferable over the other method that takes a configuration section. Only this version will populate
/// IConfiguration parameters on target methods.
/// </summary>
/// <param name="settingConfiguration">Logger setting configuration.</param>
/// <param name="configuration">A configuration object which contains a Serilog section.</param>
/// <param name="sectionName">A section name for section which contains a Serilog section.</param>
/// <param name="loadedConfiguration">Contains information about the loaded configuration upon return.</param>
/// <param name="dependencyContext">The dependency context from which sink/enricher packages can be located. If not supplied, the platform
/// default will be used.</param>
/// <returns>An object allowing configuration to continue.</returns>
public static LoggerConfiguration Configuration(
this LoggerSettingsConfiguration settingConfiguration,
IConfiguration configuration,
string sectionName,
out LoadedConfiguration loadedConfiguration,
DependencyContext dependencyContext = null)
{
if (settingConfiguration == null) throw new ArgumentNullException(nameof(settingConfiguration));
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
Expand All @@ -56,11 +78,10 @@ public static LoggerConfiguration Configuration(
? AssemblyFinder.Auto()
: AssemblyFinder.ForDependencyContext(dependencyContext);

return settingConfiguration.Settings(
new ConfigurationReader(
configuration.GetSection(sectionName),
assemblyFinder,
configuration));
var configurationReader = new ConfigurationReader(configuration.GetSection(sectionName), assemblyFinder, configuration);
var loggerConfiguration = settingConfiguration.Settings(configurationReader);
loadedConfiguration = configurationReader.GetLoadedConfiguration();
return loggerConfiguration;
}

/// <summary>
Expand All @@ -79,6 +100,24 @@ public static LoggerConfiguration Configuration(
DependencyContext dependencyContext = null)
=> Configuration(settingConfiguration, configuration, DefaultSectionName, dependencyContext);

/// <summary>
/// Reads logger settings from the provided configuration object using the default section name. Generally this
/// is preferable over the other method that takes a configuration section. Only this version will populate
/// IConfiguration parameters on target methods.
/// </summary>
/// <param name="settingConfiguration">Logger setting configuration.</param>
/// <param name="configuration">A configuration object which contains a Serilog section.</param>
/// <param name="loadedConfiguration">Contains information about the loaded configuration upon return.</param>
/// <param name="dependencyContext">The dependency context from which sink/enricher packages can be located. If not supplied, the platform
/// default will be used.</param>
/// <returns>An object allowing configuration to continue.</returns>
public static LoggerConfiguration Configuration(
this LoggerSettingsConfiguration settingConfiguration,
IConfiguration configuration,
out LoadedConfiguration loadedConfiguration,
DependencyContext dependencyContext = null)
=> Configuration(settingConfiguration, configuration, DefaultSectionName, out loadedConfiguration, dependencyContext);

/// <summary>
/// Reads logger settings from the provided configuration section. Generally it is preferable to use the other
/// extension method that takes the full configuration object.
Expand Down Expand Up @@ -123,14 +162,36 @@ public static LoggerConfiguration Configuration(
IConfiguration configuration,
string sectionName,
ConfigurationAssemblySource configurationAssemblySource)
=> Configuration(settingConfiguration, configuration, sectionName, configurationAssemblySource, out _);

/// <summary>
/// Reads logger settings from the provided configuration object using the provided section name. Generally this
/// is preferable over the other method that takes a configuration section. Only this version will populate
/// IConfiguration parameters on target methods.
/// </summary>
/// <param name="settingConfiguration">Logger setting configuration.</param>
/// <param name="configuration">A configuration object which contains a Serilog section.</param>
/// <param name="sectionName">A section name for section which contains a Serilog section.</param>
/// <param name="configurationAssemblySource">Defines how the package identifies assemblies to scan for sinks and other types.</param>
/// <param name="loadedConfiguration">Contains information about the loaded configuration upon return.</param>
/// <returns>An object allowing configuration to continue.</returns>
public static LoggerConfiguration Configuration(
this LoggerSettingsConfiguration settingConfiguration,
IConfiguration configuration,
string sectionName,
ConfigurationAssemblySource configurationAssemblySource,
out LoadedConfiguration loadedConfiguration)
{
if (settingConfiguration == null) throw new ArgumentNullException(nameof(settingConfiguration));
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
if (sectionName == null) throw new ArgumentNullException(nameof(sectionName));

var assemblyFinder = AssemblyFinder.ForSource(configurationAssemblySource);

return settingConfiguration.Settings(new ConfigurationReader(configuration.GetSection(sectionName), assemblyFinder, configuration));
var configurationReader = new ConfigurationReader(configuration.GetSection(sectionName), assemblyFinder, configuration);
var loggerConfiguration = settingConfiguration.Settings(configurationReader);
loadedConfiguration = configurationReader.GetLoadedConfiguration();
return loggerConfiguration;
}

/// <summary>
Expand Down Expand Up @@ -183,12 +244,32 @@ public static LoggerConfiguration Configuration(
IConfiguration configuration,
string sectionName,
params Assembly[] assemblies)
=> Configuration(settingConfiguration, configuration, sectionName, out _, assemblies);

/// <summary>
/// Reads logger settings from the provided configuration object using the provided section name.
/// </summary>
/// <param name="settingConfiguration">Logger setting configuration.</param>
/// <param name="configuration">A configuration object which contains a Serilog section.</param>
/// <param name="sectionName">A section name for section which contains a Serilog section.</param>
/// <param name="loadedConfiguration">Contains information about the loaded configuration upon return.</param>
/// <param name="assemblies">A collection of assemblies that contains sinks and other types.</param>
/// <returns>An object allowing configuration to continue.</returns>
public static LoggerConfiguration Configuration(
this LoggerSettingsConfiguration settingConfiguration,
IConfiguration configuration,
string sectionName,
out LoadedConfiguration loadedConfiguration,
params Assembly[] assemblies)
{
if (settingConfiguration == null) throw new ArgumentNullException(nameof(settingConfiguration));
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
if (sectionName == null) throw new ArgumentNullException(nameof(sectionName));

return settingConfiguration.Settings(new ConfigurationReader(configuration.GetSection(sectionName), assemblies, new ResolutionContext(configuration)));
var configurationReader = new ConfigurationReader(configuration.GetSection(sectionName), assemblies, new ResolutionContext(configuration));
var loggerConfiguration = settingConfiguration.Settings(configurationReader);
loadedConfiguration = configurationReader.GetLoadedConfiguration();
return loggerConfiguration;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ public void Configure(LoggerConfiguration loggerConfiguration)
ApplyAuditSinks(loggerConfiguration);
}

public LoadedConfiguration GetLoadedConfiguration()
{
return new LoadedConfiguration(_resolutionContext.LogLevelSwitches);
}

void ProcessFilterSwitchDeclarations()
{
var filterSwitchesDirective = _section.GetSection("FilterSwitches");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Serilog.Core;

namespace Serilog.Settings.Configuration;

/// <summary>
/// Contains information about the loaded configuration.
/// </summary>
public class LoadedConfiguration
{
readonly IDictionary<string,LoggingLevelSwitch> _logLevelSwitches;

internal LoadedConfiguration(IDictionary<string, LoggingLevelSwitch> logLevelSwitches)
{
_logLevelSwitches = logLevelSwitches;
}

/// <summary>
/// The log level switches that were loaded from the <c>LevelSwitches</c> section of the configuration.
/// </summary>
/// <remarks>The keys of the dictionary are the name of the switches without the leading <c>$</c> character.</remarks>
public IReadOnlyDictionary<string, LoggingLevelSwitch> LogLevelSwitches => _logLevelSwitches.ToDictionary(e => e.Key.TrimStart('$'), e => e.Value);
Copy link
Contributor

Choose a reason for hiding this comment

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

Property allocates Dictionary and N strings each call to it.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know if it is worth worrying about the initial dollar sign in the name. In fact, this is part of the switch name. What is the goal in removal of the dollar sign?

Copy link
Member Author

Choose a reason for hiding this comment

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

In fact, this is part of the switch name.

Well, this is debatable: the LevelSwitches section does not need to contain the $ prefix. As found in the README and the appsettings.json file of the Sample project.

{
  "Serilog": {
    "Using": [ "Serilog.Sinks.Console" ],
    "LevelSwitches": { "controlSwitch": "Verbose" },

Property allocates Dictionary and N strings each call to it.

I wouldn't worry too much about that, one call is made per call to .ReadFrom.Configuration(configuration). So for most apps that would be exactly once at startup I guess.

Also, IDictionary does not implement IReadOnlyDictionary, sigh. But we could store Dictionary<string, LoggingLevelSwitch> instead of IDictionary<string, LoggingLevelSwitch> everywhere if the consensus is that this allocation must really be avoided.

Copy link
Contributor

Choose a reason for hiding this comment

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

the LevelSwitches section does not need to contain the $ prefix.

Then it makes no sense in cutting off the dollar sign if it is not a special symbol. Why to do this? Just return _logLevelSwitches as is.

Copy link
Member Author

Choose a reason for hiding this comment

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

I have addressed this concern in 2ecbe0a.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍 $ should be appended in tests.

Copy link
Member Author

Choose a reason for hiding this comment

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

Of course! Fixed in 3f459e1.

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public ResolutionContext(IConfiguration appConfiguration = null)
_appConfiguration = appConfiguration;
}

public IDictionary<string, LoggingLevelSwitch> LogLevelSwitches => _declaredLevelSwitches;

/// <summary>
/// Looks up a switch in the declared LoggingLevelSwitches
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration;
using Serilog.Core;
using Serilog.Events;
using Serilog.Settings.Configuration.Tests.Support;
Expand All @@ -12,22 +12,27 @@ public class ConfigurationSettingsTests
{
static LoggerConfiguration ConfigFromJson(string jsonString, string secondJsonSource = null)
{
return ConfigFromJson(jsonString, secondJsonSource, out _);
return ConfigFromJson(jsonString, secondJsonSource, out _, out _);
}

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

static LoggerConfiguration ConfigFromJson(string jsonString, string secondJsonSource, out IConfiguration configuration)
static LoggerConfiguration ConfigFromJson(string jsonString, out LoadedConfiguration loadedConfiguration)
{
return ConfigFromJson(jsonString, null, out _, out loadedConfiguration);
}

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

[Fact]
Expand Down Expand Up @@ -384,10 +389,12 @@ public void LoggingLevelSwitchIsConfigured(string switchName)
}}";
LogEvent evt = null;

var log = ConfigFromJson(json)
var log = ConfigFromJson(json, out LoadedConfiguration loadedConfiguration)
.WriteTo.Sink(new DelegatingSink(e => evt = e))
.CreateLogger();

Assert.Contains("switch1", loadedConfiguration.LogLevelSwitches);
Assert.Equal(LogEventLevel.Warning, loadedConfiguration.LogLevelSwitches["switch1"].MinimumLevel);
log.Write(Some.DebugEvent());
Assert.True(evt is null, "LoggingLevelSwitch initial level was Warning. It should not log Debug messages");
log.Write(Some.InformationEvent());
Expand Down Expand Up @@ -542,7 +549,7 @@ public void SinkWithIConfigurationArguments()
}";

DummyConfigurationSink.Reset();
var log = ConfigFromJson(json, out var expectedConfig)
var log = ConfigFromJson(json, out IConfiguration expectedConfig)
.CreateLogger();

log.Write(Some.InformationEvent());
Expand All @@ -566,7 +573,7 @@ public void SinkWithOptionalIConfigurationArguments()
}";

DummyConfigurationSink.Reset();
var log = ConfigFromJson(json, out var expectedConfig)
var log = ConfigFromJson(json, out IConfiguration expectedConfig)
.CreateLogger();

log.Write(Some.InformationEvent());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,11 @@ public void ShouldRespectDynamicLevelChanges()
{
using var logger = new LoggerConfiguration()
.ReadFrom
.Configuration(new ConfigurationBuilder().Add(_configSource).Build())
.Configuration(new ConfigurationBuilder().Add(_configSource).Build(), out var loadedConfiguration)
.CreateLogger();

Assert.Equal(LogEventLevel.Information, loadedConfiguration.LogLevelSwitches["mySwitch"].MinimumLevel);

DummyConsoleSink.Emitted.Clear();
logger.Write(Some.DebugEvent());
Assert.Empty(DummyConsoleSink.Emitted);
Expand All @@ -64,6 +66,7 @@ public void ShouldRespectDynamicLevelChanges()
logger.Write(Some.DebugEvent());
logger.ForContext(Constants.SourceContextPropertyName, "Root.Test").Write(Some.DebugEvent());
Assert.Single(DummyConsoleSink.Emitted);
Assert.Equal(LogEventLevel.Debug, loadedConfiguration.LogLevelSwitches["mySwitch"].MinimumLevel);

DummyConsoleSink.Emitted.Clear();
UpdateConfig(overrideLevel: LogEventLevel.Debug);
Expand Down