Skip to content

Commit 2a798a8

Browse files
Sync Serilog scope properties to Sentry events (#3976)
1 parent e0ed84c commit 2a798a8

8 files changed

+134
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Features
66

7+
- Serilog scope properties are now sent with Sentry events ([#3976](https://github.com/getsentry/sentry-dotnet/pull/3976))
78
- The sample seed used for sampling decisions is now propagated, for use in downstream custom trace samplers ([#3951](https://github.com/getsentry/sentry-dotnet/pull/3951))
89

910
### Fixes
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
namespace Sentry.Serilog;
2+
3+
/// <summary>
4+
/// Extensions for <see cref="SentryOptions"/> to add Serilog specific configuration.
5+
/// </summary>
6+
public static class SentryOptionExtensions
7+
{
8+
/// <summary>
9+
/// Ensures Serilog scope properties get applied to Sentry events. If you are not initialising Sentry when
10+
/// configuring the Sentry sink for Serilog then you should call this method in the options callback for whichever
11+
/// Sentry integration you are using to initialise Sentry.
12+
/// </summary>
13+
/// <param name="options"></param>
14+
/// <typeparam name="T"></typeparam>
15+
/// <returns></returns>
16+
public static T ApplySerilogScopeToEvents<T>(this T options) where T : SentryOptions
17+
{
18+
options.AddEventProcessor(new SerilogScopeEventProcessor(options));
19+
return options;
20+
}
21+
}

src/Sentry.Serilog/SentrySinkExtensions.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,14 @@ internal static void ConfigureSentrySerilogOptions(
326326
sentrySerilogOptions.DefaultTags.Add(tag.Key, tag.Value);
327327
}
328328
}
329+
330+
// This only works when the SDK is initialized using the LoggerSinkConfiguration extensions. If the SDK is
331+
// initialized using some other integration then the processor will need to be added manually to whichever
332+
// options are used to initialize the SDK.
333+
if (sentrySerilogOptions.InitializeSdk)
334+
{
335+
sentrySerilogOptions.ApplySerilogScopeToEvents();
336+
}
329337
}
330338

331339
/// <summary>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using Serilog.Context;
2+
3+
namespace Sentry.Serilog;
4+
5+
/// <summary>
6+
/// Sentry event processor that applies properties from the Serilog scope to Sentry events.
7+
/// </summary>
8+
internal class SerilogScopeEventProcessor : ISentryEventProcessor
9+
{
10+
private readonly SentryOptions _options;
11+
12+
/// <summary>
13+
/// This processor extracts properties from the Serilog context and applies these to Sentry events.
14+
/// </summary>
15+
public SerilogScopeEventProcessor(SentryOptions options)
16+
{
17+
_options = options;
18+
_options.LogDebug("Initializing Serilog scope event processor.");
19+
}
20+
21+
/// <inheritdoc cref="ISentryEventProcessor"/>
22+
public SentryEvent Process(SentryEvent @event)
23+
{
24+
_options.LogDebug("Running Serilog scope event processor on: Event {0}", @event.EventId);
25+
26+
// This is a bit of a hack. Serilog doesn't have any hooks that let us inspect the context. We can, however,
27+
// apply the context to a dummy log event and then copy across the properties from that log event to our Sentry
28+
// event.
29+
// See: https://github.com/getsentry/sentry-dotnet/issues/3544#issuecomment-2307884977
30+
var enricher = LogContext.Clone();
31+
var logEvent = new LogEvent(DateTimeOffset.Now, LogEventLevel.Error, null, MessageTemplate.Empty, []);
32+
enricher.Enrich(logEvent, new LogEventPropertyFactory());
33+
foreach (var (key, value) in logEvent.Properties)
34+
{
35+
if (!@event.Tags.ContainsKey(key))
36+
{
37+
// Potentially we could be doing SetData here instead of SetTag. See DefaultSentryScopeStateProcessor.
38+
@event.SetTag(
39+
key,
40+
value is ScalarValue { Value: string stringValue }
41+
? stringValue
42+
: value.ToString()
43+
);
44+
}
45+
}
46+
return @event;
47+
}
48+
49+
private class LogEventPropertyFactory : ILogEventPropertyFactory
50+
{
51+
public LogEventProperty CreateProperty(string name, object? value, bool destructureObjects = false)
52+
{
53+
var scalarValue = new ScalarValue(value);
54+
return new LogEventProperty(name, scalarValue);
55+
}
56+
}
57+
}

test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
[assembly: System.CLSCompliant(true)]
22
namespace Sentry.Serilog
33
{
4+
public static class SentryOptionExtensions
5+
{
6+
public static T ApplySerilogScopeToEvents<T>(this T options)
7+
where T : Sentry.SentryOptions { }
8+
}
49
public class SentrySerilogOptions : Sentry.SentryOptions
510
{
611
public SentrySerilogOptions() { }

test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
[assembly: System.CLSCompliant(true)]
22
namespace Sentry.Serilog
33
{
4+
public static class SentryOptionExtensions
5+
{
6+
public static T ApplySerilogScopeToEvents<T>(this T options)
7+
where T : Sentry.SentryOptions { }
8+
}
49
public class SentrySerilogOptions : Sentry.SentryOptions
510
{
611
public SentrySerilogOptions() { }

test/Sentry.Serilog.Tests/ApiApprovalTests.Run.Net4_8.verified.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
[assembly: System.CLSCompliant(true)]
22
namespace Sentry.Serilog
33
{
4+
public static class SentryOptionExtensions
5+
{
6+
public static T ApplySerilogScopeToEvents<T>(this T options)
7+
where T : Sentry.SentryOptions { }
8+
}
49
public class SentrySerilogOptions : Sentry.SentryOptions
510
{
611
public SentrySerilogOptions() { }
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using Microsoft.Extensions.Logging;
2+
3+
namespace Sentry.Serilog.Tests;
4+
5+
public class SerilogScopeEventProcessorTests
6+
{
7+
[Theory]
8+
[InlineData("42", "42")]
9+
[InlineData(42, "42")]
10+
public void Emit_WithException_CreatesEventWithException(object value, string expected)
11+
{
12+
// Arrange
13+
var options = new SentryOptions();
14+
var sut = new SerilogScopeEventProcessor(options);
15+
16+
using var log = new LoggerConfiguration().CreateLogger();
17+
var factory = new LoggerFactory().AddSerilog(log);
18+
var logger = factory.CreateLogger<SerilogScopeEventProcessorTests>();
19+
20+
// Act
21+
SentryEvent evt;
22+
using (logger.BeginScope(new Dictionary<string, object> { ["Answer"] = value }))
23+
{
24+
evt = new SentryEvent();
25+
sut.Process(evt);
26+
}
27+
28+
// Assert
29+
evt.Tags.Should().ContainKey("Answer");
30+
evt.Tags["Answer"].Should().Be(expected);
31+
}
32+
}

0 commit comments

Comments
 (0)