Skip to content

Commit 395cdc2

Browse files
Added a SerilogScopeEventProcessor to sync Serilog scope properties to Sentry events
1 parent 9925968 commit 395cdc2

File tree

3 files changed

+81
-0
lines changed

3 files changed

+81
-0
lines changed
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: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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(key, value.ToString());
39+
}
40+
}
41+
return @event;
42+
}
43+
44+
private class LogEventPropertyFactory : ILogEventPropertyFactory
45+
{
46+
public LogEventProperty CreateProperty(string name, object? value, bool destructureObjects = false)
47+
{
48+
var scalarValue = new ScalarValue(value);
49+
return new LogEventProperty(name, scalarValue);
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)