Skip to content

Commit 407f5fb

Browse files
authored
Merge pull request #46 from skomis-mm/sublog
Configure sub-loggers through JSON appsettings
2 parents 389299f + b37c265 commit 407f5fb

File tree

9 files changed

+306
-152
lines changed

9 files changed

+306
-152
lines changed

sample/Sample/Sample.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
</ItemGroup>
1515

1616
<ItemGroup>
17-
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.0.0" />
17+
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.1" />
18+
<PackageReference Include="Serilog.Sinks.Async" Version="1.0.1" />
1819
<PackageReference Include="Serilog.Sinks.Literate" Version="2.0.0" />
1920
<PackageReference Include="Serilog.Sinks.RollingFile" Version="3.0.0" />
2021
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.0.0" />

sample/Sample/appsettings.json

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,35 @@
1010
},
1111
"WriteTo": [
1212
{
13-
"Name": "LiterateConsole",
13+
"Name": "Logger",
1414
"Args": {
15-
"outputTemplate": "[{Timestamp:HH:mm:ss} {SourceContext} [{Level}] {Message}{NewLine}{Exception}"
16-
}
15+
"configureLogger": {
16+
"WriteTo": [
17+
{
18+
"Name": "LiterateConsole",
19+
"Args": {
20+
"outputTemplate": "[{Timestamp:HH:mm:ss} {SourceContext} [{Level}] {Message}{NewLine}{Exception}"
21+
}
22+
}
23+
]
24+
},
25+
"restrictedToMinimumLevel": "Debug"
26+
}
1727
},
18-
{
19-
"Name": "File",
28+
{
29+
"Name": "Async",
2030
"Args": {
21-
"path": "%TEMP%\\Logs\\serilog-configuration-sample.txt",
22-
"outputTemplate": "{Timestamp:o} [{Level:u3}] ({Application}/{MachineName}/{ThreadId}) {Message}{NewLine}{Exception}"
31+
"configure": [
32+
{
33+
"Name": "File",
34+
"Args": {
35+
"path": "%TEMP%\\Logs\\serilog-configuration-sample.txt",
36+
"outputTemplate": "{Timestamp:o} [{Level:u3}] ({Application}/{MachineName}/{ThreadId}) {Message}{NewLine}{Exception}"
37+
}
38+
}
39+
]
2340
}
24-
}
41+
}
2542
],
2643
"Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"],
2744
"Properties": {

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

Lines changed: 94 additions & 129 deletions
Large diffs are not rendered by default.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using Serilog.Configuration;
2+
using System;
3+
using System.Reflection;
4+
5+
namespace Serilog.Settings.Configuration
6+
{
7+
class ConfigurationSectionArgumentValue : IConfigurationArgumentValue
8+
{
9+
readonly IConfigurationReader _configReader;
10+
11+
public ConfigurationSectionArgumentValue(IConfigurationReader configReader)
12+
{
13+
_configReader = configReader ?? throw new ArgumentNullException(nameof(configReader));
14+
}
15+
16+
public object ConvertTo(Type toType)
17+
{
18+
var typeInfo = toType.GetTypeInfo();
19+
if (!typeInfo.IsGenericType ||
20+
typeInfo.GetGenericTypeDefinition() is Type genericType && genericType != typeof(Action<>))
21+
{
22+
throw new InvalidOperationException("Argument value should be of type Action<>.");
23+
}
24+
25+
var configurationType = typeInfo.GenericTypeArguments[0];
26+
if (configurationType == typeof(LoggerSinkConfiguration))
27+
{
28+
return new Action<LoggerSinkConfiguration>(_configReader.ApplySinks);
29+
}
30+
31+
if (configurationType == typeof(LoggerConfiguration))
32+
{
33+
return new Action<LoggerConfiguration>(_configReader.Configure);
34+
}
35+
36+
throw new ArgumentException($"Handling {configurationType} is not implemented.");
37+
}
38+
}
39+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System;
2+
3+
namespace Serilog.Settings.Configuration
4+
{
5+
interface IConfigurationArgumentValue
6+
{
7+
object ConvertTo(Type toType);
8+
}
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Serilog.Configuration;
2+
3+
namespace Serilog.Settings.Configuration
4+
{
5+
interface IConfigurationReader : ILoggerSettings
6+
{
7+
void ApplySinks(LoggerSinkConfiguration loggerSinkConfiguration);
8+
}
9+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
6+
using Microsoft.Extensions.Primitives;
7+
8+
using Serilog.Core;
9+
using Serilog.Debugging;
10+
using Serilog.Events;
11+
12+
namespace Serilog.Settings.Configuration
13+
{
14+
class StringArgumentValue : IConfigurationArgumentValue
15+
{
16+
readonly Func<string> _valueProducer;
17+
readonly Func<IChangeToken> _changeTokenProducer;
18+
19+
public StringArgumentValue(Func<string> valueProducer, Func<IChangeToken> changeTokenProducer = null)
20+
{
21+
_valueProducer = valueProducer ?? throw new ArgumentNullException(nameof(valueProducer));
22+
_changeTokenProducer = changeTokenProducer;
23+
}
24+
25+
static readonly Dictionary<Type, Func<string, object>> ExtendedTypeConversions = new Dictionary<Type, Func<string, object>>
26+
{
27+
{ typeof(Uri), s => new Uri(s) },
28+
{ typeof(TimeSpan), s => TimeSpan.Parse(s) }
29+
};
30+
31+
public object ConvertTo(Type toType)
32+
{
33+
var argumentValue = Environment.ExpandEnvironmentVariables(_valueProducer());
34+
35+
var toTypeInfo = toType.GetTypeInfo();
36+
if (toTypeInfo.IsGenericType && toType.GetGenericTypeDefinition() == typeof(Nullable<>))
37+
{
38+
if (string.IsNullOrEmpty(argumentValue))
39+
return null;
40+
41+
// unwrap Nullable<> type since we're not handling null situations
42+
toType = toTypeInfo.GenericTypeArguments[0];
43+
toTypeInfo = toType.GetTypeInfo();
44+
}
45+
46+
if (toTypeInfo.IsEnum)
47+
return Enum.Parse(toType, argumentValue);
48+
49+
var convertor = ExtendedTypeConversions
50+
.Where(t => t.Key.GetTypeInfo().IsAssignableFrom(toTypeInfo))
51+
.Select(t => t.Value)
52+
.FirstOrDefault();
53+
54+
if (convertor != null)
55+
return convertor(argumentValue);
56+
57+
if (toTypeInfo.IsInterface && !string.IsNullOrWhiteSpace(argumentValue))
58+
{
59+
var type = Type.GetType(argumentValue.Trim(), throwOnError: false);
60+
if (type != null)
61+
{
62+
var ctor = type.GetTypeInfo().DeclaredConstructors.FirstOrDefault(ci =>
63+
{
64+
var parameters = ci.GetParameters();
65+
return parameters.Length == 0 || parameters.All(pi => pi.HasDefaultValue);
66+
});
67+
68+
if (ctor == null)
69+
throw new InvalidOperationException($"A default constructor was not found on {type.FullName}.");
70+
71+
var call = ctor.GetParameters().Select(pi => pi.DefaultValue).ToArray();
72+
return ctor.Invoke(call);
73+
}
74+
}
75+
76+
if (toType == typeof(LoggingLevelSwitch))
77+
{
78+
if (!Enum.TryParse(argumentValue, out LogEventLevel minimumLevel))
79+
throw new InvalidOperationException($"The value `{argumentValue}` is not a valid Serilog level.");
80+
81+
var levelSwitch = new LoggingLevelSwitch(minimumLevel);
82+
83+
if (_changeTokenProducer != null)
84+
{
85+
ChangeToken.OnChange(
86+
_changeTokenProducer,
87+
() =>
88+
{
89+
var newArgumentValue = _valueProducer();
90+
91+
if (Enum.TryParse(newArgumentValue, out minimumLevel))
92+
levelSwitch.MinimumLevel = minimumLevel;
93+
else
94+
SelfLog.WriteLine($"The value `{newArgumentValue}` is not a valid Serilog level.");
95+
});
96+
}
97+
98+
return levelSwitch;
99+
}
100+
101+
return Convert.ChangeType(argumentValue, toType);
102+
}
103+
}
104+
}

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

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
using System.Collections.Generic;
2-
using Serilog.Settings.Configuration;
32
using Serilog.Formatting;
4-
using Serilog.Formatting.Json;
53
using Xunit;
64
using System.Reflection;
75
using System.Linq;
@@ -10,21 +8,14 @@ namespace Serilog.Settings.Configuration.Tests
108
{
119
public class ConfigurationReaderTests
1210
{
13-
[Fact]
14-
public void StringValuesConvertToDefaultInstancesIfTargetIsInterface()
15-
{
16-
var result = ConfigurationReader.ConvertToType("Serilog.Formatting.Json.JsonFormatter, Serilog", typeof(ITextFormatter));
17-
Assert.IsType<JsonFormatter>(result);
18-
}
19-
2011
[Fact]
2112
public void CallableMethodsAreSelected()
2213
{
2314
var options = typeof(DummyLoggerConfigurationExtensions).GetTypeInfo().DeclaredMethods.ToList();
2415
Assert.Equal(2, options.Count(mi => mi.Name == "DummyRollingFile"));
25-
var suppliedArguments = new Dictionary<string, string>
16+
var suppliedArguments = new Dictionary<string, IConfigurationArgumentValue>
2617
{
27-
{"pathFormat", "C:\\" }
18+
{"pathFormat", new StringArgumentValue(() => "C:\\") }
2819
};
2920

3021
var selected = ConfigurationReader.SelectConfigurationMethod(options, "DummyRollingFile", suppliedArguments);
@@ -36,10 +27,10 @@ public void MethodsAreSelectedBasedOnCountOfMatchedArguments()
3627
{
3728
var options = typeof(DummyLoggerConfigurationExtensions).GetTypeInfo().DeclaredMethods.ToList();
3829
Assert.Equal(2, options.Count(mi => mi.Name == "DummyRollingFile"));
39-
var suppliedArguments = new Dictionary<string, string>()
30+
var suppliedArguments = new Dictionary<string, IConfigurationArgumentValue>()
4031
{
41-
{ "pathFormat", "C:\\" },
42-
{ "formatter", "SomeFormatter, SomeAssembly" }
32+
{ "pathFormat", new StringArgumentValue(() => "C:\\") },
33+
{ "formatter", new StringArgumentValue(() => "SomeFormatter, SomeAssembly") }
4334
};
4435

4536
var selected = ConfigurationReader.SelectConfigurationMethod(options, "DummyRollingFile", suppliedArguments);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Serilog.Formatting;
2+
using Serilog.Formatting.Json;
3+
using Xunit;
4+
5+
namespace Serilog.Settings.Configuration.Tests
6+
{
7+
public class StringArgumentValueTests
8+
{
9+
[Fact]
10+
public void StringValuesConvertToDefaultInstancesIfTargetIsInterface()
11+
{
12+
var stringArgumentValue = new StringArgumentValue(() => "Serilog.Formatting.Json.JsonFormatter, Serilog");
13+
14+
var result = stringArgumentValue.ConvertTo(typeof(ITextFormatter));
15+
16+
Assert.IsType<JsonFormatter>(result);
17+
}
18+
}
19+
}

0 commit comments

Comments
 (0)