Skip to content

Commit 94366ba

Browse files
authored
Merge pull request #48 from skomis-mm/samekind
Fix #47, #23 [multiple sinks of the same kind; environment-friendly syntax)
2 parents 2115a46 + 81451fd commit 94366ba

File tree

5 files changed

+248
-63
lines changed

5 files changed

+248
-63
lines changed

Diff for: sample/Sample/appsettings.json

+25-27
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,36 @@
88
"MyApp.Something.Tricky": "Verbose"
99
}
1010
},
11-
"WriteTo": [
12-
{
13-
"Name": "Logger",
14-
"Args": {
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-
}
27-
},
28-
{
29-
"Name": "Async",
30-
"Args": {
31-
"configure": [
11+
"WriteTo:Sublogger": {
12+
"Name": "Logger",
13+
"Args": {
14+
"configureLogger": {
15+
"WriteTo": [
3216
{
33-
"Name": "File",
17+
"Name": "LiterateConsole",
3418
"Args": {
35-
"path": "%TEMP%\\Logs\\serilog-configuration-sample.txt",
36-
"outputTemplate": "{Timestamp:o} [{Level:u3}] ({Application}/{MachineName}/{ThreadId}) {Message}{NewLine}{Exception}"
19+
"outputTemplate": "[{Timestamp:HH:mm:ss} {SourceContext} [{Level}] {Message}{NewLine}{Exception}"
3720
}
3821
}
3922
]
40-
}
41-
}
42-
],
23+
},
24+
"restrictedToMinimumLevel": "Debug"
25+
}
26+
},
27+
"WriteTo:Async": {
28+
"Name": "Async",
29+
"Args": {
30+
"configure": [
31+
{
32+
"Name": "File",
33+
"Args": {
34+
"path": "%TEMP%\\Logs\\serilog-configuration-sample.txt",
35+
"outputTemplate": "{Timestamp:o} [{Level:u3}] ({Application}/{MachineName}/{ThreadId}) {Message}{NewLine}{Exception}"
36+
}
37+
}
38+
]
39+
}
40+
},
4341
"Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"],
4442
"Properties": {
4543
"Application": "Sample"

Diff for: src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs

+41-35
Original file line numberDiff line numberDiff line change
@@ -123,46 +123,51 @@ void ApplyEnrichment(LoggerConfiguration loggerConfiguration)
123123
}
124124
}
125125

126-
Dictionary<string, Dictionary<string, IConfigurationArgumentValue>> GetMethodCalls(IConfigurationSection directive)
126+
internal ILookup<string, Dictionary<string, IConfigurationArgumentValue>> GetMethodCalls(IConfigurationSection directive)
127127
{
128-
var result = new Dictionary<string, Dictionary<string, IConfigurationArgumentValue>>();
129-
foreach (var child in directive.GetChildren())
128+
var children = directive.GetChildren();
129+
130+
var result =
131+
(from child in children
132+
where child.Value != null // Plain string
133+
select new { Name = child.Value, Args = new Dictionary<string, IConfigurationArgumentValue>() })
134+
.Concat(
135+
(from child in children
136+
where child.Value == null
137+
let name = GetSectionName(child)
138+
let callArgs = (from argument in child.GetSection("Args").GetChildren()
139+
select new {
140+
Name = argument.Key,
141+
Value = GetArgumentValue(argument) }).ToDictionary(p => p.Name, p => p.Value)
142+
select new { Name = name, Args = callArgs }))
143+
.ToLookup(p => p.Name, p => p.Args);
144+
145+
return result;
146+
147+
IConfigurationArgumentValue GetArgumentValue(IConfigurationSection argumentSection)
130148
{
131-
if (child.Value != null)
149+
IConfigurationArgumentValue argumentValue;
150+
if (argumentSection.Value != null)
132151
{
133-
// Plain string
134-
result.Add(child.Value, new Dictionary<string, IConfigurationArgumentValue>());
152+
argumentValue = new StringArgumentValue(() => argumentSection.Value, argumentSection.GetReloadToken);
135153
}
136154
else
137155
{
138-
var name = child.GetSection("Name");
139-
if (name.Value == null)
140-
throw new InvalidOperationException($"The configuration value in {name.Path} has no Name element.");
141-
142-
var callArgs = new Dictionary<string, IConfigurationArgumentValue>();
143-
var args = child.GetSection("Args");
144-
if (args != null)
145-
{
146-
foreach (var argument in args.GetChildren())
147-
{
148-
IConfigurationArgumentValue argumentValue;
149-
if (argument.Value != null)
150-
{
151-
argumentValue = new StringArgumentValue(() => argument.Value, argument.GetReloadToken);
152-
}
153-
else
154-
{
155-
argumentValue = new ConfigurationSectionArgumentValue(new ConfigurationReader(argument, _configurationAssemblies, _dependencyContext));
156-
}
157-
158-
callArgs.Add(argument.Key, argumentValue);
159-
}
160-
}
161-
result.Add(name.Value, callArgs);
156+
argumentValue = new ConfigurationSectionArgumentValue(new ConfigurationReader(argumentSection, _configurationAssemblies, _dependencyContext));
162157
}
158+
159+
return argumentValue;
163160
}
164-
return result;
165-
}
161+
162+
string GetSectionName(IConfigurationSection s)
163+
{
164+
var name = s.GetSection("Name");
165+
if (name.Value == null)
166+
throw new InvalidOperationException($"The configuration value in {name.Path} has no 'Name' element.");
167+
168+
return name.Value;
169+
}
170+
}
166171

