Skip to content

Commit

Permalink
Sync Serilog scope properties to Sentry events (#3976)
Browse files Browse the repository at this point in the history
  • Loading branch information
jamescrosswell authored Feb 19, 2025
1 parent e0ed84c commit 2a798a8
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Features

- Serilog scope properties are now sent with Sentry events ([#3976](https://github.com/getsentry/sentry-dotnet/pull/3976))
- 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))

### Fixes
Expand Down
21 changes: 21 additions & 0 deletions src/Sentry.Serilog/SentryOptionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Sentry.Serilog;

/// <summary>
/// Extensions for <see cref="SentryOptions"/> to add Serilog specific configuration.
/// </summary>
public static class SentryOptionExtensions
{
/// <summary>
/// Ensures Serilog scope properties get applied to Sentry events. If you are not initialising Sentry when
/// configuring the Sentry sink for Serilog then you should call this method in the options callback for whichever
/// Sentry integration you are using to initialise Sentry.
/// </summary>
/// <param name="options"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static T ApplySerilogScopeToEvents<T>(this T options) where T : SentryOptions
{
options.AddEventProcessor(new SerilogScopeEventProcessor(options));
return options;
}
}
8 changes: 8 additions & 0 deletions src/Sentry.Serilog/SentrySinkExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,14 @@ internal static void ConfigureSentrySerilogOptions(
sentrySerilogOptions.DefaultTags.Add(tag.Key, tag.Value);
}
}

// This only works when the SDK is initialized using the LoggerSinkConfiguration extensions. If the SDK is
// initialized using some other integration then the processor will need to be added manually to whichever
// options are used to initialize the SDK.
if (sentrySerilogOptions.InitializeSdk)
{
sentrySerilogOptions.ApplySerilogScopeToEvents();
}
}

/// <summary>
Expand Down
57 changes: 57 additions & 0 deletions src/Sentry.Serilog/SerilogScopeEventProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using Serilog.Context;

namespace Sentry.Serilog;

/// <summary>
/// Sentry event processor that applies properties from the Serilog scope to Sentry events.
/// </summary>
internal class SerilogScopeEventProcessor : ISentryEventProcessor
{
private readonly SentryOptions _options;

/// <summary>
/// This processor extracts properties from the Serilog context and applies these to Sentry events.
/// </summary>
public SerilogScopeEventProcessor(SentryOptions options)
{
_options = options;
_options.LogDebug("Initializing Serilog scope event processor.");
}

/// <inheritdoc cref="ISentryEventProcessor"/>
public SentryEvent Process(SentryEvent @event)
{
_options.LogDebug("Running Serilog scope event processor on: Event {0}", @event.EventId);

// This is a bit of a hack. Serilog doesn't have any hooks that let us inspect the context. We can, however,
// apply the context to a dummy log event and then copy across the properties from that log event to our Sentry
// event.
// See: https://github.com/getsentry/sentry-dotnet/issues/3544#issuecomment-2307884977
var enricher = LogContext.Clone();
var logEvent = new LogEvent(DateTimeOffset.Now, LogEventLevel.Error, null, MessageTemplate.Empty, []);
enricher.Enrich(logEvent, new LogEventPropertyFactory());
foreach (var (key, value) in logEvent.Properties)
{
if (!@event.Tags.ContainsKey(key))
{
// Potentially we could be doing SetData here instead of SetTag. See DefaultSentryScopeStateProcessor.
@event.SetTag(
key,
value is ScalarValue { Value: string stringValue }
? stringValue
: value.ToString()
);
}
}
return @event;
}

private class LogEventPropertyFactory : ILogEventPropertyFactory
{
public LogEventProperty CreateProperty(string name, object? value, bool destructureObjects = false)
{
var scalarValue = new ScalarValue(value);
return new LogEventProperty(name, scalarValue);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
[assembly: System.CLSCompliant(true)]
namespace Sentry.Serilog
{
public static class SentryOptionExtensions
{
public static T ApplySerilogScopeToEvents<T>(this T options)
where T : Sentry.SentryOptions { }
}
public class SentrySerilogOptions : Sentry.SentryOptions
{
public SentrySerilogOptions() { }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
[assembly: System.CLSCompliant(true)]
namespace Sentry.Serilog
{
public static class SentryOptionExtensions
{
public static T ApplySerilogScopeToEvents<T>(this T options)
where T : Sentry.SentryOptions { }
}
public class SentrySerilogOptions : Sentry.SentryOptions
{
public SentrySerilogOptions() { }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
[assembly: System.CLSCompliant(true)]
namespace Sentry.Serilog
{
public static class SentryOptionExtensions
{
public static T ApplySerilogScopeToEvents<T>(this T options)
where T : Sentry.SentryOptions { }
}
public class SentrySerilogOptions : Sentry.SentryOptions
{
public SentrySerilogOptions() { }
Expand Down
32 changes: 32 additions & 0 deletions test/Sentry.Serilog.Tests/SerilogScopeEventProcessorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Microsoft.Extensions.Logging;

namespace Sentry.Serilog.Tests;

public class SerilogScopeEventProcessorTests
{
[Theory]
[InlineData("42", "42")]
[InlineData(42, "42")]
public void Emit_WithException_CreatesEventWithException(object value, string expected)
{
// Arrange
var options = new SentryOptions();
var sut = new SerilogScopeEventProcessor(options);

using var log = new LoggerConfiguration().CreateLogger();
var factory = new LoggerFactory().AddSerilog(log);
var logger = factory.CreateLogger<SerilogScopeEventProcessorTests>();

// Act
SentryEvent evt;
using (logger.BeginScope(new Dictionary<string, object> { ["Answer"] = value }))
{
evt = new SentryEvent();
sut.Process(evt);
}

// Assert
evt.Tags.Should().ContainKey("Answer");
evt.Tags["Answer"].Should().Be(expected);
}
}

0 comments on commit 2a798a8

Please sign in to comment.