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

Minimumlevel partial fix with IConfigurationRoot.Providers #233

Merged
merged 2 commits into from
Sep 13, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,18 @@ class ConfigurationReader : IConfigurationReader
readonly IConfigurationSection _section;
readonly IReadOnlyCollection<Assembly> _configurationAssemblies;
readonly ResolutionContext _resolutionContext;
#if NETSTANDARD || NET461
readonly IConfigurationRoot _configurationRoot;
#endif

public ConfigurationReader(IConfigurationSection configSection, AssemblyFinder assemblyFinder, IConfiguration configuration = null)
{
_section = configSection ?? throw new ArgumentNullException(nameof(configSection));
_configurationAssemblies = LoadConfigurationAssemblies(_section, assemblyFinder);
_resolutionContext = new ResolutionContext(configuration);
#if NETSTANDARD || NET461
_configurationRoot = configuration as IConfigurationRoot;
#endif
}

// Used internally for processing nested configuration sections -- see GetMethodCalls below.
Expand All @@ -36,6 +42,9 @@ internal ConfigurationReader(IConfigurationSection configSection, IReadOnlyColle
_section = configSection ?? throw new ArgumentNullException(nameof(configSection));
_configurationAssemblies = configurationAssemblies ?? throw new ArgumentNullException(nameof(configurationAssemblies));
_resolutionContext = resolutionContext ?? throw new ArgumentNullException(nameof(resolutionContext));
#if NETSTANDARD || NET461
_configurationRoot = resolutionContext.HasAppConfiguration ? resolutionContext.AppConfiguration as IConfigurationRoot : null;
#endif
}

public void Configure(LoggerConfiguration loggerConfiguration)
Expand Down Expand Up @@ -85,7 +94,7 @@ void ApplyMinimumLevel(LoggerConfiguration loggerConfiguration)
{
var minimumLevelDirective = _section.GetSection("MinimumLevel");

var defaultMinLevelDirective = minimumLevelDirective.Value != null ? minimumLevelDirective : minimumLevelDirective.GetSection("Default");
IConfigurationSection defaultMinLevelDirective = GetDefaultMinLevelDirective();
if (defaultMinLevelDirective.Value != null)
{
ApplyMinimumLevel(defaultMinLevelDirective, (configuration, levelSwitch) => configuration.ControlledBy(levelSwitch));
Expand Down Expand Up @@ -124,6 +133,34 @@ void ApplyMinimumLevel(IConfigurationSection directive, Action<LoggerMinimumLeve

SubscribeToLoggingLevelChanges(directive, levelSwitch);
}

IConfigurationSection GetDefaultMinLevelDirective()
{
#if NETSTANDARD || NET461

var defaultLevelDirective = minimumLevelDirective.GetSection("Default");
if (_configurationRoot != null && minimumLevelDirective.Value != null && defaultLevelDirective.Value != null)
{
foreach (var provider in _configurationRoot.Providers.Reverse())
{
if (provider.TryGet(minimumLevelDirective.Path, out _))
{
return _configurationRoot.GetSection(minimumLevelDirective.Path);
}

if (provider.TryGet(defaultLevelDirective.Path, out _))
{
return _configurationRoot.GetSection(defaultLevelDirective.Path);
}
}

return null;
}

#endif //NET451 or fallback

return minimumLevelDirective.Value != null ? minimumLevelDirective : minimumLevelDirective.GetSection("Default");
}
}

void SubscribeToLoggingLevelChanges(IConfigurationSection levelSection, LoggingLevelSwitch levelSwitch)
Expand Down
109 changes: 109 additions & 0 deletions test/Serilog.Settings.Configuration.Tests/ConfigurationReaderTests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
using Xunit;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using Microsoft.Extensions.Configuration;
using Serilog.Events;
using Serilog.Formatting;
using Serilog.Settings.Configuration.Assemblies;
using Serilog.Settings.Configuration.Tests.Support;
using static Serilog.Settings.Configuration.Tests.Support.ConfigurationReaderTestHelpers;

namespace Serilog.Settings.Configuration.Tests
{
Expand Down Expand Up @@ -172,5 +176,110 @@ public void MethodsAreSelectedBasedOnCountOfMatchedArgumentsAndThenStringType()
var selected = ConfigurationReader.SelectConfigurationMethod(options, "DummyRollingFile", suppliedArgumentNames);
Assert.Equal(typeof(string), selected.GetParameters()[2].ParameterType);
}

public static IEnumerable<object[]> FlatMinimumLevel => new List<object[]>
{
new object[] { GetConfigRoot(appsettingsJsonLevel: minimumLevelFlatTemplate.Format(LogEventLevel.Error)), LogEventLevel.Error },
new object[] { GetConfigRoot(appsettingsDevelopmentJsonLevel: minimumLevelFlatTemplate.Format(LogEventLevel.Error)), LogEventLevel.Error },
new object[] { GetConfigRoot(envVariables: new Dictionary<string, string>() {{minimumLevelFlatKey, LogEventLevel.Error.ToString()}}), LogEventLevel.Error},
new object[] { GetConfigRoot(
appsettingsJsonLevel: minimumLevelFlatTemplate.Format(LogEventLevel.Debug),
envVariables: new Dictionary<string, string>() {{minimumLevelFlatKey, LogEventLevel.Error.ToString()}}),
LogEventLevel.Error
}
};

[Theory]
[MemberData(nameof(FlatMinimumLevel))]
public void FlatMinimumLevelCorrectOneIsEnabledOnLogger(IConfigurationRoot root, LogEventLevel expectedMinimumLevel)
{
var reader = new ConfigurationReader(root.GetSection("Serilog"), AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), root);
var loggerConfig = new LoggerConfiguration();

reader.Configure(loggerConfig);

AssertLogEventLevels(loggerConfig, expectedMinimumLevel);
}

