Skip to content

Commit 7030b19

Browse files
committed
Proof-of-concept AddProvider() infrastructure
1 parent da7198b commit 7030b19

10 files changed

+321
-35
lines changed

samples/Sample/Program.cs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,37 @@
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+
services.AddSingleton<ILoggerFactory>(sc =>
25+
{
26+
// Add providers already registered through IoC
27+
foreach (var provider in sc.GetServices<ILoggerProvider>())
28+
providers.AddProvider(provider);
29+
30+
return new SerilogLoggerFactory(null, true, providers);
31+
});
32+
33+
services.AddLogging(l => l.AddConsole());
2234

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

2738
var startTime = DateTimeOffset.UtcNow;
@@ -57,6 +68,8 @@ public static void Main(string[] args)
5768
logger.LogInformation("{Result,-10:l}{StartTime,15:l}{EndTime,15:l}{Duration,15:l}", "RESULT", "START TIME", "END TIME", "DURATION(ms)");
5869
logger.LogInformation("{Result,-10:l}{StartTime,15:l}{EndTime,15:l}{Duration,15:l}", "------", "----- ----", "--- ----", "------------");
5970
logger.LogInformation("{Result,-10:l}{StartTime,15:mm:s tt}{EndTime,15:mm:s tt}{Duration,15}", "SUCCESS", startTime, endTime, (endTime - startTime).TotalMilliseconds);
71+
72+
serviceProvider.Dispose();
6073
}
6174
}
6275
}

samples/Sample/Sample.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using Microsoft.Extensions.Logging;
2+
using Serilog.Events;
3+
4+
namespace Serilog.Extensions.Logging
5+
{
6+
static class LevelMapping
7+
{
8+
public static LogEventLevel ToSerilogLevel(LogLevel logLevel)
9+
{
10+
switch (logLevel)
11+
{
12+
case LogLevel.Critical:
13+
return LogEventLevel.Fatal;
14+
case LogLevel.Error:
15+
return LogEventLevel.Error;
16+
case LogLevel.Warning:
17+
return LogEventLevel.Warning;
18+
case LogLevel.Information:
19+
return LogEventLevel.Information;
20+
case LogLevel.Debug:
21+
return LogEventLevel.Debug;
22+
// ReSharper disable once RedundantCaseLabel
23+
case LogLevel.Trace:
24+
default:
25+
return LogEventLevel.Verbose;
26+
}
27+
}
28+
29+
public static LogLevel ToExtensionsLevel(LogEventLevel logEventLevel)
30+
{
31+
switch (logEventLevel)
32+
{
33+
case LogEventLevel.Fatal:
34+
return LogLevel.Critical;
35+
case LogEventLevel.Error:
36+
return LogLevel.Error;
37+
case LogEventLevel.Warning:
38+
return LogLevel.Warning;
39+
case LogEventLevel.Information:
40+
return LogLevel.Information;
41+
case LogEventLevel.Debug:
42+
return LogLevel.Debug;
43+
// ReSharper disable once RedundantCaseLabel
44+
case LogEventLevel.Verbose:
45+
default:
46+
return LogLevel.Trace;
47+
}
48+
}
49+
}
50+
}
Lines changed: 67 additions & 0 deletions
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+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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 Microsoft.Extensions.Logging;
17+
using Serilog.Core;
18+
using Serilog.Events;
19+
20+
namespace Serilog.Extensions.Logging
21+
{
22+
class LoggerProviderCollectionSink : ILogEventSink, IDisposable
23+
{
24+
readonly LoggerProviderCollection _providers;
25+
26+
public LoggerProviderCollectionSink(LoggerProviderCollection providers)
27+
{
28+
_providers = providers ?? throw new ArgumentNullException(nameof(providers));
29+
}
30+
31+
public void Emit(LogEvent logEvent)
32+
{
33+
string categoryName = null;
34+
35+
if (logEvent.Properties.TryGetValue("SourceContext", out var sourceContextProperty) &&
36+
sourceContextProperty is ScalarValue sourceContextValue &&
37+
sourceContextValue.Value is string sourceContext)
38+
{
39+
categoryName = sourceContext;
40+
}
41+
42+
foreach (var provider in _providers.Providers)
43+
{
44+
var logger = provider.CreateLogger(categoryName);
45+
46+
47+
logger.Log(
48+
LevelMapping.ToExtensionsLevel(logEvent.Level),
49+
default(EventId),
50+
logEvent,
51+
logEvent.Exception,
52+
(s, ex) => s.RenderMessage());
53+
}
54+
}
55+
56+
public void Dispose()
57+
{
58+
_providers.Dispose();
59+
}
60+
}
61+
}

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

