Skip to content

Commit 9f2fb30

Browse files
author
Sergey Komisarchik
authored
Merge pull request #160 from skomis-mm/issue99
Respect dynamic logging level changes for LevelSwitch section
2 parents ce614e6 + f835538 commit 9f2fb30

File tree

9 files changed

+183
-63
lines changed

9 files changed

+183
-63
lines changed

sample/Sample/appsettings.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"Serilog": {
3-
"Using": ["Serilog.Sinks.Console"],
3+
"Using": [ "Serilog.Sinks.Console" ],
4+
"LevelSwitches": { "$controlSwitch": "Verbose" },
45
"MinimumLevel": {
56
"Default": "Debug",
67
"Override": {
@@ -12,6 +13,7 @@
1213
"Name": "Logger",
1314
"Args": {
1415
"configureLogger": {
16+
"MinimumLevel": "Verbose",
1517
"WriteTo": [
1618
{
1719
"Name": "Console",
@@ -22,7 +24,8 @@
2224
}
2325
]
2426
},
25-
"restrictedToMinimumLevel": "Debug"
27+
"restrictedToMinimumLevel": "Verbose",
28+
"levelSwitch": "$controlSwitch"
2629
}
2730
},
2831
"WriteTo:Async": {
@@ -39,7 +42,7 @@
3942
]
4043
}
4144
},
42-
"Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"],
45+
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
4346
"Properties": {
4447
"Application": "Sample"
4548
},

src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ void ProcessLevelSwitchDeclarations()
6262
{
6363
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\"}}");
6464
}
65+
6566
LoggingLevelSwitch newSwitch;
6667
if (string.IsNullOrEmpty(switchInitialLevel))
6768
{
@@ -72,6 +73,9 @@ void ProcessLevelSwitchDeclarations()
7273
var initialLevel = ParseLogEventLevel(switchInitialLevel);
7374
newSwitch = new LoggingLevelSwitch(initialLevel);
7475
}
76+
77+
SubscribeToLoggingLevelChanges(levelSwitchDeclaration, newSwitch);
78+
7579
// make them available later on when resolving argument values
7680
_resolutionContext.AddLevelSwitch(switchName, newSwitch);
7781
}
@@ -118,18 +122,23 @@ void ApplyMinimumLevel(IConfigurationSection directive, Action<LoggerMinimumLeve
118122
var levelSwitch = new LoggingLevelSwitch(minimumLevel);
119123
applyConfigAction(loggerConfiguration.MinimumLevel, levelSwitch);
120124

121-
ChangeToken.OnChange(
122-
directive.GetReloadToken,
123-
() =>
124-
{
125-
if (Enum.TryParse(directive.Value, out minimumLevel))
126-
levelSwitch.MinimumLevel = minimumLevel;
127-
else
128-
SelfLog.WriteLine($"The value {directive.Value} is not a valid Serilog level.");
129-
});
125+
SubscribeToLoggingLevelChanges(directive, levelSwitch);
130126
}
131127
}
132128

