Skip to content

Add support for LoggingLevelSwitch #88

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

Merged
merged 2 commits into from
Feb 13, 2018
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
root = true

[*]
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 4

[*.{csproj,json,config,yml}]
indent_size = 2

[*.sh]
end_of_line = lf

[*.{cmd, bat}]
end_of_line = crlf
2 changes: 2 additions & 0 deletions serilog-settings-configuration.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4E41FD57-5FA
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{62D0B904-1D11-4962-A4A8-DE28672AA28B}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
appveyor.yml = appveyor.yml
Build.ps1 = Build.ps1
CHANGES.md = CHANGES.md
LICENSE = LICENSE
README.md = README.md
serilog-settings-configuration.sln.DotSettings = serilog-settings-configuration.sln.DotSettings
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{D551DCB0-7771-4D01-BEBD-F7B57D1CF0E3}"
Expand Down
559 changes: 559 additions & 0 deletions serilog-settings-configuration.sln.DotSettings

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Serilog.Configuration;
using System;
using System.Collections.Generic;
using System.Reflection;
using Serilog.Core;

namespace Serilog.Settings.Configuration
{
Expand All @@ -13,10 +15,10 @@ public ConfigurationSectionArgumentValue(IConfigurationReader configReader)
_configReader = configReader ?? throw new ArgumentNullException(nameof(configReader));
}

