Skip to content

Commit 7713521

Browse files
authored
Merge pull request #269 from nblumhardt/nested-scope-ordering
Correct the order of property additions from external scopes
2 parents 8575f90 + 68374e5 commit 7713521

File tree

5 files changed

+69
-21
lines changed

5 files changed

+69
-21
lines changed

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,12 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
102102
}
103103
}
104104

105+
scopeItems?.Reverse();
106+
105107
_externalScopeProvider?.ForEachScope((state, accumulatingLogEvent) =>
106108
{
107-
SerilogLoggerScope.EnrichWithStateAndCreateScopeItem(accumulatingLogEvent, propertyFactory, state, out var scopeItem);
109+
SerilogLoggerScope.EnrichWithStateAndCreateScopeItem(
110+
accumulatingLogEvent, propertyFactory, state, update: true, out var scopeItem);
108111

109112
if (scopeItem != null)
110113
{
@@ -115,7 +118,6 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
115118

116119
if (scopeItems != null)
117120
{
118-
scopeItems.Reverse();
119121
logEvent.AddPropertyIfAbsent(new LogEventProperty(ScopePropertyName, new SequenceValue(scopeItems)));
120122
}
121123
}

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

+19-12
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ public void Dispose()
4747
}
4848
}
4949

50-
public void EnrichAndCreateScopeItem(LogEvent logEvent, ILogEventPropertyFactory propertyFactory, out LogEventPropertyValue? scopeItem) => EnrichWithStateAndCreateScopeItem(logEvent, propertyFactory, _state, out scopeItem);
50+
public void EnrichAndCreateScopeItem(LogEvent logEvent, ILogEventPropertyFactory propertyFactory, out LogEventPropertyValue? scopeItem) => EnrichWithStateAndCreateScopeItem(logEvent, propertyFactory, _state, update: false, out scopeItem);
5151

52-
public static void EnrichWithStateAndCreateScopeItem(LogEvent logEvent, ILogEventPropertyFactory propertyFactory, object? state, out LogEventPropertyValue? scopeItem)
52+
public static void EnrichWithStateAndCreateScopeItem(LogEvent logEvent, ILogEventPropertyFactory propertyFactory, object? state, bool update, out LogEventPropertyValue? scopeItem)
5353
{
5454
if (state == null)
5555
{
@@ -63,8 +63,8 @@ public static void EnrichWithStateAndCreateScopeItem(LogEvent logEvent, ILogEven
6363
// Separate handling of this case eliminates boxing of Dictionary<TKey, TValue>.Enumerator.
6464
scopeItem = null;
6565
foreach (var stateProperty in dictionary)
66-
{
67-
AddProperty(logEvent, propertyFactory, stateProperty.Key, stateProperty.Value);
66+
{
67+
AddProperty(logEvent, propertyFactory, stateProperty.Key, stateProperty.Value, update);
6868
}
6969
}
7070
else if (state is IEnumerable<KeyValuePair<string, object>> stateProperties)
@@ -79,21 +79,21 @@ public static void EnrichWithStateAndCreateScopeItem(LogEvent logEvent, ILogEven
7979
}
8080
else
8181
{
82-
AddProperty(logEvent, propertyFactory, stateProperty.Key, stateProperty.Value);
82+
AddProperty(logEvent, propertyFactory, stateProperty.Key, stateProperty.Value, update);
8383
}
8484
}
8585
}
8686
#if FEATURE_ITUPLE
8787
else if (state is System.Runtime.CompilerServices.ITuple tuple && tuple.Length == 2 && tuple[0] is string s)
8888
{
8989
scopeItem = null; // Unless it's `FormattedLogValues`, these are treated as property bags rather than scope items.
90-
AddProperty(logEvent, propertyFactory, s, tuple[1]);
90+
AddProperty(logEvent, propertyFactory, s, tuple[1], update);
9191
}
9292
#else
9393
else if (state is ValueTuple<string, object?> tuple)
9494
{
9595
scopeItem = null;
96-
AddProperty(logEvent, propertyFactory, tuple.Item1, tuple.Item2);
96+
AddProperty(logEvent, propertyFactory, tuple.Item1, tuple.Item2, update);
9797
}
9898
#endif
9999
else
@@ -102,22 +102,29 @@ public static void EnrichWithStateAndCreateScopeItem(LogEvent logEvent, ILogEven
102102
}
103103
}
104104

105-
static void AddProperty(LogEvent logEvent, ILogEventPropertyFactory propertyFactory, string key, object? value)
105+
static void AddProperty(LogEvent logEvent, ILogEventPropertyFactory propertyFactory, string key, object? value, bool update)
106106
{
107-
var destructureObject = false;
107+
var destructureObjects = false;
108108

109109
if (key.StartsWith('@'))
110110
{
111111
key = SerilogLogger.GetKeyWithoutFirstSymbol(SerilogLogger.DestructureDictionary, key);
112-
destructureObject = true;
112+
destructureObjects = true;
113113
}
114114
else if (key.StartsWith('$'))
115115
{
116116
key = SerilogLogger.GetKeyWithoutFirstSymbol(SerilogLogger.StringifyDictionary, key);
117117
value = value?.ToString();
118118
}
119119

120-
var property = propertyFactory.CreateProperty(key, value, destructureObject);
121-
logEvent.AddPropertyIfAbsent(property);
120+
var property = propertyFactory.CreateProperty(key, value, destructureObjects);
121+
if (update)
122+
{
123+
logEvent.AddOrUpdateProperty(property);
124+
}
125+
else
126+
{
127+
logEvent.AddPropertyIfAbsent(property);
128+
}
122129
}
123130
}

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

