diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs index 4d44c32..1ed2521 100644 --- a/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs +++ b/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs @@ -22,12 +22,18 @@ class ConfigurationReader : IConfigurationReader readonly IConfigurationSection _section; readonly IReadOnlyCollection _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. @@ -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) @@ -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)); @@ -124,6 +133,34 @@ void ApplyMinimumLevel(IConfigurationSection directive, Action FlatMinimumLevel => new List + { + 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() {{minimumLevelFlatKey, LogEventLevel.Error.ToString()}}), LogEventLevel.Error}, + new object[] { GetConfigRoot( + appsettingsJsonLevel: minimumLevelFlatTemplate.Format(LogEventLevel.Debug), + envVariables: new Dictionary() {{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 ObjectMinimumLevel => new List + { + 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(){{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 MixedMinimumLevel => new List + { + 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() + { + {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); + } } } diff --git a/test/Serilog.Settings.Configuration.Tests/Support/ConfigurationReaderTestHelpers.cs b/test/Serilog.Settings.Configuration.Tests/Support/ConfigurationReaderTestHelpers.cs new file mode 100644 index 0000000..d1fa618 --- /dev/null +++ b/test/Serilog.Settings.Configuration.Tests/Support/ConfigurationReaderTestHelpers.cs @@ -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(); + + 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 envVariables = null) + { + var configBuilder = new ConfigurationBuilder(); + + configBuilder.AddJsonString(appsettingsJsonLevel ?? "{}"); + configBuilder.AddJsonString(appsettingsDevelopmentJsonLevel ?? "{}"); + configBuilder.Add(new ReloadableConfigurationSource(envVariables ?? new Dictionary())); + + return configBuilder.Build(); + } + } +} diff --git a/test/Serilog.Settings.Configuration.Tests/Support/Extensions.cs b/test/Serilog.Settings.Configuration.Tests/Support/Extensions.cs index e02d875..70be52b 100644 --- a/test/Serilog.Settings.Configuration.Tests/Support/Extensions.cs +++ b/test/Serilog.Settings.Configuration.Tests/Support/Extensions.cs @@ -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); } }