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

Enables updating the configured min-level overrides at runtime #1764

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 7 additions & 10 deletions src/Serilog/Configuration/LoggerMinimumLevelConfiguration.cs
Expand Up @@ -22,15 +22,15 @@ public class LoggerMinimumLevelConfiguration
readonly LoggerConfiguration _loggerConfiguration;
readonly Action<LogEventLevel> _setMinimum;
readonly Action<LoggingLevelSwitch> _setLevelSwitch;
readonly Action<string, LoggingLevelSwitch> _addOverride;
readonly LevelOverrideMapWrapper _wrapper;

internal LoggerMinimumLevelConfiguration(LoggerConfiguration loggerConfiguration, Action<LogEventLevel> setMinimum,
Action<LoggingLevelSwitch> setLevelSwitch, Action<string, LoggingLevelSwitch> addOverride)
Action<LoggingLevelSwitch> setLevelSwitch, LevelOverrideMapWrapper wrapper)
{
_loggerConfiguration = Guard.AgainstNull(loggerConfiguration);
_setMinimum = Guard.AgainstNull(setMinimum);
_setLevelSwitch = setLevelSwitch;
_addOverride = Guard.AgainstNull(addOverride);
_wrapper = Guard.AgainstNull(wrapper);
}

/// <summary>
Expand Down Expand Up @@ -113,16 +113,13 @@ public LoggerConfiguration ControlledBy(LoggingLevelSwitch levelSwitch)
/// <exception cref="ArgumentNullException">When <paramref name="levelSwitch"/> is <code>null</code></exception>
public LoggerConfiguration Override(string source, LoggingLevelSwitch levelSwitch)
{
Guard.AgainstNull(source);
Guard.AgainstNull(levelSwitch);

var trimmed = source.Trim();
if (trimmed.Length == 0) throw new ArgumentException($"A source {nameof(source)} must be provided.", nameof(source));

_addOverride(trimmed, levelSwitch);
Overrides.Add(source, levelSwitch);
return _loggerConfiguration;
}

/// <summary/>
public LoggerMinimumLevelOverridesConfiguration Overrides => new(_loggerConfiguration, _wrapper);

/// <summary>
/// Override the minimum level for events from a specific namespace or type name.
/// This API is not supported for configuring sub-loggers (created through <see cref="LoggerSinkConfiguration.Logger(ILogger, LogEventLevel)"/>). Use <see cref="LoggerConfiguration.Filter"/> or <see cref="LoggerSinkConfiguration.Conditional(Func{LogEvent, bool}, Action{LoggerSinkConfiguration})"/> instead.
Expand Down
@@ -0,0 +1,48 @@
namespace Serilog.Configuration;

/// <summary/>
public class LoggerMinimumLevelOverridesConfiguration
{
readonly LoggerConfiguration _loggerConfiguration;
readonly LevelOverrideMapWrapper _wrapper;

internal LoggerMinimumLevelOverridesConfiguration(LoggerConfiguration loggerConfiguration, LevelOverrideMapWrapper wrapper)
{
_loggerConfiguration = Guard.AgainstNull(loggerConfiguration);
_wrapper = Guard.AgainstNull(wrapper);
}

/// <summary/>
public LoggerConfiguration Add(string source, LoggingLevelSwitch levelSwitch)
{
Guard.AgainstNull(source);
Guard.AgainstNull(levelSwitch);

var trimmed = source.Trim();
if (trimmed.Length == 0) throw new ArgumentException($"A {nameof(source)} must be provided.", nameof(source));

_wrapper.Add(trimmed, levelSwitch);

return _loggerConfiguration;
}

/// <summary/>
public LoggerConfiguration Set(IDictionary<string, LoggingLevelSwitch> overrides)
{
Guard.AgainstNull(overrides);

Dictionary<string, LoggingLevelSwitch> trimmedOverrides = new();

foreach (var levelOverride in overrides)
{
var trimmed = levelOverride.Key.Trim();
if (trimmed.Length == 0) throw new ArgumentException("A source must be provided.", nameof(overrides));

trimmedOverrides.Add(trimmed, levelOverride.Value);
}

_wrapper.Replace(trimmedOverrides);

return _loggerConfiguration;
}
}
5 changes: 5 additions & 0 deletions src/Serilog/Core/LevelOverrideMap.cs
Expand Up @@ -57,6 +57,11 @@ public LevelOverride(string context, LoggingLevelSwitch levelSwitch)
.ToArray();
}

internal LevelOverrideMap WithOverrides(IDictionary<string, LoggingLevelSwitch> overrides)
{
return new LevelOverrideMap(overrides, _defaultMinimumLevel, _defaultLevelSwitch);
}

