Skip to content

Commit

Permalink
Merge pull request #162 from skomis-mm/filterswitch
Browse files Browse the repository at this point in the history
 LoggingFilterSwitch support
  • Loading branch information
nblumhardt authored Nov 23, 2020
2 parents aff1d22 + 9af2f30 commit 07d7a8d
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 7 deletions.
2 changes: 2 additions & 0 deletions sample/Sample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
using Serilog;
using Serilog.Core;
using Serilog.Events;
using System.Collections.Generic;
using Serilog.Debugging;

namespace Sample
{
Expand Down
5 changes: 3 additions & 2 deletions sample/Sample/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"Serilog": {
"Using": [ "Serilog.Sinks.Console" ],
"LevelSwitches": { "$controlSwitch": "Verbose" },
"FilterSwitches": { "$filterSwitch": "Application = 'Sample'" },
"MinimumLevel": {
"Default": "Debug",
"Override": {
Expand Down Expand Up @@ -97,9 +98,9 @@
],
"Filter": [
{
"Name": "ByIncludingOnly",
"Name": "ControlledBy",
"Args": {
"expression": "Application = 'Sample'"
"switch": "$filterSwitch"
}
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ internal ConfigurationReader(IConfigurationSection configSection, IReadOnlyColle
public void Configure(LoggerConfiguration loggerConfiguration)
{
ProcessLevelSwitchDeclarations();
ProcessFilterSwitchDeclarations();

ApplyMinimumLevel(loggerConfiguration);
ApplyEnrichment(loggerConfiguration);
Expand All @@ -50,6 +51,63 @@ public void Configure(LoggerConfiguration loggerConfiguration)
ApplyAuditSinks(loggerConfiguration);
}

void ProcessFilterSwitchDeclarations()
{
var filterSwitchesDirective = _section.GetSection("FilterSwitches");

foreach (var filterSwitchDeclaration in filterSwitchesDirective.GetChildren())
{
var filterSwitch = LoggingFilterSwitchProxy.Create();
if (filterSwitch == null)
{
SelfLog.WriteLine($"FilterSwitches section found, but neither Serilog.Expressions nor Serilog.Filters.Expressions is referenced.");
break;
}

var switchName = filterSwitchDeclaration.Key;
// switchName must be something like $switch to avoid ambiguities
if (!IsValidSwitchName(switchName))
{
throw new FormatException($"\"{switchName}\" is not a valid name for a Filter Switch declaration. Filter switch must be declared with a '$' sign, like \"FilterSwitches\" : {{\"$switchName\" : \"{{FilterExpression}}\"}}");
}

SetFilterSwitch(throwOnError: true);
SubscribeToFilterExpressionChanges();

_resolutionContext.AddFilterSwitch(switchName, filterSwitch);

void SubscribeToFilterExpressionChanges()
{
ChangeToken.OnChange(filterSwitchDeclaration.GetReloadToken, () => SetFilterSwitch(throwOnError: false));
}

void SetFilterSwitch(bool throwOnError)
{
var filterExpr = filterSwitchDeclaration.Value;
if (string.IsNullOrWhiteSpace(filterExpr))
{
filterSwitch.Expression = null;
return;
}

try
{
filterSwitch.Expression = filterExpr;
}
catch (Exception e)
{
var errMsg = $"The expression '{filterExpr}' is invalid filter expression: {e.Message}.";
if (throwOnError)
{
throw new InvalidOperationException(errMsg, e);
}

SelfLog.WriteLine(errMsg);
}
}
}
}

void ProcessLevelSwitchDeclarations()
{
var levelSwitchesDirective = _section.GetSection("LevelSwitches");
Expand Down Expand Up @@ -94,7 +152,7 @@ void ApplyMinimumLevel(LoggerConfiguration loggerConfiguration)
var minLevelControlledByDirective = minimumLevelDirective.GetSection("ControlledBy");
if (minLevelControlledByDirective.Value != null)
{
var globalMinimumLevelSwitch = _resolutionContext.LookUpSwitchByName(minLevelControlledByDirective.Value);
var globalMinimumLevelSwitch = _resolutionContext.LookUpLevelSwitchByName(minLevelControlledByDirective.Value);
// not calling ApplyMinimumLevel local function because here we have a reference to a LogLevelSwitch already
loggerConfiguration.MinimumLevel.ControlledBy(globalMinimumLevelSwitch);
}
Expand All @@ -109,7 +167,7 @@ void ApplyMinimumLevel(LoggerConfiguration loggerConfiguration)
}
else
{
var overrideSwitch = _resolutionContext.LookUpSwitchByName(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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;

namespace Serilog.Settings.Configuration
{
class LoggingFilterSwitchProxy
{
readonly Action<string> _setProxy;
readonly Func<string> _getProxy;

LoggingFilterSwitchProxy(object realSwitch)
{
RealSwitch = realSwitch ?? throw new ArgumentNullException(nameof(realSwitch));

var expressionProperty = realSwitch.GetType().GetProperty("Expression");

_setProxy = (Action<string>)Delegate.CreateDelegate(
typeof(Action<string>),
realSwitch,
expressionProperty.GetSetMethod());

_getProxy = (Func<string>)Delegate.CreateDelegate(
typeof(Func<string>),
realSwitch,
expressionProperty.GetGetMethod());
}

public object RealSwitch { get; }

public string Expression
{
get => _getProxy();
set => _setProxy(value);
}

public static LoggingFilterSwitchProxy Create(string expression = null)
{
var filterSwitchType =
Type.GetType("Serilog.Expressions.LoggingFilterSwitch, Serilog.Expressions") ??
Type.GetType("Serilog.Filters.Expressions.LoggingFilterSwitch, Serilog.Filters.Expressions");

if (filterSwitchType is null)
{
return null;
}

return new LoggingFilterSwitchProxy(Activator.CreateInstance(filterSwitchType, expression));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ namespace Serilog.Settings.Configuration
sealed class ResolutionContext
{
readonly IDictionary<string, LoggingLevelSwitch> _declaredLevelSwitches;
readonly IDictionary<string, LoggingFilterSwitchProxy> _declaredFilterSwitches;
readonly IConfiguration _appConfiguration;

public ResolutionContext(IConfiguration appConfiguration = null)
{
_declaredLevelSwitches = new Dictionary<string, LoggingLevelSwitch>();
_declaredFilterSwitches = new Dictionary<string, LoggingFilterSwitchProxy>();
_appConfiguration = appConfiguration;
}

Expand All @@ -26,7 +28,7 @@ public ResolutionContext(IConfiguration appConfiguration = null)
/// <param name="switchName">the name of a switch to look up</param>
/// <returns>the LoggingLevelSwitch registered with the name</returns>
/// <exception cref="InvalidOperationException">if no switch has been registered with <paramref name="switchName"/></exception>
public LoggingLevelSwitch LookUpSwitchByName(string switchName)
public LoggingLevelSwitch LookUpLevelSwitchByName(string switchName)
{
if (_declaredLevelSwitches.TryGetValue(switchName, out var levelSwitch))
{
Expand All @@ -36,6 +38,16 @@ public LoggingLevelSwitch LookUpSwitchByName(string switchName)
throw new InvalidOperationException($"No LoggingLevelSwitch has been declared with name \"{switchName}\". You might be missing a section \"LevelSwitches\":{{\"{switchName}\":\"InitialLevel\"}}");
}

public LoggingFilterSwitchProxy LookUpFilterSwitchByName(string switchName)
{
if (_declaredFilterSwitches.TryGetValue(switchName, out var filterSwitch))
{
return filterSwitch;
}

throw new InvalidOperationException($"No LoggingFilterSwitch has been declared with name \"{switchName}\". You might be missing a section \"FilterSwitches\":{{\"{switchName}\":\"{{FilterExpression}}\"}}");
}

public bool HasAppConfiguration => _appConfiguration != null;

public IConfiguration AppConfiguration
Expand All @@ -57,5 +69,12 @@ public void AddLevelSwitch(string levelSwitchName, LoggingLevelSwitch levelSwitc
if (levelSwitch == null) throw new ArgumentNullException(nameof(levelSwitch));
_declaredLevelSwitches[levelSwitchName] = levelSwitch;
}

public void AddFilterSwitch(string filterSwitchName, LoggingFilterSwitchProxy filterSwitch)
{
if (filterSwitchName == null) throw new ArgumentNullException(nameof(filterSwitchName));
if (filterSwitch == null) throw new ArgumentNullException(nameof(filterSwitch));
_declaredFilterSwitches[filterSwitchName] = filterSwitch;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,13 @@ public object ConvertTo(Type toType, ResolutionContext resolutionContext)

if (toType == typeof(LoggingLevelSwitch))
{
return resolutionContext.LookUpSwitchByName(argumentValue);
return resolutionContext.LookUpLevelSwitchByName(argumentValue);
}

if (toType.FullName == "Serilog.Expressions.LoggingFilterSwitch" ||
toType.FullName == "Serilog.Filters.Expressions.LoggingFilterSwitch")
{
return resolutionContext.LookUpFilterSwitchByName(argumentValue).RealSwitch;
}

var toTypeInfo = toType.GetTypeInfo();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,33 @@ public void LoggingLevelSwitchWithInvalidNameThrowsFormatException()
Assert.Contains("\"LevelSwitches\" : {\"$switchName\" :", ex.Message);
}

[Fact]
public void LoggingFilterSwitchIsConfigured()
{
var json = @"{
'Serilog': {
'FilterSwitches': { '$mySwitch': 'Prop = 42' },
'Filter:BySwitch': {
'Name': 'ControlledBy',
'Args': {
'switch': '$mySwitch'
}
}
}
}";
LogEvent evt = null;

var log = ConfigFromJson(json)
.WriteTo.Sink(new DelegatingSink(e => evt = e))
.CreateLogger();

log.Write(Some.InformationEvent());
Assert.Null(evt);

log.ForContext("Prop", 42).Write(Some.InformationEvent());
Assert.NotNull(evt);
}

[Fact]
public void LoggingLevelSwitchIsConfigured()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ public class DynamicLevelChangeTests
}
},
'LevelSwitches': { '$mySwitch': 'Information' },
'FilterSwitches': { '$myFilter': null },
'Filter:Dummy': {
'Name': 'ControlledBy',
'Args': {
'switch': '$myFilter'
}
},
'WriteTo:Dummy': {
'Name': 'DummyConsole',
'Args': {
Expand Down Expand Up @@ -64,10 +71,22 @@ public void ShouldRespectDynamicLevelChanges()
UpdateConfig(overrideLevel: LogEventLevel.Debug);
logger.ForContext(Constants.SourceContextPropertyName, "Root.Test").Write(Some.DebugEvent());
Assert.Single(DummyConsoleSink.Emitted);

DummyConsoleSink.Emitted.Clear();
UpdateConfig(filterExpression: "Prop = 'Val_1'");
logger.Write(Some.DebugEvent());
logger.ForContext("Prop", "Val_1").Write(Some.DebugEvent());
Assert.Single(DummyConsoleSink.Emitted);

DummyConsoleSink.Emitted.Clear();
UpdateConfig(filterExpression: "Prop = 'Val_2'");
logger.Write(Some.DebugEvent());
logger.ForContext("Prop", "Val_1").Write(Some.DebugEvent());
Assert.Empty(DummyConsoleSink.Emitted);
}
}

void UpdateConfig(LogEventLevel? minimumLevel = null, LogEventLevel? switchLevel = null, LogEventLevel? overrideLevel = null)
void UpdateConfig(LogEventLevel? minimumLevel = null, LogEventLevel? switchLevel = null, LogEventLevel? overrideLevel = null, string filterExpression = null)
{
if (minimumLevel.HasValue)
{
Expand All @@ -84,6 +103,11 @@ void UpdateConfig(LogEventLevel? minimumLevel = null, LogEventLevel? switchLevel
_configSource.Set("Serilog:MinimumLevel:Override:Root.Test", overrideLevel.Value.ToString());
}

if (filterExpression != null)
{
_configSource.Set("Serilog:FilterSwitches:$myFilter", filterExpression);
}

_configSource.Reload();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
<PackageReference Include="Serilog.Filters.Expressions" Version="2.0.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
<PackageReference Include="xunit" Version="2.2.0" />
</ItemGroup>
Expand Down

0 comments on commit 07d7a8d

Please sign in to comment.