Lines changed: 2 additions & 23 deletions
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);
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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 Microsoft.Extensions.Logging;
17+
using Serilog.Debugging;
18+
19+
namespace Serilog.Extensions.Logging
20+
{
21+
/// <summary>
22+
/// A complete Serilog-backed implementation of the .NET Core logging infrastructure.
23+
/// </summary>
24+
public class SerilogLoggerFactory : ILoggerFactory
25+
{
26+
readonly LoggerProviderCollection _providerCollection;
27+
readonly SerilogLoggerProvider _provider;
28+
29+
/// <summary>
30+
/// Initializes a new instance of the <see cref="SerilogLoggerFactory"/> class.
31+
/// </summary>
32+
/// <param name="logger">The Serilog logger; if not supplied, the static <see cref="Serilog.Log"/> will be used.</param>
33+
/// <param name="dispose">When true, dispose <paramref name="logger"/> when the framework disposes the provider. If the
34+
/// logger is not specified but <paramref name="dispose"/> is true, the <see cref="Log.CloseAndFlush()"/> method will be
35+
/// called on the static <see cref="Log"/> class instead.</param>
36+
/// <param name="providerCollection">A <see cref="LoggerProviderCollection"/>, for use with <c>WriteTo.Providers()</c>.</param>
37+
public SerilogLoggerFactory(ILogger logger = null, bool dispose = false, LoggerProviderCollection providerCollection = null)
38+
{
39+
_provider = new SerilogLoggerProvider(logger, dispose);
40+
_providerCollection = providerCollection;
41+
}
42+
43+
/// <summary>
44+
/// Disposes the provider.
45+
/// </summary>
46+
public void Dispose()
47+
{
48+
_provider.Dispose();
49+
}
50+
51+
/// <summary>
52+
/// Creates a new <see cref="T:Microsoft.Extensions.Logging.ILogger" /> instance.
53+
/// </summary>
54+
/// <param name="categoryName">The category name for messages produced by the logger.</param>
55+
/// <returns>
56+
/// The <see cref="T:Microsoft.Extensions.Logging.ILogger" />.
57+
/// </returns>
58+
public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName)
59+
{
60+
return _provider.CreateLogger(categoryName);
61+
}
62+
63+
/// <summary>
64+
/// Adds an <see cref="T:Microsoft.Extensions.Logging.ILoggerProvider" /> to the logging system.
65+
/// </summary>
66+
/// <param name="provider">The <see cref="T:Microsoft.Extensions.Logging.ILoggerProvider" />.</param>
67+
public void AddProvider(ILoggerProvider provider)
68+
{
69+
if (provider == null) throw new ArgumentNullException(nameof(provider));
70+
if (_providerCollection != null)
71+
_providerCollection.AddProvider(provider);
72+
else
73+
SelfLog.WriteLine("Ignoring added logger provider {0}", provider);
74+
}
75+
}
76+
}

src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public FrameworkLogger CreateLogger(string name)
5757
return new SerilogLogger(this, _logger, name);
5858
}
5959

60-
/// <inheritdoc />
60+
/// <inheritdoc cref="IDisposable" />
6161
public IDisposable BeginScope<T>(T state)
6262
{
6363
if (CurrentScope != null)
@@ -66,7 +66,7 @@ public IDisposable BeginScope<T>(T state)
6666
// The outermost scope pushes and pops the Serilog `LogContext` - once
6767
// this enricher is on the stack, the `CurrentScope` property takes care
6868
// of the rest of the `BeginScope()` stack.
69-
var popSerilogContext = LogContext.PushProperties(this);
69+
var popSerilogContext = LogContext.Push(this);
7070
return new SerilogLoggerScope(this, state, popSerilogContext);
7171
}
7272

0 commit comments

Comments
 (0)