Skip to content

Commit

Permalink
Merge pull request #160 from skomis-mm/issue99
Browse files Browse the repository at this point in the history
Respect dynamic logging level changes for LevelSwitch section
  • Loading branch information
Sergey Komisarchik committed Jan 7, 2019
2 parents ce614e6 + f835538 commit 9f2fb30
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 63 deletions.
9 changes: 6 additions & 3 deletions sample/Sample/appsettings.json
@@ -1,6 +1,7 @@
{
"Serilog": {
"Using": ["Serilog.Sinks.Console"],
"Using": [ "Serilog.Sinks.Console" ],
"LevelSwitches": { "$controlSwitch": "Verbose" },
"MinimumLevel": {
"Default": "Debug",
"Override": {
Expand All @@ -12,6 +13,7 @@
"Name": "Logger",
"Args": {
"configureLogger": {
"MinimumLevel": "Verbose",
"WriteTo": [
{
"Name": "Console",
Expand All @@ -22,7 +24,8 @@
}
]
},
"restrictedToMinimumLevel": "Debug"
"restrictedToMinimumLevel": "Verbose",
"levelSwitch": "$controlSwitch"
}
},
"WriteTo:Async": {
Expand All @@ -39,7 +42,7 @@
]
}
},
"Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"],
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
"Properties": {
"Application": "Sample"
},
Expand Down
Expand Up @@ -62,6 +62,7 @@ void ProcessLevelSwitchDeclarations()
{
throw new FormatException($"\"{switchName}\" is not a valid name for a Level Switch declaration. Level switch must be declared with a '$' sign, like \"LevelSwitches\" : {{\"$switchName\" : \"InitialLevel\"}}");
}

LoggingLevelSwitch newSwitch;
if (string.IsNullOrEmpty(switchInitialLevel))
{
Expand All @@ -72,6 +73,9 @@ void ProcessLevelSwitchDeclarations()
var initialLevel = ParseLogEventLevel(switchInitialLevel);
newSwitch = new LoggingLevelSwitch(initialLevel);
}

SubscribeToLoggingLevelChanges(levelSwitchDeclaration, newSwitch);

// make them available later on when resolving argument values
_resolutionContext.AddLevelSwitch(switchName, newSwitch);
}
Expand Down Expand Up @@ -118,18 +122,23 @@ void ApplyMinimumLevel(IConfigurationSection directive, Action<LoggerMinimumLeve
var levelSwitch = new LoggingLevelSwitch(minimumLevel);
applyConfigAction(loggerConfiguration.MinimumLevel, levelSwitch);

ChangeToken.OnChange(
directive.GetReloadToken,
() =>
{
if (Enum.TryParse(directive.Value, out minimumLevel))
levelSwitch.MinimumLevel = minimumLevel;
else
SelfLog.WriteLine($"The value {directive.Value} is not a valid Serilog level.");
});
SubscribeToLoggingLevelChanges(directive, levelSwitch);
}
}

void SubscribeToLoggingLevelChanges(IConfigurationSection levelSection, LoggingLevelSwitch levelSwitch)
{
ChangeToken.OnChange(
levelSection.GetReloadToken,
() =>
{
if (Enum.TryParse(levelSection.Value, out LogEventLevel minimumLevel))
levelSwitch.MinimumLevel = minimumLevel;
else
SelfLog.WriteLine($"The value {levelSection.Value} is not a valid Serilog level.");
});
}

