Skip to content

Commit 79127bd

Browse files
authored
Merge pull request #132 from nblumhardt/add-provider
SerilogLoggerFactory and AddProvider() infrastructure
2 parents 31d7194 + d2155ce commit 79127bd

17 files changed

+481
-69
lines changed

samples/Sample/Program.cs

+24-7
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,41 @@
22
using Microsoft.Extensions.DependencyInjection;
33
using Microsoft.Extensions.Logging;
44
using Serilog;
5+
using Serilog.Extensions.Logging;
56

67
namespace Sample
78
{
89
public class Program
910
{
1011
public static void Main(string[] args)
1112
{
13+
// Creating a `LoggerProviderCollection` lets Serilog optionally write
14+
// events through other dynamically-added MEL ILoggerProviders.
15+
var providers = new LoggerProviderCollection();
16+
1217
Log.Logger = new LoggerConfiguration()
1318
.MinimumLevel.Debug()
14-
.WriteTo.LiterateConsole()
19+
.WriteTo.Console()
20+
.WriteTo.Providers(providers)
1521
.CreateLogger();
1622

17-
var services = new ServiceCollection()
18-
.AddLogging(builder =>
19-
{
20-
builder.AddSerilog();
21-
});
23+
var services = new ServiceCollection();
24+
25+
services.AddSingleton(providers);
26+
services.AddSingleton<ILoggerFactory>(sc =>
27+
{
28+
var providerCollection = sc.GetService<LoggerProviderCollection>();
29+
var factory = new SerilogLoggerFactory(null, true, providerCollection);
30+
31+
foreach (var provider in sc.GetServices<ILoggerProvider>())
32+
factory.AddProvider(provider);
33+
34+
return factory;
35+
});
36+
37+
services.AddLogging(l => l.AddConsole());
2238

2339
var serviceProvider = services.BuildServiceProvider();
24-
// getting the logger using the class's name is conventional
2540
var logger = serviceProvider.GetRequiredService<ILogger<Program>>();
2641

2742
var startTime = DateTimeOffset.UtcNow;
@@ -57,6 +72,8 @@ public static void Main(string[] args)
5772
logger.LogInformation("{Result,-10:l}{StartTime,15:l}{EndTime,15:l}{Duration,15:l}", "RESULT", "START TIME", "END TIME", "DURATION(ms)");
5873
logger.LogInformation("{Result,-10:l}{StartTime,15:l}{EndTime,15:l}{Duration,15:l}", "------", "----- ----", "--- ----", "------------");
5974
logger.LogInformation("{Result,-10:l}{StartTime,15:mm:s tt}{EndTime,15:mm:s tt}{Duration,15}", "SUCCESS", startTime, endTime, (endTime - startTime).TotalMilliseconds);
75+
76+
serviceProvider.Dispose();
6077
}
6178
}
6279
}

samples/Sample/Sample.csproj

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
4+
<TargetFrameworks>netcoreapp2.0</TargetFrameworks>
55
<AssemblyName>Sample</AssemblyName>
66
<OutputType>Exe</OutputType>
77
<PackageId>Sample</PackageId>
@@ -14,7 +14,8 @@
1414
<ItemGroup>
1515
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
1616
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0" />
17-
<PackageReference Include="Serilog.Sinks.Literate" Version="2.0.0" />
17+
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.0" />
18+
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
1819
</ItemGroup>
1920

