Skip to content
Draft
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
12 changes: 1 addition & 11 deletions src/Sentry/SentryEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ internal ExceptionType GetExceptionType()
return ExceptionType.UnhandledNonTerminal;
}

if (HasUnhandledException())
if (this.IsFromUnhandledException())
{
return ExceptionType.UnhandledTerminal;
}
Expand All @@ -208,16 +208,6 @@ internal ExceptionType GetExceptionType()

private bool HasException() => Exception is not null || SentryExceptions?.Any() == true;

private bool HasUnhandledException()
{
if (Exception?.Data[Mechanism.HandledKey] is false)
{
return true;
}

return SentryExceptions?.Any(e => e.Mechanism is { Handled: false }) ?? false;
}

private bool HasUnhandledNonTerminalException()
{
// Generally, an unhandled exception is considered terminal.
Expand Down
110 changes: 110 additions & 0 deletions src/Sentry/SentryEventExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using Sentry.Protocol;

namespace Sentry;

/// <summary>
/// Extension methods for <see cref="SentryEvent"/>.
/// </summary>
public static class SentryEventExtensions
{
/// <summary>
/// Determines whether this event was created from an unhandled exception.
/// </summary>
/// <param name="event">The Sentry event.</param>
/// <returns>
/// <c>true</c> if the event was created from an unhandled exception; otherwise, <c>false</c>.
/// </returns>
/// <remarks>
/// An unhandled exception is one that was not caught by application code and was instead
/// captured by the Sentry SDK through hooks like UnhandledExceptionHandler, ASP.NET Core
/// middleware, or other integration points. By default, Sentry marks exceptions as handled
/// unless explicitly captured through one of these unhandled exception hooks.
///
/// This is useful for filtering events in callbacks like BeforeSend, where you may want to
/// treat unhandled exceptions differently from handled ones (e.g., always send unhandled
/// exceptions even if they match a filter that would normally exclude them).
/// </remarks>
/// <example>
/// <code>
/// options.SetBeforeSend((@event, hint) =>
/// {
/// // Always send unhandled exceptions, even if they're network timeouts
/// if (@event.IsFromUnhandledException())
/// {
/// return @event;
/// }
///
/// // Filter out handled network timeout exceptions
/// if (@event.Exception is HttpRequestException)
/// {
/// return null;
/// }
///
/// return @event;
/// });
/// </code>
/// </example>
public static bool IsFromUnhandledException(this SentryEvent @event)
{
// Check if the original exception was marked as unhandled
if (@event.Exception?.Data[Mechanism.HandledKey] is false)
{
return true;
}

// Check if any of the Sentry exceptions have an unhandled mechanism
return @event.SentryExceptions?.Any(e => e.Mechanism is { Handled: false }) ?? false;
}
Comment thread
cursor[bot] marked this conversation as resolved.

/// <summary>
/// Determines whether this event was created from a terminal exception.
/// </summary>
/// <param name="event">The Sentry event.</param>
/// <returns>
/// <c>true</c> if the event was created from a terminal exception; otherwise, <c>false</c>.
/// </returns>
/// <remarks>
/// A terminal exception is an unhandled exception that caused the application to crash or
/// terminate. This excludes unhandled exceptions that were explicitly marked as non-terminal,
/// such as those captured by UnobservedTaskException handlers or certain Unity SDK integrations.
///
/// In most cases, an unhandled exception is terminal. However, some integrations may capture
/// unhandled exceptions that don't actually crash the app (e.g., unobserved task exceptions)
/// and mark them with Terminal = false.
/// </remarks>
/// <example>
/// <code>
/// options.SetBeforeSend((@event, hint) =>
/// {
/// // Only send terminal exceptions for certain exception types
/// if (@event.Exception is NetworkException &amp;&amp; !@event.IsFromTerminalException())
/// {
/// return null; // Don't send non-terminal network exceptions
/// }
///
/// return @event;
/// });
/// </code>
/// </example>
public static bool IsFromTerminalException(this SentryEvent @event)
{
// Check if the original exception was unhandled and not explicitly marked as non-terminal
if (@event.Exception?.Data[Mechanism.HandledKey] is false)
{
// If it's unhandled but explicitly marked as non-terminal, return false
if (@event.Exception.Data[Mechanism.TerminalKey] is false)
{
return false;
}
// Otherwise, unhandled exceptions are terminal by default
return true;
}

// Check if any Sentry exceptions are unhandled and terminal
// (handled: false and terminal: not explicitly false)
return @event.SentryExceptions?.Any(e =>
e.Mechanism is { Handled: false } &&
e.Mechanism.Terminal != false
) ?? false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,11 @@ namespace Sentry
public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { }
public static Sentry.SentryEvent FromJson(System.Text.Json.JsonElement json) { }
}
public static class SentryEventExtensions
{
public static bool IsFromTerminalException(this Sentry.SentryEvent @event) { }
public static bool IsFromUnhandledException(this Sentry.SentryEvent @event) { }
}
public sealed class SentryFeedback : Sentry.ISentryJsonSerializable
{
public SentryFeedback(string message, string? contactEmail = null, string? name = null, string? replayId = null, string? url = null, Sentry.SentryId? associatedEventId = default) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,11 @@ namespace Sentry
public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { }
public static Sentry.SentryEvent FromJson(System.Text.Json.JsonElement json) { }
}
public static class SentryEventExtensions
{
public static bool IsFromTerminalException(this Sentry.SentryEvent @event) { }
public static bool IsFromUnhandledException(this Sentry.SentryEvent @event) { }
}
public sealed class SentryFeedback : Sentry.ISentryJsonSerializable
{
public SentryFeedback(string message, string? contactEmail = null, string? name = null, string? replayId = null, string? url = null, Sentry.SentryId? associatedEventId = default) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,11 @@ namespace Sentry
public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { }
public static Sentry.SentryEvent FromJson(System.Text.Json.JsonElement json) { }
}
public static class SentryEventExtensions
{
public static bool IsFromTerminalException(this Sentry.SentryEvent @event) { }
public static bool IsFromUnhandledException(this Sentry.SentryEvent @event) { }
}
public sealed class SentryFeedback : Sentry.ISentryJsonSerializable
{
public SentryFeedback(string message, string? contactEmail = null, string? name = null, string? replayId = null, string? url = null, Sentry.SentryId? associatedEventId = default) { }
Expand Down
5 changes: 5 additions & 0 deletions test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,11 @@ namespace Sentry
public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { }
public static Sentry.SentryEvent FromJson(System.Text.Json.JsonElement json) { }
}
public static class SentryEventExtensions
{
public static bool IsFromTerminalException(this Sentry.SentryEvent @event) { }
public static bool IsFromUnhandledException(this Sentry.SentryEvent @event) { }
}
public sealed class SentryFeedback : Sentry.ISentryJsonSerializable
{
public SentryFeedback(string message, string? contactEmail = null, string? name = null, string? replayId = null, string? url = null, Sentry.SentryId? associatedEventId = default) { }
Expand Down
Loading
Loading