Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 ([email protected](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);
}
}
Loading