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

Respect dynamic logging level changes for LevelSwitch section #160

Merged
merged 5 commits into from Jan 7, 2019
Merged
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
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 @@ -81,7 +81,7 @@ public void TryParseStaticMemberAccessorReturnsExpectedResults(string input, str
[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 @@ -98,7 +98,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 @@ -117,7 +117,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 @@ -134,7 +134,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 @@ -149,7 +149,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 @@ -163,7 +163,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 @@ -174,7 +174,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