void ApplyFilters(LoggerConfiguration loggerConfiguration)
{
var filterDirective = _section.GetSection("Filter");
Expand Down Expand Up @@ -233,7 +242,7 @@ IConfigurationArgumentValue GetArgumentValue(IConfigurationSection argumentSecti

if (argumentSection.Value != null)
{
argumentValue = new StringArgumentValue(() => argumentSection.Value, argumentSection.GetReloadToken);
argumentValue = new StringArgumentValue(argumentSection.Value);
}
else
{
Expand Down
Expand Up @@ -3,25 +3,20 @@
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Primitives;

using Serilog.Core;
using Serilog.Debugging;
using Serilog.Events;

namespace Serilog.Settings.Configuration
{
class StringArgumentValue : IConfigurationArgumentValue
{
readonly Func<string> _valueProducer;
readonly Func<IChangeToken> _changeTokenProducer;
readonly string _providedValue;

static readonly Regex StaticMemberAccessorRegex = new Regex("^(?<shortTypeName>[^:]+)::(?<memberName>[A-Za-z][A-Za-z0-9]*)(?<typeNameExtraQualifiers>[^:]*)$");

public StringArgumentValue(Func<string> valueProducer, Func<IChangeToken> changeTokenProducer = null)
public StringArgumentValue(string providedValue)
{
_valueProducer = valueProducer ?? throw new ArgumentNullException(nameof(valueProducer));
_changeTokenProducer = changeTokenProducer;
_providedValue = providedValue ?? throw new ArgumentNullException(nameof(providedValue));
}

static readonly Dictionary<Type, Func<string, object>> ExtendedTypeConversions = new Dictionary<Type, Func<string, object>>
Expand All @@ -33,7 +28,7 @@ public StringArgumentValue(Func<string> valueProducer, Func<IChangeToken> change

public object ConvertTo(Type toType, ResolutionContext resolutionContext)
{
var argumentValue = Environment.ExpandEnvironmentVariables(_valueProducer());
var argumentValue = Environment.ExpandEnvironmentVariables(_providedValue);

if (toType == typeof(LoggingLevelSwitch))
{
Expand Down Expand Up @@ -114,31 +109,6 @@ public object ConvertTo(Type toType, ResolutionContext resolutionContext)
}
}

if (toType == typeof(LoggingLevelSwitch))
{
if (!Enum.TryParse(argumentValue, out LogEventLevel minimumLevel))
throw new InvalidOperationException($"The value `{argumentValue}` is not a valid Serilog level.");

var levelSwitch = new LoggingLevelSwitch(minimumLevel);

if (_changeTokenProducer != null)
{
ChangeToken.OnChange(
_changeTokenProducer,
() =>
{
var newArgumentValue = _valueProducer();
if (Enum.TryParse(newArgumentValue, out minimumLevel))
levelSwitch.MinimumLevel = minimumLevel;
else
SelfLog.WriteLine($"The value `{newArgumentValue}` is not a valid Serilog level.");
});
}

return levelSwitch;
}

return Convert.ChangeType(argumentValue, toType);
}

Expand Down
@@ -0,0 +1,90 @@
using Serilog.Core;
using Serilog.Events;
using Serilog.Settings.Configuration.Tests.Support;

using Xunit;
using Microsoft.Extensions.Configuration;

using TestDummies.Console;

namespace Serilog.Settings.Configuration.Tests
{
public class DynamicLevelChangeTests
{
const string DefaultConfig = @"{
'Serilog': {
'Using': [ 'TestDummies' ],
'MinimumLevel': {
'Default': 'Information',
'Override': {
'Root.Test': 'Information'
}
},
'LevelSwitches': { '$mySwitch': 'Information' },
'WriteTo:Dummy': {
'Name': 'DummyConsole',
'Args': {
'levelSwitch': '$mySwitch'
}
}
}
}";

readonly ReloadableConfigurationSource _configSource;

public DynamicLevelChangeTests()
{
_configSource = new ReloadableConfigurationSource(JsonStringConfigSource.LoadData(DefaultConfig));
}

[Fact]
public void ShouldRespectDynamicLevelChanges()
{
using (var logger = new LoggerConfiguration()
.ReadFrom
.Configuration(new ConfigurationBuilder().Add(_configSource).Build())
.CreateLogger())
{
DummyConsoleSink.Emitted.Clear();
logger.Write(Some.DebugEvent());
Assert.Empty(DummyConsoleSink.Emitted);

DummyConsoleSink.Emitted.Clear();
UpdateConfig(minimumLevel: LogEventLevel.Debug);
logger.Write(Some.DebugEvent());
Assert.Empty(DummyConsoleSink.Emitted);

DummyConsoleSink.Emitted.Clear();
UpdateConfig(switchLevel: LogEventLevel.Debug);
logger.Write(Some.DebugEvent());
logger.ForContext(Constants.SourceContextPropertyName, "Root.Test").Write(Some.DebugEvent());
Assert.Single(DummyConsoleSink.Emitted);

DummyConsoleSink.Emitted.Clear();
UpdateConfig(overrideLevel: LogEventLevel.Debug);
logger.ForContext(Constants.SourceContextPropertyName, "Root.Test").Write(Some.DebugEvent());
Assert.Single(DummyConsoleSink.Emitted);
}
}

void UpdateConfig(LogEventLevel? minimumLevel = null, LogEventLevel? switchLevel = null, LogEventLevel? overrideLevel = null)
{
if (minimumLevel.HasValue)
{
_configSource.Set("Serilog:MinimumLevel:Default", minimumLevel.Value.ToString());
}

if (switchLevel.HasValue)
{
_configSource.Set("Serilog:LevelSwitches:$mySwitch", switchLevel.Value.ToString());
}

if (overrideLevel.HasValue)
{
_configSource.Set("Serilog:MinimumLevel:Override:Root.Test", overrideLevel.Value.ToString());
}

_configSource.Reload();
}
}
}
Expand Up @@ -15,7 +15,7 @@ public class StringArgumentValueTests
[Fact]
public void StringValuesConvertToDefaultInstancesIfTargetIsInterface()
{
var stringArgumentValue = new StringArgumentValue(() => "Serilog.Formatting.Json.JsonFormatter, Serilog");
var stringArgumentValue = new StringArgumentValue("Serilog.Formatting.Json.JsonFormatter, Serilog");

var result = stringArgumentValue.ConvertTo(typeof(ITextFormatter), new ResolutionContext());

Expand All @@ -25,7 +25,7 @@ public void StringValuesConvertToDefaultInstancesIfTargetIsInterface()
[Fact]
public void StringValuesConvertToDefaultInstancesIfTargetIsAbstractClass()
{
var stringArgumentValue = new StringArgumentValue(() => "Serilog.Settings.Configuration.Tests.Support.ConcreteClass, Serilog.Settings.Configuration.Tests");
var stringArgumentValue = new StringArgumentValue("Serilog.Settings.Configuration.Tests.Support.ConcreteClass, Serilog.Settings.Configuration.Tests");

var result = stringArgumentValue.ConvertTo(typeof(AbstractClass), new ResolutionContext());

Expand Down Expand Up @@ -91,7 +91,7 @@ public void FindTypeSupportsSimpleNamesForSerilogTypes(string input, Type target
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::AbstractField, Serilog.Settings.Configuration.Tests", typeof(AnAbstractClass))]
public void StaticMembersAccessorsCanBeUsedForReferenceTypes(string input, Type targetType)
{
var stringArgumentValue = new StringArgumentValue(() => $"{input}");
var stringArgumentValue = new StringArgumentValue($"{input}");

var actual = stringArgumentValue.ConvertTo(targetType, new ResolutionContext());

Expand All @@ -108,7 +108,7 @@ public void StaticMembersAccessorsCanBeUsedForReferenceTypes(string input, Type
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::InterfaceProperty", typeof(IAmAnInterface))]
public void StaticAccessorOnUnknownTypeThrowsTypeLoadException(string input, Type targetType)
{
var stringArgumentValue = new StringArgumentValue(() => $"{input}");
var stringArgumentValue = new StringArgumentValue($"{input}");
Assert.Throws<TypeLoadException>(() =>
stringArgumentValue.ConvertTo(targetType, new ResolutionContext())
);
Expand All @@ -127,7 +127,7 @@ public void StaticAccessorOnUnknownTypeThrowsTypeLoadException(string input, Typ
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::InstanceInterfaceField, Serilog.Settings.Configuration.Tests", typeof(IAmAnInterface))]
public void StaticAccessorWithInvalidMemberThrowsInvalidOperationException(string input, Type targetType)
{
var stringArgumentValue = new StringArgumentValue(() => $"{input}");
var stringArgumentValue = new StringArgumentValue($"{input}");
var exception = Assert.Throws<InvalidOperationException>(() =>
stringArgumentValue.ConvertTo(targetType, new ResolutionContext())
);
Expand All @@ -144,7 +144,7 @@ public void LevelSwitchesCanBeLookedUpByName()
var resolutionContext = new ResolutionContext();
resolutionContext.AddLevelSwitch(switchName, @switch);

var stringArgumentValue = new StringArgumentValue(() => switchName);
var stringArgumentValue = new StringArgumentValue(switchName);

var resolvedSwitch = stringArgumentValue.ConvertTo(typeof(LoggingLevelSwitch), resolutionContext);

Expand All @@ -159,7 +159,7 @@ public void ReferencingUndeclaredLevelSwitchThrows()
var resolutionContext = new ResolutionContext();
resolutionContext.AddLevelSwitch("$anotherSwitch", new LoggingLevelSwitch(LogEventLevel.Verbose));

var stringArgumentValue = new StringArgumentValue(() => "$mySwitch");
var stringArgumentValue = new StringArgumentValue("$mySwitch");

var ex = Assert.Throws<InvalidOperationException>(() =>
stringArgumentValue.ConvertTo(typeof(LoggingLevelSwitch), resolutionContext)
Expand All @@ -173,7 +173,7 @@ public void ReferencingUndeclaredLevelSwitchThrows()
public void StringValuesConvertToTypeFromShortTypeName()
{
var shortTypeName = "System.Version";
var stringArgumentValue = new StringArgumentValue(() => shortTypeName);
var stringArgumentValue = new StringArgumentValue(shortTypeName);

var actual = (Type)stringArgumentValue.ConvertTo(typeof(Type), new ResolutionContext());

Expand All @@ -184,7 +184,7 @@ public void StringValuesConvertToTypeFromShortTypeName()
public void StringValuesConvertToTypeFromAssemblyQualifiedName()
{
var assemblyQualifiedName = typeof(Version).AssemblyQualifiedName;
var stringArgumentValue = new StringArgumentValue(() => assemblyQualifiedName);
var stringArgumentValue = new StringArgumentValue(assemblyQualifiedName);

var actual = (Type)stringArgumentValue.ConvertTo(typeof(Type), new ResolutionContext());

Expand Down
@@ -1,8 +1,9 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;

using System.Collections.Generic;
using System.IO;

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;

namespace Serilog.Settings.Configuration.Tests.Support
{
class JsonStringConfigSource : IConfigurationSource
Expand All @@ -24,6 +25,13 @@ public static IConfigurationSection LoadSection(string json, string section)
return new ConfigurationBuilder().Add(new JsonStringConfigSource(json)).Build().GetSection(section);
}

public static IDictionary<string, string> LoadData(string json)
{
var provider = new JsonStringConfigProvider(json);
provider.Load();
return provider.Data;
}

class JsonStringConfigProvider : JsonConfigurationProvider
{
readonly string _json;
Expand All @@ -33,6 +41,8 @@ public JsonStringConfigProvider(string json) : base(new JsonConfigurationSource
_json = json;
}

public new IDictionary<string, string> Data => base.Data;

public override void Load()
{
Load(StringToStream(_json));
Expand Down

0 comments on commit 9f2fb30

Please sign in to comment.