Skip to content

Commit bc44a18

Browse files
authored
Merge pull request #43 from nblumhardt/fix-minimum-level-overrides
Fix minimum level overrides in reloadable loggers
2 parents a2acc63 + 00f00d8 commit bc44a18

File tree

4 files changed

+122
-26
lines changed

4 files changed

+122
-26
lines changed

src/Serilog.Extensions.Hosting/Extensions/Hosting/CachingReloadableLogger.cs

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ public ILogger ForContext(IEnumerable<ILogEventEnricher> enrichers)
7676
if (_frozen)
7777
return _cached.ForContext(enrichers);
7878

79-
8079
if (_reloadableLogger.CreateChild(
8180
_root,
8281
this,
@@ -100,25 +99,46 @@ public ILogger ForContext(string propertyName, object value, bool destructureObj
10099
if (_frozen)
101100
return _cached.ForContext(propertyName, value, destructureObjects);
102101

103-
// There's a trade-off, here. Changes to destructuring configuration won't be picked up, but,
104-
// it's better to not extend the lifetime of `value` or pass it between threads unexpectedly.
105-
var eager = ReloadLogger();
106-
if (!eager.BindProperty(propertyName, value, destructureObjects, out var property))
107-
return this;
108-
109-
var enricher = new FixedPropertyEnricher(property);
102+
ILogger child;
103+
if (value == null || value is string || value.GetType().IsPrimitive || value.GetType().IsEnum)
104+
{
105+
// Safe to extend the lifetime of `value` by closing over it.
106+
// This ensures `SourceContext` is passed through appropriately and triggers minimum level overrides.
107+
if (_reloadableLogger.CreateChild(
108+
_root,
109+
this,
110+
_cached,
111+
p => p.ForContext(propertyName, value, destructureObjects),
112+
out child,
113+
out var newRoot,
114+
out var newCached,
115+
out var frozen))
116+
{
117+
Update(newRoot, newCached, frozen);
118+
}
119+
}
120+
else
121+
{
122+
// It's not safe to extend the lifetime of `value` or pass it unexpectedly between threads.
123+
// Changes to destructuring configuration won't be picked up by the cached logger.
124+
var eager = ReloadLogger();
125+
if (!eager.BindProperty(propertyName, value, destructureObjects, out var property))
126+
return this;
127+
128+
var enricher = new FixedPropertyEnricher(property);
110129

111-
if (_reloadableLogger.CreateChild(
112-
_root,
113-
this,
114-
_cached,
115-
p => p.ForContext(enricher),
116-
out var child,
117-
out var newRoot,
118-
out var newCached,
119-
out var frozen))
120-
{
121-
Update(newRoot, newCached, frozen);
130+
if (_reloadableLogger.CreateChild(
131+
_root,
132+
this,
133+
_cached,
134+
p => p.ForContext(enricher),
135+
out child,
136+
out var newRoot,
137+
out var newCached,
138+
out var frozen))
139+
{
140+
Update(newRoot, newCached, frozen);
141+
}
122142
}
123143

124144
return child;

test/Serilog.Extensions.Hosting.Tests/LoggerSettingsConfigurationExtensionsTests.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
using Microsoft.Extensions.DependencyInjection;
1+
using System.Collections.Generic;
2+
using Microsoft.Extensions.DependencyInjection;
23
using Serilog.Core;
4+
using Serilog.Events;
35
using Serilog.Extensions.Hosting.Tests.Support;
46
using Xunit;
57

@@ -10,10 +12,10 @@ public class LoggerSettingsConfigurationExtensionsTests
1012
[Fact]
1113
public void SinksAreInjectedFromTheServiceProvider()
1214
{
13-
var sink = new SerilogSink();
15+
var emittedEvents = new List<LogEvent>();
1416

1517
var serviceCollection = new ServiceCollection();
16-
serviceCollection.AddSingleton<ILogEventSink>(sink);
18+
serviceCollection.AddSingleton<ILogEventSink>(new ListSink(emittedEvents));
1719
using var services = serviceCollection.BuildServiceProvider();
1820

1921
using var logger = new LoggerConfiguration()
@@ -22,7 +24,7 @@ public void SinksAreInjectedFromTheServiceProvider()
2224

2325
logger.Information("Hello, world!");
2426

25-
var evt = Assert.Single(sink.Writes);
27+
var evt = Assert.Single(emittedEvents);
2628
Assert.Equal("Hello, world!", evt!.MessageTemplate.Text);
2729
}
2830
}

test/Serilog.Extensions.Hosting.Tests/ReloadableLoggerTests.cs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
#if !NO_RELOADABLE_LOGGER
22

3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using Serilog.Core;
6+
using Serilog.Events;
7+
using Serilog.Extensions.Hosting.Tests.Support;
38
using Xunit;
49

510
namespace Serilog.Extensions.Hosting.Tests
@@ -33,6 +38,70 @@ public void CachingReloadableLoggerRemainsUsableAfterFreezing()
3338
contextual.Information("Third");
3439
contextual.Information("Fourth"); // No crash :-)
3540
}
41+
42+
[Fact]
43+
public void ReloadableLoggerRespectsMinimumLevelOverrides()
44+
{
45+
var emittedEvents = new List<LogEvent>();
46+
var logger = new LoggerConfiguration()
47+
.MinimumLevel.Override("Test", LogEventLevel.Warning)
48+
.WriteTo.Sink(new ListSink(emittedEvents))
49+
.CreateBootstrapLogger();
50+
51+
var limited = logger
52+
.ForContext("X", 1)
53+
.ForContext(Constants.SourceContextPropertyName, "Test.Stuff");
54+
55+
var notLimited = logger.ForContext<ReloadableLoggerTests>();
56+
57+
foreach (var context in new[] { limited, notLimited })
58+
{
59+
// Suppressed by both sinks
60+
context.Debug("First");
61+
62+
// Suppressed by the limited logger
63+
context.Information("Second");
64+
65+
// Emitted by both loggers
66+
context.Warning("Third");
67+
}
68+
69+
Assert.Equal(3, emittedEvents.Count);
70+
Assert.Equal(2, emittedEvents.Count(le => le.Level == LogEventLevel.Warning));
71+
}
72+
73+
[Fact]
74+
public void ReloadableLoggersRecordEnrichment()
75+
{
76+
var emittedEvents = new List<LogEvent>();
77+
78+
var logger = new LoggerConfiguration()
79+
.WriteTo.Sink(new ListSink(emittedEvents))
80+
.CreateBootstrapLogger();
81+
82+
var outer = logger
83+
.ForContext("A", new object());
84+
var inner = outer.ForContext("B", "test");
85+
86+
inner.Information("First");
87+
88+
logger.Reload(lc => lc.WriteTo.Sink(new ListSink(emittedEvents)));
89+
90+
inner.Information("Second");
91+
92+
logger.Freeze();
93+
94+
inner.Information("Third");
95+
96+
outer.ForContext("B", "test").Information("Fourth");
97+
98+
logger.ForContext("A", new object())
99+
.ForContext("B", "test")
100+
.Information("Fifth");
101+
102+
Assert.Equal(5, emittedEvents.Count);
103+
Assert.All(emittedEvents, e => Assert.Equal(2, e.Properties.Count));
104+
}
36105
}
37106
}
38107

test/Serilog.Extensions.Hosting.Tests/Support/SerilogSink.cs renamed to test/Serilog.Extensions.Hosting.Tests/Support/ListSink.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,18 @@
77

88
namespace Serilog.Extensions.Hosting.Tests.Support
99
{
10-
public class SerilogSink : ILogEventSink
10+
public class ListSink : ILogEventSink
1111
{
12-
public List<LogEvent> Writes { get; set; } = new List<LogEvent>();
12+
readonly List<LogEvent> _list;
13+
14+
public ListSink(List<LogEvent> list)
15+
{
16+
_list = list;
17+
}
1318

1419
public void Emit(LogEvent logEvent)
1520
{
16-
Writes.Add(logEvent);
21+
_list.Add(logEvent);
1722
}
1823
}
1924
}

0 commit comments

Comments
 (0)