2021
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:Boolean x:Key="/Default/UserDictionary/Words/=destructure/@EntryIndexedValue">True</s:Boolean>
3+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Destructurer/@EntryIndexedValue">True</s:Boolean>
4+
<s:Boolean x:Key="/Default/UserDictionary/Words/=enricher/@EntryIndexedValue">True</s:Boolean>
5+
<s:Boolean x:Key="/Default/UserDictionary/Words/=enrichers/@EntryIndexedValue">True</s:Boolean>
6+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nonscalar/@EntryIndexedValue">True</s:Boolean>
7+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Serilog/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2019 Serilog Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using Microsoft.Extensions.Logging;
16+
using Serilog.Events;
17+
18+
namespace Serilog.Extensions.Logging
19+
{
20+
static class LevelMapping
21+
{
22+
public static LogEventLevel ToSerilogLevel(LogLevel logLevel)
23+
{
24+
switch (logLevel)
25+
{
26+
case LogLevel.Critical:
27+
return LogEventLevel.Fatal;
28+
case LogLevel.Error:
29+
return LogEventLevel.Error;
30+
case LogLevel.Warning:
31+
return LogEventLevel.Warning;
32+
case LogLevel.Information:
33+
return LogEventLevel.Information;
34+
case LogLevel.Debug:
35+
return LogEventLevel.Debug;
36+
// ReSharper disable once RedundantCaseLabel
37+
case LogLevel.Trace:
38+
default:
39+
return LogEventLevel.Verbose;
40+
}
41+
}
42+
43+
public static LogLevel ToExtensionsLevel(LogEventLevel logEventLevel)
44+
{
45+
switch (logEventLevel)
46+
{
47+
case LogEventLevel.Fatal:
48+
return LogLevel.Critical;
49+
case LogEventLevel.Error:
50+
return LogLevel.Error;
51+
case LogEventLevel.Warning:
52+
return LogLevel.Warning;
53+
case LogEventLevel.Information:
54+
return LogLevel.Information;
55+
case LogEventLevel.Debug:
56+
return LogLevel.Debug;
57+
// ReSharper disable once RedundantCaseLabel
58+
case LogEventLevel.Verbose:
59+
default:
60+
return LogLevel.Trace;
61+
}
62+
}
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2019 Serilog Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
16+
using System;
17+
using System.Collections.Generic;
18+
using System.Linq;
19+
using System.Threading;
20+
using Microsoft.Extensions.Logging;
21+
22+
namespace Serilog.Extensions.Logging
23+
{
24+
/// <summary>
25+
/// A dynamically-modifiable collection of <see cref="ILoggerProvider"/>s.
26+
/// </summary>
27+
public class LoggerProviderCollection : IDisposable
28+
{
29+
volatile ILoggerProvider[] _providers = new ILoggerProvider[0];
30+
31+
/// <summary>
32+
/// Add <paramref name="provider"/> to the collection.
33+
/// </summary>
34+
/// <param name="provider">A logger provider.</param>
35+
public void AddProvider(ILoggerProvider provider)
36+
{
37+
if (provider == null) throw new ArgumentNullException(nameof(provider));
38+
39+
var existing = _providers;
40+
var added = existing.Concat(new[] {provider}).ToArray();
41+
42+
#pragma warning disable 420 // ref to a volatile field
43+
while (Interlocked.CompareExchange(ref _providers, added, existing) != existing)
44+
#pragma warning restore 420
45+
{
46+
existing = _providers;
47+
added = existing.Concat(new[] { provider }).ToArray();
48+
}
49+
}
50+
51+
/// <summary>
52+
/// Get the currently-active providers.
53+
/// </summary>
54+
/// <remarks>
55+
/// If the collection has been disposed, we'll leave the individual
56+
/// providers with the job of throwing <see cref="ObjectDisposedException"/>.
57+
/// </remarks>
58+
public IEnumerable<ILoggerProvider> Providers => _providers;
59+
60+
/// <inheritdoc cref="IDisposable"/>
61+
public void Dispose()
62+
{
63+
foreach (var provider in _providers)
64+
provider.Dispose();
65+
}
66+
}
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2019 Serilog Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System;
16+
using Serilog.Core;
17+
using Serilog.Events;
18+
19+
namespace Serilog.Extensions.Logging
20+
{
21+
class LoggerProviderCollectionSink : ILogEventSink, IDisposable
22+
{
23+
readonly LoggerProviderCollection _providers;
24+
25+
public LoggerProviderCollectionSink(LoggerProviderCollection providers)
26+
{
27+
_providers = providers ?? throw new ArgumentNullException(nameof(providers));
28+
}
29+
30+
public void Emit(LogEvent logEvent)
31+
{
32+
string categoryName = null;
33+
34+
if (logEvent.Properties.TryGetValue("SourceContext", out var sourceContextProperty) &&
35+
sourceContextProperty is ScalarValue sourceContextValue &&
36+
sourceContextValue.Value is string sourceContext)
37+
{
38+
categoryName = sourceContext;
39+
}
40+
41+
var level = LevelMapping.ToExtensionsLevel(logEvent.Level);
42+
var slv = new SerilogLogValues(logEvent.MessageTemplate, logEvent.Properties);
43+
44+
foreach (var provider in _providers.Providers)
45+
{
46+
var logger = provider.CreateLogger(categoryName);
47+
48+
logger.Log(
49+
level,
50+
default,
51+
slv,
52+
logEvent.Exception,
53+
(s, e) => s.ToString());
54+
}
55+
}
56+
57+
public void Dispose()
58+
{
59+
_providers.Dispose();
60+
}
61+
}
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2019 Serilog Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using Serilog.Events;
16+
using System;
17+
using System.Collections;
18+
using System.Collections.Generic;
19+
20+
namespace Serilog.Extensions.Logging
21+
{
22+
readonly struct SerilogLogValues : IReadOnlyList<KeyValuePair<string, object>>
23+
{
24+
// Note, this struct is only used in a very limited context internally, so we ignore
25+
// the possibility of fields being null via the default struct initialization.
26+
27+
private readonly MessageTemplate _messageTemplate;
28+
private readonly IReadOnlyDictionary<string, LogEventPropertyValue> _properties;
29+
private readonly KeyValuePair<string, object>[] _values;
30+
31+
public SerilogLogValues(MessageTemplate messageTemplate, IReadOnlyDictionary<string, LogEventPropertyValue> properties)
32+
{
33+
_messageTemplate = messageTemplate ?? throw new ArgumentNullException(nameof(messageTemplate));
34+
35+
// The dictionary is needed for rendering through the message template
36+
_properties = properties ?? throw new ArgumentNullException(nameof(properties));
37+
38+
// The array is needed because the IReadOnlyList<T> interface expects indexed access
39+
_values = new KeyValuePair<string, object>[_properties.Count + 1];
40+
var i = 0;
41+
foreach (var p in properties)
42+
{
43+
_values[i] = new KeyValuePair<string, object>(p.Key, (p.Value is ScalarValue sv) ? sv.Value : p.Value);
44+
++i;
45+
}
46+
_values[i] = new KeyValuePair<string, object>("{OriginalFormat}", _messageTemplate.Text);
47+
}
48+
49+
public KeyValuePair<string, object> this[int index]
50+
{
51+
get => _values[index];
52+
}
53+
54+
public int Count => _properties.Count + 1;
55+
56+
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() => ((IEnumerable<KeyValuePair<string, object>>)_values).GetEnumerator();
57+
58+
public override string ToString() => _messageTemplate.Render(_properties);
59+
60+
IEnumerator IEnumerable.GetEnumerator() => _values.GetEnumerator();
61+
}
62+
}

src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs

+2-23
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public SerilogLogger(
3939

4040
public bool IsEnabled(LogLevel logLevel)
4141
{
42-
return _logger.IsEnabled(ConvertLevel(logLevel));
42+
return _logger.IsEnabled(LevelMapping.ToSerilogLevel(logLevel));
4343
}
4444

4545
public IDisposable BeginScope<TState>(TState state)
@@ -49,7 +49,7 @@ public IDisposable BeginScope<TState>(TState state)
4949

5050
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
5151
{
52-
var level = ConvertLevel(logLevel);
52+
var level = LevelMapping.ToSerilogLevel(logLevel);
5353
if (!_logger.IsEnabled(level))
5454
{
5555
return;
@@ -133,27 +133,6 @@ static object AsLoggableValue<TState>(TState state, Func<TState, Exception, stri
133133
return sobj;
134134
}
135135

136-
static LogEventLevel ConvertLevel(LogLevel logLevel)
137-
{
138-
switch (logLevel)
139-
{
140-
case LogLevel.Critical:
141-
return LogEventLevel.Fatal;
142-
case LogLevel.Error:
143-
return LogEventLevel.Error;
144-
case LogLevel.Warning:
145-
return LogEventLevel.Warning;
146-
case LogLevel.Information:
147-
return LogEventLevel.Information;
148-
case LogLevel.Debug:
149-
return LogEventLevel.Debug;
150-
// ReSharper disable once RedundantCaseLabel
151-
case LogLevel.Trace:
152-
default:
153-
return LogEventLevel.Verbose;
154-
}
155-
}
156-
157136
static LogEventProperty CreateEventIdProperty(EventId eventId)
158137
{
159138
var properties = new List<LogEventProperty>(2);

0 commit comments

Comments
 (0)