+43-4
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33

44
using System.Collections;
55
using System.Diagnostics;
6+
using Microsoft.Extensions.DependencyInjection;
67
using Serilog.Events;
78
using Microsoft.Extensions.Logging;
89
using Serilog.Debugging;
910
using Serilog.Extensions.Logging.Tests.Support;
1011
using Xunit;
1112
using Serilog.Core;
13+
// ReSharper disable AccessToDisposedClosure
1214

1315
namespace Serilog.Extensions.Logging.Tests;
1416

@@ -17,9 +19,9 @@ public class SerilogLoggerTest
1719
const string Name = "test";
1820
const string TestMessage = "This is a test";
1921

20-
static Tuple<SerilogLogger, SerilogSink> SetUp(LogLevel logLevel, IExternalScopeProvider? externalScopeProvider = null)
22+
static Tuple<SerilogLogger, CollectingSink> SetUp(LogLevel logLevel, IExternalScopeProvider? externalScopeProvider = null)
2123
{
22-
var sink = new SerilogSink();
24+
var sink = new CollectingSink();
2325

2426
var serilogLogger = new LoggerConfiguration()
2527
.WriteTo.Sink(sink)
@@ -34,7 +36,7 @@ static Tuple<SerilogLogger, SerilogSink> SetUp(LogLevel logLevel, IExternalScope
3436
provider.SetScopeProvider(externalScopeProvider);
3537
}
3638

37-
return new Tuple<SerilogLogger, SerilogSink>(logger, sink);
39+
return new Tuple<SerilogLogger, CollectingSink>(logger, sink);
3840
}
3941

4042
[Fact]
@@ -516,7 +518,7 @@ public void Dispose()
516518
}
517519
}
518520
}
519-
521+
520522
[Fact]
521523
public void MismatchedMessageTemplateParameterCountIsHandled()
522524
{
@@ -579,4 +581,41 @@ public void TraceAndSpanIdsAreCaptured()
579581
Assert.Equal(Activity.Current.TraceId, evt.TraceId);
580582
Assert.Equal(Activity.Current.SpanId, evt.SpanId);
581583
}
584+
585+
[Fact]
586+
public void LoggingScopeReplacesPropertyInNestedScope()
587+
{
588+
var sink = new CollectingSink();
589+
using var logger = new LoggerConfiguration().WriteTo.Sink(sink).CreateLogger();
590+
591+
var services = new ServiceCollection();
592+
services.AddLogging(l => l.AddSerilog(logger));
593+
594+
using var serviceProvider = services.BuildServiceProvider();
595+
var msLogger = serviceProvider.GetRequiredService<ILogger<SerilogLoggerTest>>();
596+
597+
using (msLogger.BeginScope(new Dictionary<string, object> { { "EXECUTION_TAGS", "[TAG1]" } }))
598+
{
599+
msLogger.LogInformation("Message1");
600+
using (msLogger.BeginScope(new Dictionary<string, object> { { "EXECUTION_TAGS", "[TAG2]" } }))
601+
{
602+
msLogger.LogInformation("Message2");
603+
}
604+
}
605+
606+
var logEvent = sink.Writes.FirstOrDefault(e => e.MessageTemplate.Text == "Message1");
607+
Assert.NotNull(logEvent);
608+
AssertHasScalarProperty(logEvent, "EXECUTION_TAGS", "[TAG1]");
609+
610+
logEvent = sink.Writes.FirstOrDefault(e => e.MessageTemplate.Text == "Message2");
611+
Assert.NotNull(logEvent);
612+
AssertHasScalarProperty(logEvent, "EXECUTION_TAGS", "[TAG2]");
613+
}
614+
615+
static void AssertHasScalarProperty(LogEvent logEvent, string name, object? expectedValue)
616+
{
617+
Assert.True(logEvent.Properties.TryGetValue(name, out var result));
618+
var scalar = Assert.IsType<ScalarValue>(result);
619+
Assert.Equal(expectedValue, scalar.Value);
620+
}
582621
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public void AddSerilogMustRegisterAnILoggerProvider()
2121
[Fact]
2222
public void AddSerilogMustRegisterAnILoggerProviderThatForwardsLogsToStaticSerilogLogger()
2323
{
24-
var sink = new SerilogSink();
24+
var sink = new CollectingSink();
2525
Log.Logger = new LoggerConfiguration()
2626
.WriteTo.Sink(sink)
2727
.CreateLogger();
@@ -39,7 +39,7 @@ public void AddSerilogMustRegisterAnILoggerProviderThatForwardsLogsToStaticSeril
3939
[Fact]
4040
public void AddSerilogMustRegisterAnILoggerProviderThatForwardsLogsToProvidedLogger()
4141
{
42-
var sink = new SerilogSink();
42+
var sink = new CollectingSink();
4343
var serilogLogger = new LoggerConfiguration()
4444
.WriteTo.Sink(sink)
4545
.CreateLogger();

test/Serilog.Extensions.Logging.Tests/Support/SerilogSink.cs test/Serilog.Extensions.Logging.Tests/Support/CollectingSink.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
namespace Serilog.Extensions.Logging.Tests.Support;
88

9-
public class SerilogSink : ILogEventSink
9+
public class CollectingSink : ILogEventSink
1010
{
1111
public List<LogEvent> Writes { get; set; } = new();
1212

0 commit comments

Comments
 (0)