public static IEnumerable<object[]> ObjectMinimumLevel => new List<object[]>
{
new object[] { GetConfigRoot(appsettingsJsonLevel: minimumLevelObjectTemplate.Format(LogEventLevel.Error)), LogEventLevel.Error },
new object[] { GetConfigRoot(appsettingsDevelopmentJsonLevel: minimumLevelObjectTemplate.Format(LogEventLevel.Error)), LogEventLevel.Error },
new object[] { GetConfigRoot(envVariables: new Dictionary<string, string>(){{minimumLevelObjectKey, LogEventLevel.Error.ToString() } }), LogEventLevel.Error },
new object[] { GetConfigRoot(
appsettingsJsonLevel: minimumLevelObjectTemplate.Format(LogEventLevel.Error),
appsettingsDevelopmentJsonLevel: minimumLevelObjectTemplate.Format(LogEventLevel.Debug)),
LogEventLevel.Debug }
};

[Theory]
[MemberData(nameof(ObjectMinimumLevel))]
public void ObjectMinimumLevelCorrectOneIsEnabledOnLogger(IConfigurationRoot root, LogEventLevel expectedMinimumLevel)
{
var reader = new ConfigurationReader(root.GetSection("Serilog"), AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), root);
var loggerConfig = new LoggerConfiguration();

reader.Configure(loggerConfig);

AssertLogEventLevels(loggerConfig, expectedMinimumLevel);
}

#if !(NET452)

// currently only works in the .NET 4.6.1 and .NET Standard builds of Serilog.Settings.Configuration
public static IEnumerable<object[]> MixedMinimumLevel => new List<object[]>
{
new object[]
{
GetConfigRoot(
appsettingsJsonLevel: minimumLevelObjectTemplate.Format(LogEventLevel.Error),
appsettingsDevelopmentJsonLevel: minimumLevelFlatTemplate.Format(LogEventLevel.Debug)),
LogEventLevel.Debug
},
new object[]
{
GetConfigRoot(
appsettingsJsonLevel: minimumLevelFlatTemplate.Format(LogEventLevel.Error),
appsettingsDevelopmentJsonLevel: minimumLevelObjectTemplate.Format(LogEventLevel.Debug)),
LogEventLevel.Debug
},
// precedence should be flat > object if from the same source
new object[]
{
GetConfigRoot(
envVariables: new Dictionary<string, string>()
{
{minimumLevelObjectKey, LogEventLevel.Error.ToString()},
{minimumLevelFlatKey, LogEventLevel.Debug.ToString()}
}),
LogEventLevel.Debug
}
};

[Theory]
[MemberData(nameof(MixedMinimumLevel))]
public void MixedMinimumLevelCorrectOneIsEnabledOnLogger(IConfigurationRoot root, LogEventLevel expectedMinimumLevel)
{
var reader = new ConfigurationReader(root.GetSection("Serilog"), AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), root);
var loggerConfig = new LoggerConfiguration();

reader.Configure(loggerConfig);

AssertLogEventLevels(loggerConfig, expectedMinimumLevel);
}

#endif

[Fact]
public void NoConfigurationRootUsedStillValid()
{
var section = JsonStringConfigSource.LoadSection(@"{ 'Nest': { 'Serilog': { 'MinimumLevel': 'Error' } } }", "Nest");
var reader = new ConfigurationReader(section.GetSection("Serilog"), AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), section);
var loggerConfig = new LoggerConfiguration();

reader.Configure(loggerConfig);

AssertLogEventLevels(loggerConfig, LogEventLevel.Error);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Configuration;
using Serilog.Events;
using Xunit;

namespace Serilog.Settings.Configuration.Tests.Support
{
static class ConfigurationReaderTestHelpers
{
public const string minimumLevelFlatTemplate = @"
{{
'Serilog': {{
'MinimumLevel': '{0}'
}}
}}";
public const string minimumLevelObjectTemplate = @"
{{
'Serilog': {{
'MinimumLevel': {{
'Default': '{0}'
}}
}}
}}";
public const string minimumLevelFlatKey = "Serilog:MinimumLevel";
public const string minimumLevelObjectKey = "Serilog:MinimumLevel:Default";

public static void AssertLogEventLevels(LoggerConfiguration loggerConfig, LogEventLevel expectedMinimumLevel)
{
var logger = loggerConfig.CreateLogger();

var logEventValues = Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>();

foreach (var logEvent in logEventValues)
{
if (logEvent < expectedMinimumLevel)
{
Assert.False(logger.IsEnabled(logEvent),
$"The log level {logEvent} should be disabled as it's lower priority than the minimum level of {expectedMinimumLevel}.");
}
else
{
Assert.True(logger.IsEnabled(logEvent),
$"The log level {logEvent} should be enabled as it's {(logEvent == expectedMinimumLevel ? "the same" : "higher")} priority {(logEvent == expectedMinimumLevel ? "as" : "than")} the minimum level of {expectedMinimumLevel}.");
}
}
}

// the naming is only to show priority as providers
public static IConfigurationRoot GetConfigRoot(
string appsettingsJsonLevel = null,
string appsettingsDevelopmentJsonLevel = null,
Dictionary<string, string> envVariables = null)
{
var configBuilder = new ConfigurationBuilder();

configBuilder.AddJsonString(appsettingsJsonLevel ?? "{}");
configBuilder.AddJsonString(appsettingsDevelopmentJsonLevel ?? "{}");
configBuilder.Add(new ReloadableConfigurationSource(envVariables ?? new Dictionary<string, string>()));

return configBuilder.Build();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@ public static string ToValidJson(this string str)
#endif
return str;
}

public static string Format(this string template, params object[] paramObjects) => string.Format(template, paramObjects);
}
}