diff --git a/CHANGELOG.md b/CHANGELOG.md index b6444961f6..4deb0480d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ - The SDK now provides a `IsSessionActive` to allow checking the session state ([#4662](https://github.com/getsentry/sentry-dotnet/pull/4662)) - The SDK now makes use of the new SessionEndStatus `Unhandled` when capturing an unhandled but non-terminal exception, i.e. through the UnobservedTaskExceptionIntegration ([#4633](https://github.com/getsentry/sentry-dotnet/pull/4633)) +### Fixes + +- The `Serilog` integration captures _Structured Logs_ (when enabled) independently of captured Events and added Breadcrumbs ([#4691](https://github.com/getsentry/sentry-dotnet/pull/4691)) + ## 6.0.0-preview.2 ### BREAKING CHANGES diff --git a/src/Sentry.Serilog/SentrySink.cs b/src/Sentry.Serilog/SentrySink.cs index 369d52a673..4a4ad3e7f7 100644 --- a/src/Sentry.Serilog/SentrySink.cs +++ b/src/Sentry.Serilog/SentrySink.cs @@ -105,6 +105,7 @@ private void InnerEmit(LogEvent logEvent) var exception = logEvent.Exception; var template = logEvent.MessageTemplate.Text; var formatted = FormatLogEvent(logEvent); + var addedBreadcrumbForException = false; if (logEvent.Level >= _options.MinimumEventLevel) { @@ -137,11 +138,11 @@ private void InnerEmit(LogEvent logEvent) // Capturing exception events adds a breadcrumb automatically... we don't want to add another one if (exception != null) { - return; + addedBreadcrumbForException = true; } } - if (logEvent.Level >= _options.MinimumBreadcrumbLevel) + if (!addedBreadcrumbForException && logEvent.Level >= _options.MinimumBreadcrumbLevel) { Dictionary? data = null; if (exception != null && !string.IsNullOrWhiteSpace(formatted)) diff --git a/test/Sentry.Serilog.Tests/SentrySinkTests.Structured.cs b/test/Sentry.Serilog.Tests/SentrySinkTests.Structured.cs index dd1f44928d..dc061a37e5 100644 --- a/test/Sentry.Serilog.Tests/SentrySinkTests.Structured.cs +++ b/test/Sentry.Serilog.Tests/SentrySinkTests.Structured.cs @@ -134,4 +134,36 @@ public void Emit_StructuredLogging_LogEvent(bool withActiveSpan) log.TryGetAttribute("property.Structure-Property", out object? structure).Should().BeTrue(); structure.Should().Be("""[42, "42"]"""); } + + [Fact] + public void Emit_StructuredLoggingWithException_NoBreadcrumb() + { + InMemorySentryStructuredLogger capturer = new(); + _fixture.Hub.Logger.Returns(capturer); + _fixture.Options.Experimental.EnableLogs = true; + + var sut = _fixture.GetSut(); + var logger = new LoggerConfiguration().WriteTo.Sink(sut).MinimumLevel.Verbose().CreateLogger(); + + logger.Write(LogEventLevel.Error, new Exception("expected message"), "Message"); + + _fixture.Scope.Breadcrumbs.Should().BeEmpty(); + capturer.Logs.Should().ContainSingle().Which.Message.Should().Be("Message"); + } + + [Fact] + public void Emit_StructuredLoggingWithoutException_LeavesBreadcrumb() + { + InMemorySentryStructuredLogger capturer = new(); + _fixture.Hub.Logger.Returns(capturer); + _fixture.Options.Experimental.EnableLogs = true; + + var sut = _fixture.GetSut(); + var logger = new LoggerConfiguration().WriteTo.Sink(sut).MinimumLevel.Verbose().CreateLogger(); + + logger.Write(LogEventLevel.Error, (Exception?)null, "Message"); + + _fixture.Scope.Breadcrumbs.Should().ContainSingle().Which.Message.Should().Be("Message"); + capturer.Logs.Should().ContainSingle().Which.Message.Should().Be("Message"); + } }