Skip to content

Commit cda9e22

Browse files
committed
Fixes #50 - include named scopes
1 parent 1cb2c64 commit cda9e22

File tree

4 files changed

+93
-5
lines changed

4 files changed

+93
-5
lines changed

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

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Serilog.Core;
1313
using Serilog.Events;
1414
using FrameworkLogger = Microsoft.Extensions.Logging.ILogger;
15+
using System.Collections.Generic;
1516

1617
namespace Serilog.Extensions.Logging
1718
{
@@ -21,17 +22,49 @@ namespace Serilog.Extensions.Logging
2122
public class SerilogLoggerProvider : ILoggerProvider, ILogEventEnricher
2223
{
2324
internal const string OriginalFormatPropertyName = "{OriginalFormat}";
25+
internal const string ScopePropertyName = "Scope";
2426

2527
// May be null; if it is, Log.Logger will be lazily used
2628
readonly ILogger _logger;
2729
readonly Action _dispose;
30+
readonly bool _includeNamedScopes;
31+
32+
/// <summary>
33+
/// Construct a <see cref="SerilogLoggerProvider"/>.
34+
/// </summary>
35+
public SerilogLoggerProvider()
36+
: this(null)
37+
{
38+
}
39+
40+
/// <summary>
41+
/// Construct a <see cref="SerilogLoggerProvider"/>.
42+
/// </summary>
43+
/// <param name="logger">A Serilog logger to pipe events through; if null, the static <see cref="Log"/> class will be used.</param>
44+
public SerilogLoggerProvider(ILogger logger)
45+
: this(logger, false)
46+
{
47+
}
2848

2949
/// <summary>
3050
/// Construct a <see cref="SerilogLoggerProvider"/>.
3151
/// </summary>
3252
/// <param name="logger">A Serilog logger to pipe events through; if null, the static <see cref="Log"/> class will be used.</param>
3353
/// <param name="dispose">If true, the provided logger or static log class will be disposed/closed when the provider is disposed.</param>
34-
public SerilogLoggerProvider(ILogger logger = null, bool dispose = false)
54+
public SerilogLoggerProvider(ILogger logger, bool dispose)
55+
: this(logger, dispose, false)
56+
{
57+
}
58+
59+
/// <summary>
60+
/// Construct a <see cref="SerilogLoggerProvider"/>.
61+
/// </summary>
62+
/// <param name="logger">A Serilog logger to pipe events through; if null, the static <see cref="Log"/> class will be used.</param>
63+
/// <param name="dispose">If true, the provided logger or static log class will be disposed/closed when the provider is disposed.</param>
64+
/// <param name="includeNamedScopes">Indicates whether a <code>Scope</code> property should be generated when
65+
/// <see cref="Microsoft.Extensions.Logging.ILogger.BeginScope"/> is called with <see cref="string"/> arguments. The
66+
/// default is false.</param>
67+
public SerilogLoggerProvider(ILogger logger, bool dispose, bool includeNamedScopes)
3568
{
3669
if (logger != null)
3770
_logger = logger.ForContext(new[] { this });
@@ -43,6 +76,8 @@ public SerilogLoggerProvider(ILogger logger = null, bool dispose = false)
4376
else
4477
_dispose = Log.CloseAndFlush;
4578
}
79+
80+
_includeNamedScopes = includeNamedScopes;
4681
}
4782

4883
/// <inheritdoc />
@@ -64,6 +99,25 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
6499
{
65100
scope.Enrich(logEvent, propertyFactory);
66101
}
102+
103+
if (_includeNamedScopes)
104+
{
105+
List<ScalarValue> names = null;
106+
for (var scope = CurrentScope; scope != null; scope = scope.Parent)
107+
{
108+
string name;
109+
if (scope.TryGetName(out name))
110+
{
111+
names = names ?? new List<ScalarValue>();
112+
names.Insert(0, new ScalarValue(name));
113+
}
114+
}
115+
116+
if (names != null)
117+
{
118+
logEvent.AddPropertyIfAbsent(new LogEventProperty(ScopePropertyName, new SequenceValue(names)));
119+
}
120+
}
67121
}
68122