129+
void SubscribeToLoggingLevelChanges(IConfigurationSection levelSection, LoggingLevelSwitch levelSwitch)
130+
{
131+
ChangeToken.OnChange(
132+
levelSection.GetReloadToken,
133+
() =>
134+
{
135+
if (Enum.TryParse(levelSection.Value, out LogEventLevel minimumLevel))
136+
levelSwitch.MinimumLevel = minimumLevel;
137+
else
138+
SelfLog.WriteLine($"The value {levelSection.Value} is not a valid Serilog level.");
139+
});
140+
}
141+
133142
void ApplyFilters(LoggerConfiguration loggerConfiguration)
134143
{
135144
var filterDirective = _section.GetSection("Filter");
@@ -233,7 +242,7 @@ IConfigurationArgumentValue GetArgumentValue(IConfigurationSection argumentSecti
233242

234243
if (argumentSection.Value != null)
235244
{
236-
argumentValue = new StringArgumentValue(() => argumentSection.Value, argumentSection.GetReloadToken);
245+
argumentValue = new StringArgumentValue(argumentSection.Value);
237246
}
238247
else
239248
{

src/Serilog.Settings.Configuration/Settings/Configuration/StringArgumentValue.cs

Lines changed: 4 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,20 @@
33
using System.Linq;
44
using System.Reflection;
55
using System.Text.RegularExpressions;
6-
using Microsoft.Extensions.Primitives;
76

87
using Serilog.Core;
9-
using Serilog.Debugging;
10-
using Serilog.Events;
118

129
namespace Serilog.Settings.Configuration
1310
{
1411
class StringArgumentValue : IConfigurationArgumentValue
1512
{
16-
readonly Func<string> _valueProducer;
17-
readonly Func<IChangeToken> _changeTokenProducer;
13+
readonly string _providedValue;
1814

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

21-
public StringArgumentValue(Func<string> valueProducer, Func<IChangeToken> changeTokenProducer = null)
17+
public StringArgumentValue(string providedValue)
2218
{
23-
_valueProducer = valueProducer ?? throw new ArgumentNullException(nameof(valueProducer));
24-
_changeTokenProducer = changeTokenProducer;
19+
_providedValue = providedValue ?? throw new ArgumentNullException(nameof(providedValue));
2520
}
2621

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

3429
public object ConvertTo(Type toType, ResolutionContext resolutionContext)
3530
{
36-
var argumentValue = Environment.ExpandEnvironmentVariables(_valueProducer());
31+
var argumentValue = Environment.ExpandEnvironmentVariables(_providedValue);
3732

3833
if (toType == typeof(LoggingLevelSwitch))
3934
{
@@ -114,31 +109,6 @@ public object ConvertTo(Type toType, ResolutionContext resolutionContext)
114109
}
115110
}
116111

117-
if (toType == typeof(LoggingLevelSwitch))
118-
{
119-
if (!Enum.TryParse(argumentValue, out LogEventLevel minimumLevel))
120-
throw new InvalidOperationException($"The value `{argumentValue}` is not a valid Serilog level.");
121-
122-
var levelSwitch = new LoggingLevelSwitch(minimumLevel);
123-
124-
if (_changeTokenProducer != null)
125-
{
126-
ChangeToken.OnChange(
127-
_changeTokenProducer,
128-
() =>
129-
{
130-
var newArgumentValue = _valueProducer();
131-
132-
if (Enum.TryParse(newArgumentValue, out minimumLevel))
133-
levelSwitch.MinimumLevel = minimumLevel;
134-
else
135-
SelfLog.WriteLine($"The value `{newArgumentValue}` is not a valid Serilog level.");
136-
});
137-
}
138-
139-
return levelSwitch;
140-
}
141-
142112
return Convert.ChangeType(argumentValue, toType);
143113
}
144114

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using Serilog.Core;
2+
using Serilog.Events;
3+
using Serilog.Settings.Configuration.Tests.Support;
4+
5+
using Xunit;
6+
using Microsoft.Extensions.Configuration;
7+
8+
using TestDummies.Console;
9+
10+
namespace Serilog.Settings.Configuration.Tests
11+
{
12+
public class DynamicLevelChangeTests
13+
{
14+
const string DefaultConfig = @"{
15+
'Serilog': {
16+
'Using': [ 'TestDummies' ],
17+
'MinimumLevel': {
18+
'Default': 'Information',
19+
'Override': {
20+
'Root.Test': 'Information'
21+
}
22+
},
23+
'LevelSwitches': { '$mySwitch': 'Information' },
24+
'WriteTo:Dummy': {
25+
'Name': 'DummyConsole',
26+
'Args': {
27+
'levelSwitch': '$mySwitch'
28+
}
29+
}
30+
}
31+
}";
32+
33+
readonly ReloadableConfigurationSource _configSource;
34+
35+
public DynamicLevelChangeTests()
36+
{
37+
_configSource = new ReloadableConfigurationSource(JsonStringConfigSource.LoadData(DefaultConfig));
38+
}
39+
40+
[Fact]
41+
public void ShouldRespectDynamicLevelChanges()
42+
{
43+
using (var logger = new LoggerConfiguration()
44+
.ReadFrom
45+
.Configuration(new ConfigurationBuilder().Add(_configSource).Build())
46+
.CreateLogger())
47+
{
48+
DummyConsoleSink.Emitted.Clear();
49+
logger.Write(Some.DebugEvent());
50+
Assert.Empty(DummyConsoleSink.Emitted);
51+
52+
DummyConsoleSink.Emitted.Clear();
53+
UpdateConfig(minimumLevel: LogEventLevel.Debug);
54+
logger.Write(Some.DebugEvent());
55+
Assert.Empty(DummyConsoleSink.Emitted);
56+
57+
DummyConsoleSink.Emitted.Clear();
58+
UpdateConfig(switchLevel: LogEventLevel.Debug);
59+
logger.Write(Some.DebugEvent());
60+
logger.ForContext(Constants.SourceContextPropertyName, "Root.Test").Write(Some.DebugEvent());
61+
Assert.Single(DummyConsoleSink.Emitted);
62+
63+
DummyConsoleSink.Emitted.Clear();
64+
UpdateConfig(overrideLevel: LogEventLevel.Debug);
65+
logger.ForContext(Constants.SourceContextPropertyName, "Root.Test").Write(Some.DebugEvent());
66+
Assert.Single(DummyConsoleSink.Emitted);
67+
}
68+
}
69+
70+
void UpdateConfig(LogEventLevel? minimumLevel = null, LogEventLevel? switchLevel = null, LogEventLevel? overrideLevel = null)
71+
{
72+
if (minimumLevel.HasValue)
73+
{
74+
_configSource.Set("Serilog:MinimumLevel:Default", minimumLevel.Value.ToString());
75+
}
76+
77+
if (switchLevel.HasValue)
78+
{
79+
_configSource.Set("Serilog:LevelSwitches:$mySwitch", switchLevel.Value.ToString());
80+
}
81+
82+
if (overrideLevel.HasValue)
83+
{
84+
_configSource.Set("Serilog:MinimumLevel:Override:Root.Test", overrideLevel.Value.ToString());
85+
}
86+
87+
_configSource.Reload();
88+
}
89+
}
90+
}

test/Serilog.Settings.Configuration.Tests/StringArgumentValueTests.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class StringArgumentValueTests
1515
[Fact]
1616
public void StringValuesConvertToDefaultInstancesIfTargetIsInterface()
1717
{
18-
var stringArgumentValue = new StringArgumentValue(() => "Serilog.Formatting.Json.JsonFormatter, Serilog");
18+
var stringArgumentValue = new StringArgumentValue("Serilog.Formatting.Json.JsonFormatter, Serilog");
1919

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

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

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

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

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

@@ -108,7 +108,7 @@ public void StaticMembersAccessorsCanBeUsedForReferenceTypes(string input, Type
108108
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::InterfaceProperty", typeof(IAmAnInterface))]
109109
public void StaticAccessorOnUnknownTypeThrowsTypeLoadException(string input, Type targetType)
110110
{
111-
var stringArgumentValue = new StringArgumentValue(() => $"{input}");
111+
var stringArgumentValue = new StringArgumentValue($"{input}");
112112
Assert.Throws<TypeLoadException>(() =>
113113
stringArgumentValue.ConvertTo(targetType, new ResolutionContext())
114114
);
@@ -127,7 +127,7 @@ public void StaticAccessorOnUnknownTypeThrowsTypeLoadException(string input, Typ
127127
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::InstanceInterfaceField, Serilog.Settings.Configuration.Tests", typeof(IAmAnInterface))]
128128
public void StaticAccessorWithInvalidMemberThrowsInvalidOperationException(string input, Type targetType)
129129
{
130-
var stringArgumentValue = new StringArgumentValue(() => $"{input}");
130+
var stringArgumentValue = new StringArgumentValue($"{input}");
131131
var exception = Assert.Throws<InvalidOperationException>(() =>
132132
stringArgumentValue.ConvertTo(targetType, new ResolutionContext())
133133
);
@@ -144,7 +144,7 @@ public void LevelSwitchesCanBeLookedUpByName()
144144
var resolutionContext = new ResolutionContext();
145145
resolutionContext.AddLevelSwitch(switchName, @switch);
146146

147-
var stringArgumentValue = new StringArgumentValue(() => switchName);
147+
var stringArgumentValue = new StringArgumentValue(switchName);
148148

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

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

162-
var stringArgumentValue = new StringArgumentValue(() => "$mySwitch");
162+
var stringArgumentValue = new StringArgumentValue("$mySwitch");
163163

164164
var ex = Assert.Throws<InvalidOperationException>(() =>
165165
stringArgumentValue.ConvertTo(typeof(LoggingLevelSwitch), resolutionContext)
@@ -173,7 +173,7 @@ public void ReferencingUndeclaredLevelSwitchThrows()
173173
public void StringValuesConvertToTypeFromShortTypeName()
174174
{
175175
var shortTypeName = "System.Version";
176-
var stringArgumentValue = new StringArgumentValue(() => shortTypeName);
176+
var stringArgumentValue = new StringArgumentValue(shortTypeName);
177177

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

@@ -184,7 +184,7 @@ public void StringValuesConvertToTypeFromShortTypeName()
184184
public void StringValuesConvertToTypeFromAssemblyQualifiedName()
185185
{
186186
var assemblyQualifiedName = typeof(Version).AssemblyQualifiedName;
187-
var stringArgumentValue = new StringArgumentValue(() => assemblyQualifiedName);
187+
var stringArgumentValue = new StringArgumentValue(assemblyQualifiedName);
188188

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

test/Serilog.Settings.Configuration.Tests/Support/JsonStringConfigSource.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
using Microsoft.Extensions.Configuration;
2-
using Microsoft.Extensions.Configuration.Json;
3-
1+
using System.Collections.Generic;
42
using System.IO;
53

4+
using Microsoft.Extensions.Configuration;
5+
using Microsoft.Extensions.Configuration.Json;
6+
67
namespace Serilog.Settings.Configuration.Tests.Support
78
{
89
class JsonStringConfigSource : IConfigurationSource
@@ -24,6 +25,13 @@ public static IConfigurationSection LoadSection(string json, string section)
2425
return new ConfigurationBuilder().Add(new JsonStringConfigSource(json)).Build().GetSection(section);
2526
}
2627

28+
public static IDictionary<string, string> LoadData(string json)
29+
{
30+
var provider = new JsonStringConfigProvider(json);
31+
provider.Load();
32+
return provider.Data;
33+
}
34+
2735
class JsonStringConfigProvider : JsonConfigurationProvider
2836
{
2937
readonly string _json;
@@ -33,6 +41,8 @@ public JsonStringConfigProvider(string json) : base(new JsonConfigurationSource
3341
_json = json;
3442
}
3543

44+
public new IDictionary<string, string> Data => base.Data;
45+
3646
public override void Load()
3747
{
3848
Load(StringToStream(_json));

0 commit comments

Comments
 (0)