public object ConvertTo(Type toType)
public object ConvertTo(Type toType, IReadOnlyDictionary<string, LoggingLevelSwitch> declaredLevelSwitches)
{
var typeInfo = toType.GetTypeInfo();
if (!typeInfo.IsGenericType ||
if (!typeInfo.IsGenericType ||
typeInfo.GetGenericTypeDefinition() is Type genericType && genericType != typeof(Action<>))
{
throw new InvalidOperationException("Argument value should be of type Action<>.");
Expand All @@ -25,7 +27,7 @@ public object ConvertTo(Type toType)
var configurationType = typeInfo.GenericTypeArguments[0];
if (configurationType == typeof(LoggerSinkConfiguration))
{
return new Action<LoggerSinkConfiguration>(_configReader.ApplySinks);
return new Action<LoggerSinkConfiguration>(loggerSinkConfig => _configReader.ApplySinks(loggerSinkConfig, declaredLevelSwitches));
}

if (configurationType == typeof(LoggerConfiguration))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using Serilog.Core;

namespace Serilog.Settings.Configuration
{
interface IConfigurationArgumentValue
{
object ConvertTo(Type toType);
object ConvertTo(Type toType, IReadOnlyDictionary<string, LoggingLevelSwitch> declaredLevelSwitches);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using Serilog.Configuration;
using System.Collections.Generic;
using Serilog.Configuration;
using Serilog.Core;

namespace Serilog.Settings.Configuration
{
interface IConfigurationReader : ILoggerSettings
{
void ApplySinks(LoggerSinkConfiguration loggerSinkConfiguration);
void ApplySinks(LoggerSinkConfiguration loggerSinkConfiguration, IReadOnlyDictionary<string, LoggingLevelSwitch> declaredLevelSwitches);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using Serilog.Core;

namespace Serilog.Settings.Configuration
{
internal static class LevelSwitchDictionaryExtensions
{
/// <summary>
/// Looks up a switch in the declared LoggingLevelSwitches
/// </summary>
/// <param name="namedLevelSwitches">the dictionary of switches to look up by name</param>
/// <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 static LoggingLevelSwitch LookUpSwitchByName(this IReadOnlyDictionary<string, LoggingLevelSwitch> namedLevelSwitches, string switchName)
{
if (namedLevelSwitches == null) throw new ArgumentNullException(nameof(namedLevelSwitches));
if (namedLevelSwitches.TryGetValue(switchName, out var levelSwitch))
{
return levelSwitch;
}

throw new InvalidOperationException($"No LoggingLevelSwitch has been declared with name \"{switchName}\". You might be missing a section \"LevelSwitches\":{{\"{switchName}\":\"InitialLevel\"}}");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class StringArgumentValue : IConfigurationArgumentValue
readonly Func<string> _valueProducer;
readonly Func<IChangeToken> _changeTokenProducer;

private static readonly Regex StaticMemberAccessorRegex = new Regex("^(?<shortTypeName>[^:]+)::(?<memberName>[A-Za-z][A-Za-z0-9]*)(?<typeNameExtraQualifiers>[^:]*)$");
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)
{
Expand All @@ -30,10 +30,15 @@ public StringArgumentValue(Func<string> valueProducer, Func<IChangeToken> change
{ typeof(TimeSpan), s => TimeSpan.Parse(s) }
};

public object ConvertTo(Type toType)
public object ConvertTo(Type toType, IReadOnlyDictionary<string, LoggingLevelSwitch> declaredLevelSwitches)
{
var argumentValue = Environment.ExpandEnvironmentVariables(_valueProducer());

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

var toTypeInfo = toType.GetTypeInfo();
if (toTypeInfo.IsGenericType && toType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Xunit;
using System.Reflection;
using System.Linq;
using Serilog.Core;
using Serilog.Settings.Configuration.Tests.Support;

namespace Serilog.Settings.Configuration.Tests
Expand Down Expand Up @@ -70,11 +71,11 @@ public void WriteToSupportExpandedSyntaxWithArgs()

Assert.Equal(1, result["LiterateConsole"].Count());

var args = result["LiterateConsole"].Single().Cast<KeyValuePair<string, IConfigurationArgumentValue>>().ToArray();
var args = result["LiterateConsole"].Single().ToArray();

Assert.Equal(1, args.Length);
Assert.Equal("outputTemplate", args[0].Key);
Assert.Equal("{Message}", args[0].Value.ConvertTo(typeof(string)));
Assert.Equal("{Message}", args[0].Value.ConvertTo(typeof(string), new Dictionary<string, LoggingLevelSwitch>()));
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using Microsoft.Extensions.Configuration;
using Serilog.Core;
using Serilog.Events;
using Serilog.Settings.Configuration.Tests.Support;
using TestDummies;
Expand All @@ -11,7 +12,7 @@ namespace Serilog.Settings.Configuration.Tests
{
public class ConfigurationSettingsTests
{
private static LoggerConfiguration ConfigFromJson(string jsonString)
static LoggerConfiguration ConfigFromJson(string jsonString)
{
var config = new ConfigurationBuilder().AddJsonString(jsonString).Build();
return new LoggerConfiguration()
Expand Down Expand Up @@ -197,5 +198,197 @@ public void SinksAreConfiguredWithStaticMember()

Assert.Equal(ConsoleThemes.Theme1, DummyConsoleSink.Theme);
}


[Theory]
[InlineData("$switchName", true)]
[InlineData("$SwitchName", true)]
[InlineData("$switch1", true)]
[InlineData("$sw1tch0", true)]
[InlineData("$SWITCHNAME", true)]
[InlineData("$$switchname", false)]
[InlineData("$switchname$", false)]
[InlineData("switch$name", false)]
[InlineData("$", false)]
[InlineData("", false)]
[InlineData(" ", false)]
[InlineData("$1switch", false)]
[InlineData("$switch_name", false)]
public void LoggingLevelSwitchNameValidityScenarios(string switchName, bool expectedValid)
{
Assert.True(ConfigurationReader.IsValidSwitchName(switchName) == expectedValid,
$"expected IsValidSwitchName({switchName}) to return {expectedValid} ");
}

[Fact]
public void LoggingLevelSwitchWithInvalidNameThrowsFormatException()
{
var json = @"{
""Serilog"": {
""LevelSwitches"": {""switchNameNotStartingWithDollar"" : ""Warning"" }
}
}";

var ex = Assert.Throws<FormatException>(() => ConfigFromJson(json));

Assert.Contains("\"switchNameNotStartingWithDollar\"", ex.Message);
Assert.Contains("'$' sign", ex.Message);
Assert.Contains("\"LevelSwitches\" : {\"$switchName\" :", ex.Message);
}

[Fact]
public void LoggingLevelSwitchIsConfigured()
{
var json = @"{
""Serilog"": {
""LevelSwitches"": {""$switch1"" : ""Warning"" },
""MinimumLevel"" : {
""ControlledBy"" : ""$switch1""
}
}
}";
LogEvent evt = null;

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

log.Write(Some.DebugEvent());
Assert.True(evt is null, "LoggingLevelSwitch initial level was Warning. It should not log Debug messages");
log.Write(Some.InformationEvent());
Assert.True(evt is null, "LoggingLevelSwitch initial level was Warning. It should not log Information messages");
log.Write(Some.WarningEvent());
Assert.True(evt != null, "LoggingLevelSwitch initial level was Warning. It should log Warning messages");
}

[Fact]
public void SettingMinimumLevelControlledByToAnUndeclaredSwitchThrows()
{
var json = @"{
""Serilog"": {
""LevelSwitches"": {""$switch1"" : ""Warning"" },
""MinimumLevel"" : {
""ControlledBy"" : ""$switch2""
}
}
}";

var ex = Assert.Throws<InvalidOperationException>(() =>
ConfigFromJson(json)
.CreateLogger());

Assert.Contains("$switch2", ex.Message);
Assert.Contains("\"LevelSwitches\":{\"$switch2\":", ex.Message);
}

[Fact]
public void LoggingLevelSwitchIsPassedToSinks()
{
var json = @"{
""Serilog"": {
""Using"": [""TestDummies""],
""LevelSwitches"": {""$switch1"" : ""Information"" },
""MinimumLevel"" : {
""ControlledBy"" : ""$switch1""
},
""WriteTo"": [{
""Name"": ""DummyWithLevelSwitch"",
""Args"": {""controlLevelSwitch"" : ""$switch1""}
}]
}
}";

LogEvent evt = null;

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

Assert.False(DummyWithLevelSwitchSink.ControlLevelSwitch == null, "Sink ControlLevelSwitch should have been initialized");

var controlSwitch = DummyWithLevelSwitchSink.ControlLevelSwitch;
Assert.NotNull(controlSwitch);

log.Write(Some.DebugEvent());
Assert.True(evt is null, "LoggingLevelSwitch initial level was information. It should not log Debug messages");

controlSwitch.MinimumLevel = LogEventLevel.Debug;
log.Write(Some.DebugEvent());
Assert.True(evt != null, "LoggingLevelSwitch level was changed to Debug. It should log Debug messages");
}

[Fact]
public void ReferencingAnUndeclaredSwitchInSinkThrows()
{
var json = @"{
""Serilog"": {
""Using"": [""TestDummies""],
""LevelSwitches"": {""$switch1"" : ""Information"" },
""MinimumLevel"" : {
""ControlledBy"" : ""$switch1""
},
""WriteTo"": [{
""Name"": ""DummyWithLevelSwitch"",
""Args"": {""controlLevelSwitch"" : ""$switch2""}
}]
}
}";

var ex = Assert.Throws<InvalidOperationException>(() =>
ConfigFromJson(json)
.CreateLogger());

Assert.Contains("$switch2", ex.Message);
Assert.Contains("\"LevelSwitches\":{\"$switch2\":", ex.Message);
}

[Fact]
public void LoggingLevelSwitchCanBeUsedForMinimumLevelOverrides()
{
var json = @"{
""Serilog"": {
""Using"": [""TestDummies""],
""LevelSwitches"": {""$specificSwitch"" : ""Warning"" },
""MinimumLevel"" : {
""Default"" : ""Debug"",
""Override"" : {
""System"" : ""$specificSwitch""
}
},
""WriteTo"": [{
""Name"": ""DummyWithLevelSwitch"",
""Args"": {""controlLevelSwitch"" : ""$specificSwitch""}
}]
}
}";

LogEvent evt = null;

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

var systemLogger = log.ForContext(Constants.SourceContextPropertyName, "System.Bar");

log.Write(Some.InformationEvent());
Assert.False(evt is null, "Minimum level is Debug. It should log Information messages");

evt = null;
// ReSharper disable HeuristicUnreachableCode
systemLogger.Write(Some.InformationEvent());
Assert.True(evt is null, "LoggingLevelSwitch initial level was Warning for logger System.*. It should not log Information messages for SourceContext System.Bar");

systemLogger.Write(Some.WarningEvent());
Assert.False(evt is null, "LoggingLevelSwitch initial level was Warning for logger System.*. It should log Warning messages for SourceContext System.Bar");


evt = null;
var controlSwitch = DummyWithLevelSwitchSink.ControlLevelSwitch;

controlSwitch.MinimumLevel = LogEventLevel.Information;
systemLogger.Write(Some.InformationEvent());
Assert.False(evt is null, "LoggingLevelSwitch level was changed to Information for logger System.*. It should now log Information events for SourceContext System.Bar.");
// ReSharper restore HeuristicUnreachableCode
}
}
}
Loading