Skip to content

Add support for overrides from environment variables #385

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

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
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 @@ -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) =>
Expand All @@ -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<LoggerMinimumLevelConfiguration, LoggingLevelSwitch> applyConfigAction)
{
var minimumLevel = ParseLogEventLevel(directive.Value!);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<System.Threading.Tasks.Task>();
systemThreadingLogger.Write(Some.DebugEvent());
Assert.NotNull(evt);
}

[Fact]
public void SinksWithAbstractParamsAreConfiguredWithTypeName()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT'">net48</TargetFrameworks>
Expand All @@ -19,6 +19,7 @@
<ItemGroup>
<PackageReference Include="CliWrap" Version="3.6.0" />
<PackageReference Include="FluentAssertions" Version="6.10.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="NuGet.Frameworks" Version="6.5.0" />
Expand Down