69123
#if ASYNCLOCAL

src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,11 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
7272
}
7373
}
7474
}
75+
76+
public bool TryGetName(out string name)
77+
{
78+
name = _state as string;
79+
return name != null;
80+
}
7581
}
7682
}

src/Serilog.Extensions.Logging/SerilogLoggerFactoryExtensions.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,19 @@ public static class SerilogLoggerFactoryExtensions
2020
/// <param name="dispose">When true, dispose <paramref name="logger"/> when the framework disposes the provider. If the
2121
/// logger is not specified but <paramref name="dispose"/> is true, the <see cref="Log.CloseAndFlush()"/> method will be
2222
/// called on the static <see cref="Log"/> class instead.</param>
23+
/// <param name="includeNamedScopes">Indicates whether a <code>Scope</code> property should be generated when
24+
/// <see cref="Microsoft.Extensions.Logging.ILogger.BeginScope"/> is called with <see cref="string"/> arguments. The
25+
/// default is false.</param>
2326
/// <returns>The logger factory.</returns>
2427
public static ILoggerFactory AddSerilog(
2528
this ILoggerFactory factory,
2629
ILogger logger = null,
27-
bool dispose = false)
30+
bool dispose = false,
31+
bool includeNamedScopes = false)
2832
{
2933
if (factory == null) throw new ArgumentNullException(nameof(factory));
3034

31-
factory.AddProvider(new SerilogLoggerProvider(logger, dispose));
35+
factory.AddProvider(new SerilogLoggerProvider(logger, dispose, includeNamedScopes));
3236

3337
return factory;
3438
}

test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class SerilogLoggerTest
1919
private const string Name = "test";
2020
private const string TestMessage = "This is a test";
2121

22-
private Tuple<SerilogLogger, SerilogSink> SetUp(LogLevel logLevel)
22+
private Tuple<SerilogLogger, SerilogSink> SetUp(LogLevel logLevel, bool includeNamedScopes = false)
2323
{
2424
var sink = new SerilogSink();
2525

@@ -28,7 +28,7 @@ private Tuple<SerilogLogger, SerilogSink> SetUp(LogLevel logLevel)
2828

2929
SetMinLevel(config, logLevel);
3030

31-
var provider = new SerilogLoggerProvider(config.CreateLogger());
31+
var provider = new SerilogLoggerProvider(config.CreateLogger(), false, includeNamedScopes);
3232
var logger = (SerilogLogger)provider.CreateLogger(Name);
3333

3434
return new Tuple<SerilogLogger, SerilogSink>(logger, sink);
@@ -364,6 +364,30 @@ public void BeginScopeDoesNotModifyKeyWhenDestructurerIsNotUsedInDictionary()
364364
Assert.True(sink.Writes[0].Properties.ContainsKey("FirstName"));
365365
}
366366

367+
[Fact]
368+
public void NamedScopesAreCapturedWhenRequested()
369+
{
370+
var t = SetUp(LogLevel.Trace, includeNamedScopes: true);
371+
var logger = t.Item1;
372+
var sink = t.Item2;
373+
374+
using (logger.BeginScope("Outer"))
375+
using (logger.BeginScope("Inner"))
376+
{
377+
logger.Log(LogLevel.Information, 0, TestMessage, null, null);
378+
}
379+
380+
Assert.Equal(1, sink.Writes.Count);
381+
382+
LogEventPropertyValue scopeValue;
383+
Assert.True(sink.Writes[0].Properties.TryGetValue(SerilogLoggerProvider.ScopePropertyName, out scopeValue));
384+
385+
var items = (scopeValue as SequenceValue)?.Elements.Select(e => ((ScalarValue)e).Value).Cast<string>().ToArray();
386+
Assert.Equal(2, items.Length);
387+
Assert.Equal("Outer", items[0]);
388+
Assert.Equal("Inner", items[1]);
389+
}
390+
367391
private class FoodScope : IEnumerable<KeyValuePair<string, object>>
368392
{
369393
readonly string _name;

0 commit comments

Comments
 (0)