From 2f8d0eda4aec3209957f67d0ed2e2498f6a7c1a7 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Mon, 31 Aug 2026 21:33:52 +1200 Subject: [PATCH 1/2] feat: Provide the exception in the Hint passed to BeforeBreadcrumb Breadcrumbs created from an exception now carry that exception in the SentryHint passed to the BeforeBreadcrumb callback, under the new HintTypes.Exception key. This mirrors the Java SDK, which puts the originating log event into the Hint it passes to beforeBreadcrumb. Covers the automatic "Exception" breadcrumb the Hub leaves for captured exception events, as well as the breadcrumbs created by the Microsoft.Extensions.Logging, Serilog, NLog and log4net integrations. Co-Authored-By: Claude Opus 5 --- src/Sentry.Extensions.Logging/SentryLogger.cs | 3 +- src/Sentry.Log4Net/SentryAppender.cs | 11 ++++- src/Sentry.NLog/SentryTarget.cs | 4 +- src/Sentry.Serilog/SentrySink.cs | 4 +- src/Sentry/HintTypes.cs | 5 +++ src/Sentry/HubExtensions.cs | 30 ++++++++++++-- src/Sentry/Internal/Hub.cs | 14 ++++++- .../SentryLoggerTests.cs | 20 +++++++++ .../SentryAppenderTests.cs | 22 ++++++++++ test/Sentry.NLog.Tests/SentryTargetTests.cs | 21 ++++++++++ test/Sentry.Serilog.Tests/SentrySinkTests.cs | 23 +++++++++++ ...iApprovalTests.Run.DotNet10_0.verified.txt | 2 + ...piApprovalTests.Run.DotNet8_0.verified.txt | 2 + ...piApprovalTests.Run.DotNet9_0.verified.txt | 2 + .../ApiApprovalTests.Run.Net4_8.verified.txt | 2 + test/Sentry.Tests/HubTests.cs | 41 +++++++++++++++++++ 16 files changed, 198 insertions(+), 8 deletions(-) diff --git a/src/Sentry.Extensions.Logging/SentryLogger.cs b/src/Sentry.Extensions.Logging/SentryLogger.cs index 65eb1ff4da..1c0cdbc4d3 100644 --- a/src/Sentry.Extensions.Logging/SentryLogger.cs +++ b/src/Sentry.Extensions.Logging/SentryLogger.cs @@ -82,7 +82,8 @@ public void Log( CategoryName, null, data, - logLevel.ToBreadcrumbLevel()); + logLevel.ToBreadcrumbLevel(), + exception is null ? null : new SentryHint(HintTypes.Exception, exception)); } } diff --git a/src/Sentry.Log4Net/SentryAppender.cs b/src/Sentry.Log4Net/SentryAppender.cs index a4bc0367f1..d2083bd7be 100644 --- a/src/Sentry.Log4Net/SentryAppender.cs +++ b/src/Sentry.Log4Net/SentryAppender.cs @@ -172,7 +172,16 @@ private void AddBreadcrumbFromLoggingEvent(LoggingEvent loggingEvent) .Where(kvp => kvp.Value != null) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value!.ToString() ?? ""); - _hub.AddBreadcrumb(message, category, type: null, data, level ?? default); + var exception = loggingEvent.ExceptionObject; + + _hub.AddBreadcrumb( + clock: null, + message, + category, + type: null, + data, + level ?? default, + hint: exception is null ? null : new SentryHint(HintTypes.Exception, exception)); return; } diff --git a/src/Sentry.NLog/SentryTarget.cs b/src/Sentry.NLog/SentryTarget.cs index e1777e49ff..c2f075e401 100644 --- a/src/Sentry.NLog/SentryTarget.cs +++ b/src/Sentry.NLog/SentryTarget.cs @@ -425,8 +425,10 @@ private void CreateBreadcrumb(LogEventInfo logEvent, Exception? exception, bool _clock, message, breadcrumbCategory, + type: null, data: data, - level: logEvent.Level.ToBreadcrumbLevel()); + level: logEvent.Level.ToBreadcrumbLevel(), + hint: exception is null ? null : new SentryHint(HintTypes.Exception, exception)); } private void CreateSentryEvent(LogEventInfo logEvent, Exception? exception, bool shouldIncludeProperties, IHub hub) diff --git a/src/Sentry.Serilog/SentrySink.cs b/src/Sentry.Serilog/SentrySink.cs index 8076d2da6d..4e1dc3602d 100644 --- a/src/Sentry.Serilog/SentrySink.cs +++ b/src/Sentry.Serilog/SentrySink.cs @@ -161,8 +161,10 @@ private void InnerEmit(LogEvent logEvent) ? exception?.Message ?? "" : formatted, context, + type: null, data: data, - level: logEvent.Level.ToBreadcrumbLevel()); + level: logEvent.Level.ToBreadcrumbLevel(), + hint: exception is null ? null : new SentryHint(HintTypes.Exception, exception)); } // Read the options from the Hub, rather than the Sink's Serilog-Options, because 'EnableLogs' is declared in the base 'SentryOptions', rather than the derived 'SentrySerilogOptions'. diff --git a/src/Sentry/HintTypes.cs b/src/Sentry/HintTypes.cs index 12439822f2..1d5d5b36aa 100644 --- a/src/Sentry/HintTypes.cs +++ b/src/Sentry/HintTypes.cs @@ -9,4 +9,9 @@ public static class HintTypes /// Used for HttpResponseMessage hints /// public const string HttpResponseMessage = "http-response-message"; + + /// + /// Used for the that a breadcrumb was created from + /// + public const string Exception = "exception"; } diff --git a/src/Sentry/HubExtensions.cs b/src/Sentry/HubExtensions.cs index 8874741448..26958a7e71 100644 --- a/src/Sentry/HubExtensions.cs +++ b/src/Sentry/HubExtensions.cs @@ -191,6 +191,32 @@ public static void AddBreadcrumb( string? type = null, IDictionary? data = null, BreadcrumbLevel level = default) + => hub.AddBreadcrumb(clock, message, category, type, data, level, hint: null); + + /// + /// Adds a breadcrumb using a custom which allows better testability. + /// + /// The Hub which holds the scope stack. + /// The system clock. + /// The message. + /// Category. + /// Breadcrumb type. + /// Additional data. + /// Breadcrumb level. + /// A hint provided with the breadcrumb in the BeforeBreadcrumb callback. + /// + /// This method is to be used by integrations to allow testing. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static void AddBreadcrumb( + this IHub hub, + ISystemClock? clock, + string message, + string? category, + string? type, + IDictionary? data, + BreadcrumbLevel level, + SentryHint? hint) { // Not to throw on code that ignores nullability warnings. if (hub.IsNull()) @@ -207,9 +233,7 @@ public static void AddBreadcrumb( level ); - hub.AddBreadcrumb( - breadcrumb - ); + hub.AddBreadcrumb(breadcrumb, hint); } /// diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index ab97ab60ae..fe69aed850 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -587,7 +587,19 @@ private void AddBreadcrumbForException(SentryEvent evt, Scope scope) {"exception_message", exceptionMessage} }; } - scope.AddBreadcrumb(breadcrumbMessage, "Exception", data: data, level: BreadcrumbLevel.Fatal); + + // Provide the original exception in a Hint, so that the BeforeBreadcrumb callback can filter or modify + // the breadcrumb based on the exception itself (e.g. by type) rather than by matching on its message. + var hint = new SentryHint(_options); + hint.Items[HintTypes.Exception] = exception; + + var breadcrumb = new Breadcrumb( + message: breadcrumbMessage, + data: data, + category: "Exception", + level: BreadcrumbLevel.Fatal); + + scope.AddBreadcrumb(breadcrumb, hint); } catch (Exception e) { diff --git a/test/Sentry.Extensions.Logging.Tests/SentryLoggerTests.cs b/test/Sentry.Extensions.Logging.Tests/SentryLoggerTests.cs index 02f2bf9531..8a748b030a 100644 --- a/test/Sentry.Extensions.Logging.Tests/SentryLoggerTests.cs +++ b/test/Sentry.Extensions.Logging.Tests/SentryLoggerTests.cs @@ -58,6 +58,26 @@ public void Log_EventWithoutException_LeavesBreadcrumb() _fixture.Scope.Breadcrumbs.Should().NotBeEmpty(); } + [Fact] + public void Log_BreadcrumbWithException_ProvidesExceptionInHint() + { + SentryHint hint = null; + _fixture.Scope.Options.SetBeforeBreadcrumb((breadcrumb, h) => + { + hint = h; + return breadcrumb; + }); + var expectedException = new Exception("expected message"); + + var sut = _fixture.GetSut(); + + // LogLevel.Warning is below the default MinimumEventLevel, so only a breadcrumb is added + sut.Log(LogLevel.Warning, default, null, expectedException, null); + + hint.Should().NotBeNull(); + hint.Items[HintTypes.Exception].Should().BeSameAs(expectedException); + } + [Fact] public void Log_WithEventId_EventIdAsTagOnEvent() { diff --git a/test/Sentry.Log4Net.Tests/SentryAppenderTests.cs b/test/Sentry.Log4Net.Tests/SentryAppenderTests.cs index cfcad5c7c9..afd4b13000 100644 --- a/test/Sentry.Log4Net.Tests/SentryAppenderTests.cs +++ b/test/Sentry.Log4Net.Tests/SentryAppenderTests.cs @@ -345,6 +345,28 @@ public void DoAppend_BelowMinimumEventLevel_AddsBreadcrumb() Assert.Equal(expectedBreadcrumbMsg, breadcrumb.Message); } + [Fact] + public void DoAppend_BreadcrumbWithException_ProvidesExceptionInHint() + { + SentryHint hint = null; + _fixture.Scope.Options.SetBeforeBreadcrumb((breadcrumb, h) => + { + hint = h; + return breadcrumb; + }); + var expectedException = new Exception("expected"); + + var sut = _fixture.GetSut(); + sut.Threshold = Level.Debug; + sut.MinimumEventLevel = Level.Error; + + // Level.Warn is below the MinimumEventLevel, so only a breadcrumb is added + sut.DoAppend(new LoggingEvent(null, null, "logger", Level.Warn, "log4net breadcrumb", expectedException)); + + hint.Should().NotBeNull(); + hint.Items[HintTypes.Exception].Should().BeSameAs(expectedException); + } + [Fact] public void DoAppend_NullMinimumEventLevel_AddsEvent() { diff --git a/test/Sentry.NLog.Tests/SentryTargetTests.cs b/test/Sentry.NLog.Tests/SentryTargetTests.cs index 2bb97d6e7a..8b5c9fef76 100644 --- a/test/Sentry.NLog.Tests/SentryTargetTests.cs +++ b/test/Sentry.NLog.Tests/SentryTargetTests.cs @@ -191,6 +191,27 @@ public void Log_WithoutException_LeavesBreadcrumb() _fixture.Scope.Breadcrumbs.Should().NotBeEmpty(); } + [Fact] + public void Log_BreadcrumbWithException_ProvidesExceptionInHint() + { + SentryHint hint = null; + _fixture.Scope.Options.SetBeforeBreadcrumb((breadcrumb, h) => + { + hint = h; + return breadcrumb; + }); + var expectedException = new Exception("expected"); + + _fixture.Options.MinimumEventLevel = LogLevel.Fatal; + var logger = _fixture.GetLogger(); + + // LogLevel.Error is below the MinimumEventLevel, so only a breadcrumb is added + logger.Error(expectedException, DefaultMessage); + + hint.Should().NotBeNull(); + hint.Items[HintTypes.Exception].Should().BeSameAs(expectedException); + } + [Fact] public void Log_WithException_CreatesEventWithException() { diff --git a/test/Sentry.Serilog.Tests/SentrySinkTests.cs b/test/Sentry.Serilog.Tests/SentrySinkTests.cs index 18ce0a4561..e90c405ec3 100644 --- a/test/Sentry.Serilog.Tests/SentrySinkTests.cs +++ b/test/Sentry.Serilog.Tests/SentrySinkTests.cs @@ -76,6 +76,29 @@ public void EmitEvent_WithoutException_LeavesBreadcrumb() _fixture.Scope.Breadcrumbs.Should().NotBeEmpty(); } + [Fact] + public void EmitBreadcrumb_WithException_ProvidesExceptionInHint() + { + SentryHint hint = null; + _fixture.Scope.Options.SetBeforeBreadcrumb((breadcrumb, h) => + { + hint = h; + return breadcrumb; + }); + var expectedException = new Exception("expected message"); + + var sut = _fixture.GetSut(); + + // LogEventLevel.Warning is below the default MinimumEventLevel, so only a breadcrumb is added + var evt = new LogEvent(DateTimeOffset.UtcNow, LogEventLevel.Warning, expectedException, + MessageTemplate.Empty, Enumerable.Empty()); + + sut.Emit(evt); + + hint.Should().NotBeNull(); + hint.Items[HintTypes.Exception].Should().BeSameAs(expectedException); + } + [Fact] public void Emit_SerilogSdk_Name() { diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt index 5f150f965e..56ca748572 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -125,6 +125,7 @@ namespace Sentry public delegate bool HeapDumpTrigger(long usedMemory, long totalMemory); public static class HintTypes { + public const string Exception = "exception"; public const string HttpResponseMessage = "http-response-message"; } public readonly struct HttpStatusCodeRange : System.IEquatable @@ -149,6 +150,7 @@ namespace Sentry public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Breadcrumb breadcrumb, Sentry.SentryHint? hint = null) { } public static void AddBreadcrumb(this Sentry.IHub hub, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } + public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Infrastructure.ISystemClock? clock, string message, string? category, string? type, System.Collections.Generic.IDictionary? data, Sentry.BreadcrumbLevel level, Sentry.SentryHint? hint) { } public static Sentry.SentryId CaptureException(this Sentry.IHub hub, System.Exception ex, System.Action configureScope) { } public static Sentry.SentryId CaptureException(this Sentry.IHub hub, System.Exception ex, bool handled, bool terminal, System.Action configureScope) { } public static Sentry.SentryId CaptureFeedback(this Sentry.IHub hub, Sentry.SentryFeedback feedback, System.Action configureScope, Sentry.SentryHint? hint = null) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt index 5f150f965e..56ca748572 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt @@ -125,6 +125,7 @@ namespace Sentry public delegate bool HeapDumpTrigger(long usedMemory, long totalMemory); public static class HintTypes { + public const string Exception = "exception"; public const string HttpResponseMessage = "http-response-message"; } public readonly struct HttpStatusCodeRange : System.IEquatable @@ -149,6 +150,7 @@ namespace Sentry public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Breadcrumb breadcrumb, Sentry.SentryHint? hint = null) { } public static void AddBreadcrumb(this Sentry.IHub hub, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } + public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Infrastructure.ISystemClock? clock, string message, string? category, string? type, System.Collections.Generic.IDictionary? data, Sentry.BreadcrumbLevel level, Sentry.SentryHint? hint) { } public static Sentry.SentryId CaptureException(this Sentry.IHub hub, System.Exception ex, System.Action configureScope) { } public static Sentry.SentryId CaptureException(this Sentry.IHub hub, System.Exception ex, bool handled, bool terminal, System.Action configureScope) { } public static Sentry.SentryId CaptureFeedback(this Sentry.IHub hub, Sentry.SentryFeedback feedback, System.Action configureScope, Sentry.SentryHint? hint = null) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt index 5f150f965e..56ca748572 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt @@ -125,6 +125,7 @@ namespace Sentry public delegate bool HeapDumpTrigger(long usedMemory, long totalMemory); public static class HintTypes { + public const string Exception = "exception"; public const string HttpResponseMessage = "http-response-message"; } public readonly struct HttpStatusCodeRange : System.IEquatable @@ -149,6 +150,7 @@ namespace Sentry public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Breadcrumb breadcrumb, Sentry.SentryHint? hint = null) { } public static void AddBreadcrumb(this Sentry.IHub hub, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } + public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Infrastructure.ISystemClock? clock, string message, string? category, string? type, System.Collections.Generic.IDictionary? data, Sentry.BreadcrumbLevel level, Sentry.SentryHint? hint) { } public static Sentry.SentryId CaptureException(this Sentry.IHub hub, System.Exception ex, System.Action configureScope) { } public static Sentry.SentryId CaptureException(this Sentry.IHub hub, System.Exception ex, bool handled, bool terminal, System.Action configureScope) { } public static Sentry.SentryId CaptureFeedback(this Sentry.IHub hub, Sentry.SentryFeedback feedback, System.Action configureScope, Sentry.SentryHint? hint = null) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index 61369e66c2..49ade0556e 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -113,6 +113,7 @@ namespace Sentry } public static class HintTypes { + public const string Exception = "exception"; public const string HttpResponseMessage = "http-response-message"; } public readonly struct HttpStatusCodeRange : System.IEquatable @@ -137,6 +138,7 @@ namespace Sentry public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Breadcrumb breadcrumb, Sentry.SentryHint? hint = null) { } public static void AddBreadcrumb(this Sentry.IHub hub, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } + public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Infrastructure.ISystemClock? clock, string message, string? category, string? type, System.Collections.Generic.IDictionary? data, Sentry.BreadcrumbLevel level, Sentry.SentryHint? hint) { } public static Sentry.SentryId CaptureException(this Sentry.IHub hub, System.Exception ex, System.Action configureScope) { } public static Sentry.SentryId CaptureException(this Sentry.IHub hub, System.Exception ex, bool handled, bool terminal, System.Action configureScope) { } public static Sentry.SentryId CaptureFeedback(this Sentry.IHub hub, Sentry.SentryFeedback feedback, System.Action configureScope, Sentry.SentryHint? hint = null) { } diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index d986bd4f95..07e4ded3d5 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -313,6 +313,47 @@ public void CaptureEvent_Exception_LeavesBreadcrumb(bool withScopeCallback) breadcrumb.Category.Should().Be("Exception"); } + [Fact] + public void CaptureEvent_Exception_BreadcrumbHintContainsException() + { + // Arrange + SentryHint hint = null; + _fixture.Options.SetBeforeBreadcrumb((breadcrumb, h) => + { + hint = h; + return breadcrumb; + }); + using var hub = _fixture.GetSut(); + var exception = new Exception("original"); + + // Act + hub.CaptureEvent(new SentryEvent(exception)); + + // Assert + hint.Should().NotBeNull(); + hint.Items[HintTypes.Exception].Should().BeSameAs(exception); + } + + [Fact] + public void CaptureEvent_Exception_BeforeBreadcrumbCanFilterOnExceptionType() + { + // Arrange + _fixture.Options.SetBeforeBreadcrumb((breadcrumb, hint) => + hint.Items.TryGetValue(HintTypes.Exception, out var exception) && exception is InvalidOperationException + ? null + : breadcrumb); + using var hub = _fixture.GetSut(); + var scope = hub.ScopeManager.GetCurrent().Key; + + // Act + hub.CaptureEvent(new SentryEvent(new InvalidOperationException("filtered"))); + hub.CaptureEvent(new SentryEvent(new Exception("kept"))); + + // Assert + scope.Breadcrumbs.Should().ContainSingle(b => b.Category == "Exception") + .Which.Message.Should().Be("kept"); + } + [Fact] public void CaptureEvent_WithMessageAndException_StoresExceptionMessageAsData() { From fbc74418e5f33917bb5b7bd8c1d7bfa09d0c9932 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Tue, 1 Sep 2026 10:20:16 +1200 Subject: [PATCH 2/2] ref: Extract Exception.ToHint() and drop the explanatory comment Addresses review feedback: the `exception is null ? null : new SentryHint(...)` pattern repeated at each logging integration call site is now an internal `ToHint()` extension on SentryExceptionExtensions. Co-Authored-By: Claude Opus 5 --- src/Sentry.Extensions.Logging/SentryLogger.cs | 2 +- src/Sentry.Log4Net/SentryAppender.cs | 4 +--- src/Sentry.NLog/SentryTarget.cs | 2 +- src/Sentry.Serilog/SentrySink.cs | 2 +- src/Sentry/Internal/Hub.cs | 2 -- src/Sentry/SentryExceptionExtensions.cs | 9 +++++++++ 6 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Sentry.Extensions.Logging/SentryLogger.cs b/src/Sentry.Extensions.Logging/SentryLogger.cs index 1c0cdbc4d3..05013d0fef 100644 --- a/src/Sentry.Extensions.Logging/SentryLogger.cs +++ b/src/Sentry.Extensions.Logging/SentryLogger.cs @@ -83,7 +83,7 @@ public void Log( null, data, logLevel.ToBreadcrumbLevel(), - exception is null ? null : new SentryHint(HintTypes.Exception, exception)); + exception.ToHint()); } } diff --git a/src/Sentry.Log4Net/SentryAppender.cs b/src/Sentry.Log4Net/SentryAppender.cs index d2083bd7be..2d9faaaf55 100644 --- a/src/Sentry.Log4Net/SentryAppender.cs +++ b/src/Sentry.Log4Net/SentryAppender.cs @@ -172,8 +172,6 @@ private void AddBreadcrumbFromLoggingEvent(LoggingEvent loggingEvent) .Where(kvp => kvp.Value != null) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value!.ToString() ?? ""); - var exception = loggingEvent.ExceptionObject; - _hub.AddBreadcrumb( clock: null, message, @@ -181,7 +179,7 @@ private void AddBreadcrumbFromLoggingEvent(LoggingEvent loggingEvent) type: null, data, level ?? default, - hint: exception is null ? null : new SentryHint(HintTypes.Exception, exception)); + hint: loggingEvent.ExceptionObject.ToHint()); return; } diff --git a/src/Sentry.NLog/SentryTarget.cs b/src/Sentry.NLog/SentryTarget.cs index c2f075e401..89eef117d3 100644 --- a/src/Sentry.NLog/SentryTarget.cs +++ b/src/Sentry.NLog/SentryTarget.cs @@ -428,7 +428,7 @@ private void CreateBreadcrumb(LogEventInfo logEvent, Exception? exception, bool type: null, data: data, level: logEvent.Level.ToBreadcrumbLevel(), - hint: exception is null ? null : new SentryHint(HintTypes.Exception, exception)); + hint: exception.ToHint()); } private void CreateSentryEvent(LogEventInfo logEvent, Exception? exception, bool shouldIncludeProperties, IHub hub) diff --git a/src/Sentry.Serilog/SentrySink.cs b/src/Sentry.Serilog/SentrySink.cs index 4e1dc3602d..9ace4793d3 100644 --- a/src/Sentry.Serilog/SentrySink.cs +++ b/src/Sentry.Serilog/SentrySink.cs @@ -164,7 +164,7 @@ private void InnerEmit(LogEvent logEvent) type: null, data: data, level: logEvent.Level.ToBreadcrumbLevel(), - hint: exception is null ? null : new SentryHint(HintTypes.Exception, exception)); + hint: exception.ToHint()); } // Read the options from the Hub, rather than the Sink's Serilog-Options, because 'EnableLogs' is declared in the base 'SentryOptions', rather than the derived 'SentrySerilogOptions'. diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index fe69aed850..bfcfa3e834 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -588,8 +588,6 @@ private void AddBreadcrumbForException(SentryEvent evt, Scope scope) }; } - // Provide the original exception in a Hint, so that the BeforeBreadcrumb callback can filter or modify - // the breadcrumb based on the exception itself (e.g. by type) rather than by matching on its message. var hint = new SentryHint(_options); hint.Items[HintTypes.Exception] = exception; diff --git a/src/Sentry/SentryExceptionExtensions.cs b/src/Sentry/SentryExceptionExtensions.cs index da78c8fea3..35d59de122 100644 --- a/src/Sentry/SentryExceptionExtensions.cs +++ b/src/Sentry/SentryExceptionExtensions.cs @@ -1,3 +1,4 @@ +using Sentry; using Sentry.Internal; using Sentry.Protocol; @@ -7,6 +8,14 @@ [EditorBrowsable(EditorBrowsableState.Never)] public static class SentryExceptionExtensions { + /// + /// Creates a carrying the exception that a breadcrumb was created from, or + /// if there is no exception. + /// + internal static SentryHint? ToHint(this Exception? exception) => exception is null + ? null + : new SentryHint(HintTypes.Exception, exception); + /// /// Set a tag that will be added to the event when the exception is captured. ///