167172
Assembly[] LoadConfigurationAssemblies()
168173
{
@@ -178,7 +183,8 @@ Assembly[] LoadConfigurationAssemblies()
178183
"A zero-length or whitespace assembly name was supplied to a Serilog.Using configuration statement.");
179184

180185
var assembly = Assembly.Load(new AssemblyName(simpleName));
181-
assemblies.Add(assembly.FullName, assembly);
186+
if (!assemblies.ContainsKey(assembly.FullName))
187+
assemblies.Add(assembly.FullName, assembly);
182188
}
183189
}
184190

@@ -217,9 +223,9 @@ where filter(assemblyFileName)
217223
return query.ToArray();
218224
}
219225

220-
static void CallConfigurationMethods(Dictionary<string, Dictionary<string, IConfigurationArgumentValue>> methods, IList<MethodInfo> configurationMethods, object receiver)
226+
static void CallConfigurationMethods(ILookup<string, Dictionary<string, IConfigurationArgumentValue>> methods, IList<MethodInfo> configurationMethods, object receiver)
221227
{
222-
foreach (var method in methods)
228+
foreach (var method in methods.SelectMany(g => g.Select(x => new { g.Key, Value = x })))
223229
{
224230
var methodInfo = SelectConfigurationMethod(configurationMethods, method.Key, method.Value);
225231

Diff for: test/Serilog.Settings.Configuration.Tests/ConfigurationReaderTests.cs

+128
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,139 @@
33
using Xunit;
44
using System.Reflection;
55
using System.Linq;
6+
using Serilog.Settings.Configuration.Tests.Support;
67

78
namespace Serilog.Settings.Configuration.Tests
89
{
910
public class ConfigurationReaderTests
1011
{
12+
readonly ConfigurationReader _configurationReader;
13+
14+
public ConfigurationReaderTests()
15+
{
16+
_configurationReader = new ConfigurationReader(JsonStringConfigSource.LoadSection(@"{ 'Serilog': { } }", "Serilog"), null);
17+
}
18+
19+
[Fact]
20+
public void WriteToSupportSimplifiedSyntax()
21+
{
22+
var json = @"
23+
{
24+
'WriteTo': [ 'LiterateConsole', 'DiagnosticTrace' ]
25+
}";
26+
27+
var result = _configurationReader.GetMethodCalls(JsonStringConfigSource.LoadSection(json, "WriteTo"));
28+
Assert.Equal(2, result.Count);
29+
Assert.True(result.Contains("LiterateConsole"));
30+
Assert.True(result.Contains("DiagnosticTrace"));
31+
32+
Assert.Equal(1, result["LiterateConsole"].Count());
33+
Assert.Equal(1, result["DiagnosticTrace"].Count());
34+
}
35+
36+
[Fact]
37+
public void WriteToSupportExpandedSyntaxWithoutArgs()
38+
{
39+
var json = @"
40+
{
41+
'WriteTo': [ {
42+
'Name': 'LiterateConsole'
43+
}]
44+
}";
45+
46+
var result = _configurationReader.GetMethodCalls(JsonStringConfigSource.LoadSection(json, "WriteTo"));
47+
Assert.Equal(1, result.Count);
48+
Assert.True(result.Contains("LiterateConsole"));
49+
50+
Assert.Equal(1, result["LiterateConsole"].Count());
51+
}
52+
53+
[Fact]
54+
public void WriteToSupportExpandedSyntaxWithArgs()
55+
{
56+
var json = @"
57+
{
58+
'WriteTo': [ {
59+
'Name': 'LiterateConsole',
60+
'Args': {
61+
'outputTemplate': '{Message}'
62+
},
63+
}]
64+
}";
65+
66+
var result = _configurationReader.GetMethodCalls(JsonStringConfigSource.LoadSection(json, "WriteTo"));
67+
68+
Assert.Equal(1, result.Count);
69+
Assert.True(result.Contains("LiterateConsole"));
70+
71+
Assert.Equal(1, result["LiterateConsole"].Count());
72+
73+
var args = result["LiterateConsole"].Single().Cast<KeyValuePair<string, IConfigurationArgumentValue>>().ToArray();
74+
75+
Assert.Equal(1, args.Length);
76+
Assert.Equal("outputTemplate", args[0].Key);
77+
Assert.Equal("{Message}", args[0].Value.ConvertTo(typeof(string)));
78+
}
79+
80+
[Fact]
81+
public void WriteToSupportMultipleSinksOfTheSameKind()
82+
{
83+
var json = @"
84+
{
85+
'WriteTo': [
86+
{
87+
'Name': 'LiterateConsole',
88+
'Args': {
89+
'outputTemplate': '{Message}'
90+
},
91+
},
92+
'DiagnosticTrace'
93+
],
94+
'WriteTo:File1': {
95+
'Name': 'File',
96+
'Args': {
97+
'outputTemplate': '{Message}'
98+
},
99+
},
100+
'WriteTo:File2': {
101+
'Name': 'File',
102+
'Args': {
103+
'outputTemplate': '{Message}'
104+
},
105+
}
106+
}";
107+
108+
var result = _configurationReader.GetMethodCalls(JsonStringConfigSource.LoadSection(json, "WriteTo"));
109+
110+
Assert.Equal(3, result.Count);
111+
Assert.True(result.Contains("LiterateConsole"));
112+
Assert.True(result.Contains("DiagnosticTrace"));
113+
Assert.True(result.Contains("File"));
114+
115+
Assert.Equal(1, result["LiterateConsole"].Count());
116+
Assert.Equal(1, result["DiagnosticTrace"].Count());
117+
Assert.Equal(2, result["File"].Count());
118+
}
119+
120+
[Fact]
121+
public void Enrich_SupportSimplifiedSyntax()
122+
{
123+
var json = @"
124+
{
125+
'Enrich': [ 'FromLogContext', 'WithMachineName', 'WithThreadId' ]
126+
}";
127+
128+
var result = _configurationReader.GetMethodCalls(JsonStringConfigSource.LoadSection(json, "Enrich"));
129+
Assert.Equal(3, result.Count);
130+
Assert.True(result.Contains("FromLogContext"));
131+
Assert.True(result.Contains("WithMachineName"));
132+
Assert.True(result.Contains("WithThreadId"));
133+
134+
Assert.Equal(1, result["FromLogContext"].Count());
135+
Assert.Equal(1, result["WithMachineName"].Count());
136+
Assert.Equal(1, result["WithThreadId"].Count());
137+
}
138+
11139
[Fact]
12140
public void CallableMethodsAreSelected()
13141
{

Diff for: test/Serilog.Settings.Configuration.Tests/Serilog.Settings.Configuration.Tests.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
1818
<PackageReference Include="xunit" Version="2.2.0" />
1919

20-
<PackageReference Include="Microsoft.Extensions.Configuration" Version="1.0.0" />
20+
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.0.0" />
2121
</ItemGroup>
2222

2323
<ItemGroup>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using Microsoft.Extensions.Configuration;
2+
using Microsoft.Extensions.Configuration.Json;
3+
4+
using System.IO;
5+
6+
namespace Serilog.Settings.Configuration.Tests.Support
7+
{
8+
class JsonStringConfigSource : IConfigurationSource
9+
{
10+
readonly string _json;
11+
12+
public JsonStringConfigSource(string json)
13+
{
14+
_json = json;
15+
}
16+
17+
public IConfigurationProvider Build(IConfigurationBuilder builder)
18+
{
19+
return new JsonStringConfigProvider(_json);
20+
}
21+
22+
public static IConfigurationSection LoadSection(string json, string section)
23+
{
24+
return new ConfigurationBuilder().Add(new JsonStringConfigSource(json)).Build().GetSection(section);
25+
}
26+
27+
class JsonStringConfigProvider : JsonConfigurationProvider
28+
{
29+
readonly string _json;
30+
31+
public JsonStringConfigProvider(string json) : base(new JsonConfigurationSource { Optional = true })
32+
{
33+
_json = json;
34+
}
35+
36+
public override void Load()
37+
{
38+
Load(StringToStream(_json));
39+
}
40+
41+
static Stream StringToStream(string str)
42+
{
43+
var memStream = new MemoryStream();
44+
var textWriter = new StreamWriter(memStream);
45+
textWriter.Write(str);
46+
textWriter.Flush();
47+
memStream.Seek(0, SeekOrigin.Begin);
48+
49+
return memStream;
50+
}
51+
}
52+
}
53+
}

0 commit comments

Comments
 (0)