Skip to content

Commit 3e835fa

Browse files
committed
Add support for overrides from environment variables
1 parent d7e0dbd commit 3e835fa

File tree

3 files changed

+63
-1
lines changed

3 files changed

+63
-1
lines changed

src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,19 @@ void ApplyMinimumLevel(LoggerConfiguration loggerConfiguration)
160160
loggerConfiguration.MinimumLevel.ControlledBy(globalMinimumLevelSwitch);
161161
}
162162

163+
// Level overrides may be set via environment variables. In that case it may be problematic to use dots in
164+
// variable name, see https://github.com/dotnet/AspNetCore.Docs/issues/17378, so all dots should be replaces
165+
// with double underscore. The consequence is that configuration becomes "nested" and GetSection("Override").GetChildren()
166+
// call returns nothing since this method returns only immediate descendant configuration sub-sections.
167+
// The workaround is to use AsEnumerable(true) method to retrieve all descendants and then manually replace : with .
168+
var flattenDescendants = minimumLevelDirective.GetSection("Override").AsEnumerable(true).ToList();
169+
163170
foreach (var overrideDirective in minimumLevelDirective.GetSection("Override").GetChildren())
164171
{
165172
var overridePrefix = overrideDirective.Key;
166173
var overridenLevelOrSwitch = overrideDirective.Value;
174+
// filter out "normal" keys
175+
flattenDescendants.RemoveAll(p => p.Key == overridePrefix);
167176
if (Enum.TryParse(overridenLevelOrSwitch, out LogEventLevel _))
168177
{
169178
ApplyMinimumLevelConfiguration(overrideDirective, (configuration, levelSwitch) =>
@@ -180,6 +189,29 @@ void ApplyMinimumLevel(LoggerConfiguration loggerConfiguration)
180189
}
181190
}
182191

192+
// Process remaining configuration values if any. Note that this code path does not call
193+
// SubscribeToLoggingLevelChanges as ApplyMinimumLevelConfiguration does above. This can
194+
// be improved some time later.
195+
foreach (var pair in flattenDescendants)
196+
{
197+
var overridePrefix = pair.Key.Replace(':', '.');
198+
var overridenLevelOrSwitch = pair.Value;
199+
if (Enum.TryParse(overridenLevelOrSwitch, out LogEventLevel _))
200+
{
201+
var minimumLevel = ParseLogEventLevel(overridenLevelOrSwitch!);
202+
203+
var levelSwitch = new LoggingLevelSwitch(minimumLevel);
204+
loggerConfiguration.MinimumLevel.Override(overridePrefix, levelSwitch);
205+
_resolutionContext.ReaderOptions.OnLevelSwitchCreated?.Invoke(overridePrefix, levelSwitch);
206+
}
207+
else if (!string.IsNullOrEmpty(overridenLevelOrSwitch))
208+
{
209+
var overrideSwitch = _resolutionContext.LookUpLevelSwitchByName(overridenLevelOrSwitch!);
210+
// not calling ApplyMinimumLevel local function because here we have a reference to a LogLevelSwitch already
211+
loggerConfiguration.MinimumLevel.Override(overridePrefix, overrideSwitch);
212+
}
213+
}
214+
183215
void ApplyMinimumLevelConfiguration(IConfigurationSection directive, Action<LoggerMinimumLevelConfiguration, LoggingLevelSwitch> applyConfigAction)
184216
{
185217
var minimumLevel = ParseLogEventLevel(directive.Value!);

test/Serilog.Settings.Configuration.Tests/ConfigurationSettingsTests.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,35 @@ public void TestMinimumLevelOverridesForChildContext()
405405
Assert.NotNull(evt);
406406
}
407407

408+
[Fact]
409+
public void TestMinimumLevelOverridesForChildContext_With_Environment_Variables()
410+
{
411+
Environment.SetEnvironmentVariable("My_Serilog__MinimumLevel__Default", "Warning");
412+
Environment.SetEnvironmentVariable("My_Serilog__MinimumLevel__Override__System", "Warning");
413+
Environment.SetEnvironmentVariable("My_Serilog__MinimumLevel__Override__System__Threading", "Debug");
414+
415+
var builder = new ConfigurationBuilder().AddEnvironmentVariables("My_");
416+
var configuration = new LoggerConfiguration().ReadFrom.Configuration(builder.Build());
417+
418+
LogEvent? evt = null;
419+
420+
var log = configuration
421+
.WriteTo.Sink(new DelegatingSink(e => evt = e))
422+
.CreateLogger();
423+
424+
log.Write(Some.DebugEvent());
425+
Assert.Null(evt);
426+
427+
var custom = log.ForContext(Constants.SourceContextPropertyName, typeof(System.Threading.Tasks.Task).FullName + "<42>");
428+
custom.Write(Some.DebugEvent());
429+
Assert.NotNull(evt);
430+
431+
evt = null;
432+
var systemThreadingLogger = log.ForContext<System.Threading.Tasks.Task>();
433+
systemThreadingLogger.Write(Some.DebugEvent());
434+
Assert.NotNull(evt);
435+
}
436+
408437
[Fact]
409438
public void SinksWithAbstractParamsAreConfiguredWithTypeName()
410439
{

test/Serilog.Settings.Configuration.Tests/Serilog.Settings.Configuration.Tests.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT'">net48</TargetFrameworks>
@@ -19,6 +19,7 @@
1919
<ItemGroup>
2020
<PackageReference Include="CliWrap" Version="3.6.0" />
2121
<PackageReference Include="FluentAssertions" Version="6.10.0" />
22+
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0" />
2223
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
2324
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
2425
<PackageReference Include="NuGet.Frameworks" Version="6.5.0" />

0 commit comments

Comments
 (0)