diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs index 2a6c317..203d2f2 100644 --- a/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs +++ b/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs @@ -160,10 +160,19 @@ void ApplyMinimumLevel(LoggerConfiguration loggerConfiguration) loggerConfiguration.MinimumLevel.ControlledBy(globalMinimumLevelSwitch); } + // Level overrides may be set via environment variables. In that case it may be problematic to use dots in + // variable name, see https://github.com/dotnet/AspNetCore.Docs/issues/17378, so all dots should be replaces + // with double underscore. The consequence is that configuration becomes "nested" and GetSection("Override").GetChildren() + // call returns nothing since this method returns only immediate descendant configuration sub-sections. + // The workaround is to use AsEnumerable(true) method to retrieve all descendants and then manually replace : with . + var flattenDescendants = minimumLevelDirective.GetSection("Override").AsEnumerable(true).ToList(); + foreach (var overrideDirective in minimumLevelDirective.GetSection("Override").GetChildren()) { var overridePrefix = overrideDirective.Key; var overridenLevelOrSwitch = overrideDirective.Value; + // filter out "normal" keys + flattenDescendants.RemoveAll(p => p.Key == overridePrefix); if (Enum.TryParse(overridenLevelOrSwitch, out LogEventLevel _)) { ApplyMinimumLevelConfiguration(overrideDirective, (configuration, levelSwitch) => @@ -180,6 +189,29 @@ void ApplyMinimumLevel(LoggerConfiguration loggerConfiguration) } } + // Process remaining configuration values if any. Note that this code path does not call + // SubscribeToLoggingLevelChanges as ApplyMinimumLevelConfiguration does above. This can + // be improved some time later. + foreach (var pair in flattenDescendants) + { + var overridePrefix = pair.Key.Replace(':', '.'); + var overridenLevelOrSwitch = pair.Value; + if (Enum.TryParse(overridenLevelOrSwitch, out LogEventLevel _)) + { + var minimumLevel = ParseLogEventLevel(overridenLevelOrSwitch!); + + var levelSwitch = new LoggingLevelSwitch(minimumLevel); + loggerConfiguration.MinimumLevel.Override(overridePrefix, levelSwitch); + _resolutionContext.ReaderOptions.OnLevelSwitchCreated?.Invoke(overridePrefix, levelSwitch); + } + else if (!string.IsNullOrEmpty(overridenLevelOrSwitch)) + { + var overrideSwitch = _resolutionContext.LookUpLevelSwitchByName(overridenLevelOrSwitch!); + // not calling ApplyMinimumLevel local function because here we have a reference to a LogLevelSwitch already + loggerConfiguration.MinimumLevel.Override(overridePrefix, overrideSwitch); + } + } + void ApplyMinimumLevelConfiguration(IConfigurationSection directive, Action applyConfigAction) { var minimumLevel = ParseLogEventLevel(directive.Value!); diff --git a/test/Serilog.Settings.Configuration.Tests/ConfigurationSettingsTests.cs b/test/Serilog.Settings.Configuration.Tests/ConfigurationSettingsTests.cs index 4916f35..3ecaad7 100644 --- a/test/Serilog.Settings.Configuration.Tests/ConfigurationSettingsTests.cs +++ b/test/Serilog.Settings.Configuration.Tests/ConfigurationSettingsTests.cs @@ -405,6 +405,35 @@ public void TestMinimumLevelOverridesForChildContext() Assert.NotNull(evt); } + [Fact] + public void TestMinimumLevelOverridesForChildContext_With_Environment_Variables() + { + Environment.SetEnvironmentVariable("My_Serilog__MinimumLevel__Default", "Warning"); + Environment.SetEnvironmentVariable("My_Serilog__MinimumLevel__Override__System", "Warning"); + Environment.SetEnvironmentVariable("My_Serilog__MinimumLevel__Override__System__Threading", "Debug"); + + var builder = new ConfigurationBuilder().AddEnvironmentVariables("My_"); + var configuration = new LoggerConfiguration().ReadFrom.Configuration(builder.Build()); + + LogEvent? evt = null; + + var log = configuration + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + log.Write(Some.DebugEvent()); + Assert.Null(evt); + + var custom = log.ForContext(Constants.SourceContextPropertyName, typeof(System.Threading.Tasks.Task).FullName + "<42>"); + custom.Write(Some.DebugEvent()); + Assert.NotNull(evt); + + evt = null; + var systemThreadingLogger = log.ForContext(); + systemThreadingLogger.Write(Some.DebugEvent()); + Assert.NotNull(evt); + } + [Fact] public void SinksWithAbstractParamsAreConfiguredWithTypeName() { diff --git a/test/Serilog.Settings.Configuration.Tests/Serilog.Settings.Configuration.Tests.csproj b/test/Serilog.Settings.Configuration.Tests/Serilog.Settings.Configuration.Tests.csproj index f1915c1..168ff4c 100644 --- a/test/Serilog.Settings.Configuration.Tests/Serilog.Settings.Configuration.Tests.csproj +++ b/test/Serilog.Settings.Configuration.Tests/Serilog.Settings.Configuration.Tests.csproj @@ -1,4 +1,4 @@ - + net48 @@ -19,6 +19,7 @@ +