public void GetEffectiveLevel(
#if FEATURE_SPAN
ReadOnlySpan<char> context,
Expand Down
37 changes: 37 additions & 0 deletions src/Serilog/Core/LevelOverrideMapWrapper.cs
@@ -0,0 +1,37 @@
namespace Serilog.Core;

class LevelOverrideMapWrapper
{
volatile LevelOverrideMap? _map;
IDictionary<string, LoggingLevelSwitch> _overrides = new Dictionary<string, LoggingLevelSwitch>();

public LevelOverrideMap? Map => _map;

public event EventHandler? OverridesChanged;

public void Add(string source, LoggingLevelSwitch levelSwitch)
{
_overrides[source] = levelSwitch;
RefreshMap();
}

public void Replace(IDictionary<string, LoggingLevelSwitch> overrides)
{
_overrides = overrides;
RefreshMap();
}

public void RefreshMap()
{
_map = _map?.WithOverrides(_overrides);
OverridesChanged?.Invoke(this, EventArgs.Empty);
}

public void EnsureMap(LogEventLevel defaultMinimumLevel, LoggingLevelSwitch? defaultLevelSwitch)
{
if (_overrides.Count > 0)
{
_map = new LevelOverrideMap(_overrides, defaultMinimumLevel, defaultLevelSwitch);
}
}
}
51 changes: 38 additions & 13 deletions src/Serilog/Core/Logger.cs
Expand Up @@ -42,9 +42,10 @@ public sealed class Logger : ILogger, ILogEventSink, IDisposable
// we keep a separate field from the switch, which may
// not be specified. If it is, we'll set _minimumLevel
// to its lower limit and fall through to the secondary check.
readonly LogEventLevel _minimumLevel;
readonly LoggingLevelSwitch? _levelSwitch;
readonly LevelOverrideMap? _overrideMap;
LogEventLevel _minimumLevel;
LoggingLevelSwitch? _levelSwitch;
readonly LevelOverrideMapWrapper _overrideMap;
readonly string? _contextName;

internal Logger(
MessageTemplateProcessor messageTemplateProcessor,
Expand All @@ -56,21 +57,43 @@ public sealed class Logger : ILogger, ILogEventSink, IDisposable
#if FEATURE_ASYNCDISPOSABLE
Func<ValueTask>? disposeAsync,
#endif
LevelOverrideMap? overrideMap)
LevelOverrideMapWrapper overrideMap,
string? contextName)
{
_messageTemplateProcessor = messageTemplateProcessor;
_minimumLevel = minimumLevel;
_sink = sink;
_dispose = dispose;
#if FEATURE_ASYNCDISPOSABLE
_disposeAsync = disposeAsync;
#endif
_levelSwitch = levelSwitch;
_overrideMap = overrideMap;
_contextName = contextName;
_enricher = enricher;

_overrideMap.OverridesChanged += OnOverridesChanged;

_dispose = () =>
{
_overrideMap.OverridesChanged -= OnOverridesChanged;
dispose?.Invoke();
};

#if FEATURE_ASYNCDISPOSABLE
_disposeAsync = () =>
{
_overrideMap.OverridesChanged -= OnOverridesChanged;
return disposeAsync?.Invoke() ?? new ValueTask();
};
#endif
}

void OnOverridesChanged(object? sender, EventArgs args)
{
if (_contextName != null)
{
_overrideMap.Map?.GetEffectiveLevel(_contextName, out _minimumLevel, out _levelSwitch);
}
}

internal bool HasOverrideMap => _overrideMap != null;
internal bool HasOverrideMap => _overrideMap.Map != null;

/// <summary>
/// Create a logger that enriches log events via the provided enrichers.
Expand All @@ -92,7 +115,8 @@ public ILogger ForContext(ILogEventEnricher enricher)
#if FEATURE_ASYNCDISPOSABLE
null,
#endif
_overrideMap);
_overrideMap,
_contextName);
}

/// <summary>
Expand Down Expand Up @@ -131,10 +155,10 @@ public ILogger ForContext(string propertyName, object? value, bool destructureOb

var minimumLevel = _minimumLevel;
var levelSwitch = _levelSwitch;
if (_overrideMap != null && propertyName == Constants.SourceContextPropertyName)
if (_overrideMap.Map != null && propertyName == Constants.SourceContextPropertyName)
{
if (value is string context)
_overrideMap.GetEffectiveLevel(context, out minimumLevel, out levelSwitch);
_overrideMap.Map.GetEffectiveLevel(context, out minimumLevel, out levelSwitch);
}

return new Logger(
Expand All @@ -147,7 +171,8 @@ public ILogger ForContext(string propertyName, object? value, bool destructureOb
#if FEATURE_ASYNCDISPOSABLE
null,
#endif
_overrideMap);
_overrideMap,
value as string);
}

/// <summary>
Expand Down
13 changes: 5 additions & 8 deletions src/Serilog/LoggerConfiguration.cs
Expand Up @@ -25,7 +25,7 @@ public class LoggerConfiguration
readonly List<ILogEventFilter> _filters = new();
readonly List<Type> _additionalScalarTypes = new();
readonly List<IDestructuringPolicy> _additionalDestructuringPolicies = new();
readonly Dictionary<string, LoggingLevelSwitch> _overrides = new();
readonly LevelOverrideMapWrapper _overrideWrapper = new();
LogEventLevel _minimumLevel = LogEventLevel.Information;
LoggingLevelSwitch? _levelSwitch;
int _maximumDestructuringDepth = 10;
Expand Down Expand Up @@ -77,7 +77,7 @@ public LoggerMinimumLevelConfiguration MinimumLevel
_levelSwitch = null;
},
sw => _levelSwitch = sw,
(s, lls) => _overrides[s] = lls);
_overrideWrapper);
}
}

Expand Down Expand Up @@ -166,11 +166,7 @@ public Logger CreateLogger()
break;
}

LevelOverrideMap? overrideMap = null;
if (_overrides.Count != 0)
{
overrideMap = new(_overrides, _minimumLevel, _levelSwitch);
}
_overrideWrapper.EnsureMap(_minimumLevel, _levelSwitch);

var disposableSinks = _logEventSinks
.Concat(_auditSinks)
Expand Down Expand Up @@ -215,6 +211,7 @@ async ValueTask DisposeAsync()
#if FEATURE_ASYNCDISPOSABLE
DisposeAsync,
#endif
overrideMap);
_overrideWrapper,
null);
}
}