diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9d103949ba..03006075e7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,10 @@
See https://develop.sentry.dev/sdk/telemetry/traces/distributed-tracing/#w3c-trace-context-header for more details.
+### Features
+
+- 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 SDK avoids redundant scope sync after transaction finish ([#4623](https://github.com/getsentry/sentry-dotnet/pull/4623))
diff --git a/src/Sentry/Integrations/UnobservedTaskExceptionIntegration.cs b/src/Sentry/Integrations/UnobservedTaskExceptionIntegration.cs
index a403186904..3e3b658bc2 100644
--- a/src/Sentry/Integrations/UnobservedTaskExceptionIntegration.cs
+++ b/src/Sentry/Integrations/UnobservedTaskExceptionIntegration.cs
@@ -47,7 +47,8 @@ internal void Handle(object? sender, UnobservedTaskExceptionEventArgs e)
MechanismKey,
"This exception was thrown from a task that was unobserved, such as from an async void method, or " +
"a Task.Run that was not awaited. This exception was unhandled, but likely did not crash the application.",
- handled: false);
+ handled: false,
+ terminal: false);
// Call the internal implementation, so that we still capture even if the hub has been disabled.
_hub.CaptureExceptionInternal(ex);
diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs
index d4990363b9..c27e0eb95e 100644
--- a/src/Sentry/Internal/Hub.cs
+++ b/src/Sentry/Internal/Hub.cs
@@ -585,7 +585,8 @@ private SentryId CaptureEvent(SentryEvent evt, SentryHint? hint, Scope scope)
scope.LastEventId = id;
scope.SessionUpdate = null;
- if (evt.HasTerminalException() && scope.Transaction is { } transaction)
+ if (evt.GetExceptionType() is SentryEvent.ExceptionType.UnhandledTerminal
+ && scope.Transaction is { } transaction)
{
// Event contains a terminal exception -> finish any current transaction as aborted
// Do this *after* the event was captured, so that the event is still linked to the transaction.
diff --git a/src/Sentry/Internal/MainExceptionProcessor.cs b/src/Sentry/Internal/MainExceptionProcessor.cs
index b80ab1ea94..3a5d524df4 100644
--- a/src/Sentry/Internal/MainExceptionProcessor.cs
+++ b/src/Sentry/Internal/MainExceptionProcessor.cs
@@ -199,6 +199,12 @@ private static Mechanism GetMechanism(Exception exception, int id, int? parentId
exception.Data.Remove(Mechanism.DescriptionKey);
}
+ if (exception.Data[Mechanism.TerminalKey] is bool terminal)
+ {
+ mechanism.Terminal = terminal;
+ exception.Data.Remove(Mechanism.TerminalKey);
+ }
+
// Add HResult to mechanism data before adding exception data, so that it can be overridden.
mechanism.Data["HResult"] = $"0x{exception.HResult:X8}";
diff --git a/src/Sentry/Platforms/Android/LogCatAttachmentEventProcessor.cs b/src/Sentry/Platforms/Android/LogCatAttachmentEventProcessor.cs
index 6c2e1fdf5f..89879f813d 100644
--- a/src/Sentry/Platforms/Android/LogCatAttachmentEventProcessor.cs
+++ b/src/Sentry/Platforms/Android/LogCatAttachmentEventProcessor.cs
@@ -45,7 +45,9 @@ public SentryEvent Process(SentryEvent @event, SentryHint hint)
try
{
- if (_logCatIntegrationType != LogCatIntegrationType.All && !@event.HasException())
+ var exceptionType = @event.GetExceptionType();
+
+ if (_logCatIntegrationType != LogCatIntegrationType.All && exceptionType == SentryEvent.ExceptionType.None)
{
return @event;
}
@@ -53,7 +55,7 @@ public SentryEvent Process(SentryEvent @event, SentryHint hint)
// Only send logcat logs if the event is unhandled if the integration is set to Unhandled
if (_logCatIntegrationType == LogCatIntegrationType.Unhandled)
{
- if (!@event.HasTerminalException())
+ if (exceptionType != SentryEvent.ExceptionType.UnhandledTerminal && exceptionType != SentryEvent.ExceptionType.UnhandledNonTerminal)
{
return @event;
}
diff --git a/src/Sentry/Protocol/Mechanism.cs b/src/Sentry/Protocol/Mechanism.cs
index ff0eb014fd..3b37ab5b64 100644
--- a/src/Sentry/Protocol/Mechanism.cs
+++ b/src/Sentry/Protocol/Mechanism.cs
@@ -29,6 +29,14 @@ public sealed class Mechanism : ISentryJsonSerializable
///
public static readonly string DescriptionKey = "Sentry:Description";
+ ///
+ /// Key found inside of Exception.Data describing whether the exception is considered terminal.
+ ///
+ ///
+ /// This is an SDK-internal flag used for session tracking and is not sent to Sentry servers.
+ ///
+ public static readonly string TerminalKey = "Sentry:Terminal";
+
internal Dictionary? InternalData { get; private set; }
internal Dictionary? InternalMeta { get; private set; }
@@ -76,6 +84,15 @@ public string Type
///
public bool? Handled { get; set; }
+ ///
+ /// Optional flag indicating whether the exception is terminal (will crash the application).
+ /// When false, indicates a non-terminal unhandled exception (e.g., unobserved task exception).
+ ///
+ ///
+ /// This is an SDK-internal flag used for session tracking and is not serialized to Sentry servers.
+ ///
+ public bool? Terminal { get; internal set; }
+
///
/// Optional flag indicating whether the exception is synthetic.
///
@@ -133,6 +150,7 @@ public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
writer.WriteStringIfNotWhiteSpace("source", Source);
writer.WriteStringIfNotWhiteSpace("help_link", HelpLink);
writer.WriteBooleanIfNotNull("handled", Handled);
+ // Note: Terminal is NOT serialized - it's SDK-internal only
writer.WriteBooleanIfTrue("synthetic", Synthetic);
writer.WriteBooleanIfTrue("is_exception_group", IsExceptionGroup);
writer.WriteNumberIfNotNull("exception_id", ExceptionId);
diff --git a/src/Sentry/SentryClient.cs b/src/Sentry/SentryClient.cs
index d42fe8c44f..99efe7d9fa 100644
--- a/src/Sentry/SentryClient.cs
+++ b/src/Sentry/SentryClient.cs
@@ -347,18 +347,23 @@ private SentryId DoSendEvent(SentryEvent @event, SentryHint? hint, Scope? scope)
return SentryId.Empty; // Dropped by BeforeSend callback
}
- var hasTerminalException = processedEvent.HasTerminalException();
- if (hasTerminalException)
- {
- // Event contains a terminal exception -> end session as crashed
- _options.LogDebug("Ending session as Crashed, due to unhandled exception.");
- scope.SessionUpdate = _sessionManager.EndSession(SessionEndStatus.Crashed);
- }
- else if (processedEvent.HasException())
- {
- // Event contains a non-terminal exception -> report error
- // (this might return null if the session has already reported errors before)
- scope.SessionUpdate = _sessionManager.ReportError();
+ var exceptionType = processedEvent.GetExceptionType();
+ switch (exceptionType)
+ {
+ case SentryEvent.ExceptionType.UnhandledNonTerminal:
+ _options.LogDebug("Ending session as 'Unhandled', due to non-terminal unhandled exception.");
+ scope.SessionUpdate = _sessionManager.EndSession(SessionEndStatus.Unhandled);
+ break;
+
+ case SentryEvent.ExceptionType.UnhandledTerminal:
+ _options.LogDebug("Ending session as 'Crashed', due to unhandled exception.");
+ scope.SessionUpdate = _sessionManager.EndSession(SessionEndStatus.Crashed);
+ break;
+
+ case SentryEvent.ExceptionType.Handled:
+ _options.LogDebug("Updating session by reporting an error.");
+ scope.SessionUpdate = _sessionManager.ReportError();
+ break;
}
if (_options.SampleRate != null)
diff --git a/src/Sentry/SentryEvent.cs b/src/Sentry/SentryEvent.cs
index 81abbe4ddd..c26937cbfc 100644
--- a/src/Sentry/SentryEvent.cs
+++ b/src/Sentry/SentryEvent.cs
@@ -178,21 +178,64 @@ public IReadOnlyList Fingerprint
///
public IReadOnlyDictionary Tags => _tags ??= new Dictionary();
- internal bool HasException() => Exception is not null || SentryExceptions?.Any() == true;
+ internal enum ExceptionType
+ {
+ None,
+ Handled,
+ UnhandledTerminal,
+ UnhandledNonTerminal
+ }
- internal bool HasTerminalException()
+ internal ExceptionType GetExceptionType()
{
- // The exception is considered terminal if it is marked unhandled,
- // UNLESS it comes from the UnobservedTaskExceptionIntegration
+ if (!HasException())
+ {
+ return ExceptionType.None;
+ }
+
+ if (HasUnhandledNonTerminalException())
+ {
+ return ExceptionType.UnhandledNonTerminal;
+ }
+
+ if (HasUnhandledException())
+ {
+ return ExceptionType.UnhandledTerminal;
+ }
+
+ return ExceptionType.Handled;
+ }
+ private bool HasException() => Exception is not null || SentryExceptions?.Any() == true;
+
+ private bool HasUnhandledException()
+ {
if (Exception?.Data[Mechanism.HandledKey] is false)
{
- return Exception.Data[Mechanism.MechanismKey] as string != UnobservedTaskExceptionIntegration.MechanismKey;
+ return true;
+ }
+
+ return SentryExceptions?.Any(e => e.Mechanism is { Handled: false }) ?? false;
+ }
+
+ private bool HasUnhandledNonTerminalException()
+ {
+ // Generally, an unhandled exception is considered terminal.
+ // Exception: If it is an unhandled exception but the terminal flag is explicitly set to false.
+ // I.e. captured through the UnobservedTaskExceptionIntegration, or the exception capture integrations in the Unity SDK
+
+ if (Exception?.Data[Mechanism.HandledKey] is false)
+ {
+ if (Exception.Data[Mechanism.TerminalKey] is false)
+ {
+ return true;
+ }
+
+ return false;
}
return SentryExceptions?.Any(e =>
- e.Mechanism is { Handled: false } mechanism &&
- mechanism.Type != UnobservedTaskExceptionIntegration.MechanismKey
+ e.Mechanism is { Handled: false, Terminal: false }
) ?? false;
}
diff --git a/src/Sentry/SentryExceptionExtensions.cs b/src/Sentry/SentryExceptionExtensions.cs
index 4ecac3239b..822ad8c856 100644
--- a/src/Sentry/SentryExceptionExtensions.cs
+++ b/src/Sentry/SentryExceptionExtensions.cs
@@ -32,8 +32,9 @@ public static void AddSentryContext(this Exception ex, string name, IReadOnlyDic
/// A required short string that identifies the mechanism.
/// An optional human-readable description of the mechanism.
/// An optional flag indicating whether the exception was handled by the mechanism.
+ /// An optional flag indicating whether the exception is considered terminal.
public static void SetSentryMechanism(this Exception ex, string type, string? description = null,
- bool? handled = null)
+ bool? handled = null, bool? terminal = null)
{
ex.Data[Mechanism.MechanismKey] = type;
@@ -54,5 +55,14 @@ public static void SetSentryMechanism(this Exception ex, string type, string? de
{
ex.Data[Mechanism.HandledKey] = handled;
}
+
+ if (terminal == null)
+ {
+ ex.Data.Remove(Mechanism.TerminalKey);
+ }
+ else
+ {
+ ex.Data[Mechanism.TerminalKey] = terminal;
+ }
}
}
diff --git a/src/Sentry/SessionEndStatus.cs b/src/Sentry/SessionEndStatus.cs
index fdde497533..5c483c6b5e 100644
--- a/src/Sentry/SessionEndStatus.cs
+++ b/src/Sentry/SessionEndStatus.cs
@@ -13,10 +13,15 @@ public enum SessionEndStatus
///
/// Session ended with an unhandled exception.
///
+ Unhandled,
+
+ ///
+ /// Session ended with a terminal unhandled exception.
+ ///
Crashed,
///
/// Session ended abnormally (e.g. device lost power).
///
- Abnormal
+ Abnormal,
}
diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt
index c2c782305e..e8d1b7df21 100644
--- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt
+++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt
@@ -1065,8 +1065,9 @@ namespace Sentry
public enum SessionEndStatus
{
Exited = 0,
- Crashed = 1,
- Abnormal = 2,
+ Unhandled = 1,
+ Crashed = 2,
+ Abnormal = 3,
}
public class SessionUpdate : Sentry.ISentryJsonSerializable, Sentry.ISentrySession
{
@@ -1786,6 +1787,7 @@ namespace Sentry.Protocol
public static readonly string DescriptionKey;
public static readonly string HandledKey;
public static readonly string MechanismKey;
+ public static readonly string TerminalKey;
public Mechanism() { }
public System.Collections.Generic.IDictionary Data { get; }
public string? Description { get; set; }
@@ -1797,6 +1799,7 @@ namespace Sentry.Protocol
public int? ParentId { get; set; }
public string? Source { get; set; }
public bool Synthetic { get; set; }
+ public bool? Terminal { get; }
public string Type { get; set; }
public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { }
public static Sentry.Protocol.Mechanism FromJson(System.Text.Json.JsonElement json) { }
@@ -1925,5 +1928,5 @@ public static class SentryExceptionExtensions
{
public static void AddSentryContext(this System.Exception ex, string name, System.Collections.Generic.IReadOnlyDictionary data) { }
public static void AddSentryTag(this System.Exception ex, string name, string value) { }
- public static void SetSentryMechanism(this System.Exception ex, string type, string? description = null, bool? handled = default) { }
+ public static void SetSentryMechanism(this System.Exception ex, string type, string? description = null, bool? handled = default, bool? terminal = default) { }
}
\ No newline at end of file
diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt
index c2c782305e..e8d1b7df21 100644
--- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt
+++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt
@@ -1065,8 +1065,9 @@ namespace Sentry
public enum SessionEndStatus
{
Exited = 0,
- Crashed = 1,
- Abnormal = 2,
+ Unhandled = 1,
+ Crashed = 2,
+ Abnormal = 3,
}
public class SessionUpdate : Sentry.ISentryJsonSerializable, Sentry.ISentrySession
{
@@ -1786,6 +1787,7 @@ namespace Sentry.Protocol
public static readonly string DescriptionKey;
public static readonly string HandledKey;
public static readonly string MechanismKey;
+ public static readonly string TerminalKey;
public Mechanism() { }
public System.Collections.Generic.IDictionary Data { get; }
public string? Description { get; set; }
@@ -1797,6 +1799,7 @@ namespace Sentry.Protocol
public int? ParentId { get; set; }
public string? Source { get; set; }
public bool Synthetic { get; set; }
+ public bool? Terminal { get; }
public string Type { get; set; }
public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { }
public static Sentry.Protocol.Mechanism FromJson(System.Text.Json.JsonElement json) { }
@@ -1925,5 +1928,5 @@ public static class SentryExceptionExtensions
{
public static void AddSentryContext(this System.Exception ex, string name, System.Collections.Generic.IReadOnlyDictionary data) { }
public static void AddSentryTag(this System.Exception ex, string name, string value) { }
- public static void SetSentryMechanism(this System.Exception ex, string type, string? description = null, bool? handled = default) { }
+ public static void SetSentryMechanism(this System.Exception ex, string type, string? description = null, bool? handled = default, bool? terminal = default) { }
}
\ No newline at end of file
diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt
index c2c782305e..e8d1b7df21 100644
--- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt
+++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt
@@ -1065,8 +1065,9 @@ namespace Sentry
public enum SessionEndStatus
{
Exited = 0,
- Crashed = 1,
- Abnormal = 2,
+ Unhandled = 1,
+ Crashed = 2,
+ Abnormal = 3,
}
public class SessionUpdate : Sentry.ISentryJsonSerializable, Sentry.ISentrySession
{
@@ -1786,6 +1787,7 @@ namespace Sentry.Protocol
public static readonly string DescriptionKey;
public static readonly string HandledKey;
public static readonly string MechanismKey;
+ public static readonly string TerminalKey;
public Mechanism() { }
public System.Collections.Generic.IDictionary Data { get; }
public string? Description { get; set; }
@@ -1797,6 +1799,7 @@ namespace Sentry.Protocol
public int? ParentId { get; set; }
public string? Source { get; set; }
public bool Synthetic { get; set; }
+ public bool? Terminal { get; }
public string Type { get; set; }
public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { }
public static Sentry.Protocol.Mechanism FromJson(System.Text.Json.JsonElement json) { }
@@ -1925,5 +1928,5 @@ public static class SentryExceptionExtensions
{
public static void AddSentryContext(this System.Exception ex, string name, System.Collections.Generic.IReadOnlyDictionary data) { }
public static void AddSentryTag(this System.Exception ex, string name, string value) { }
- public static void SetSentryMechanism(this System.Exception ex, string type, string? description = null, bool? handled = default) { }
+ public static void SetSentryMechanism(this System.Exception ex, string type, string? description = null, bool? handled = default, bool? terminal = default) { }
}
\ No newline at end of file
diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt
index 9cd54f7fa0..538d76be9f 100644
--- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt
+++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt
@@ -1041,8 +1041,9 @@ namespace Sentry
public enum SessionEndStatus
{
Exited = 0,
- Crashed = 1,
- Abnormal = 2,
+ Unhandled = 1,
+ Crashed = 2,
+ Abnormal = 3,
}
public class SessionUpdate : Sentry.ISentryJsonSerializable, Sentry.ISentrySession
{
@@ -1757,6 +1758,7 @@ namespace Sentry.Protocol
public static readonly string DescriptionKey;
public static readonly string HandledKey;
public static readonly string MechanismKey;
+ public static readonly string TerminalKey;
public Mechanism() { }
public System.Collections.Generic.IDictionary Data { get; }
public string? Description { get; set; }
@@ -1768,6 +1770,7 @@ namespace Sentry.Protocol
public int? ParentId { get; set; }
public string? Source { get; set; }
public bool Synthetic { get; set; }
+ public bool? Terminal { get; }
public string Type { get; set; }
public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { }
public static Sentry.Protocol.Mechanism FromJson(System.Text.Json.JsonElement json) { }
@@ -1896,5 +1899,5 @@ public static class SentryExceptionExtensions
{
public static void AddSentryContext(this System.Exception ex, string name, System.Collections.Generic.IReadOnlyDictionary data) { }
public static void AddSentryTag(this System.Exception ex, string name, string value) { }
- public static void SetSentryMechanism(this System.Exception ex, string type, string? description = null, bool? handled = default) { }
+ public static void SetSentryMechanism(this System.Exception ex, string type, string? description = null, bool? handled = default, bool? terminal = default) { }
}
\ No newline at end of file
diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs
index c04fc23a94..195d99fbb4 100644
--- a/test/Sentry.Tests/HubTests.cs
+++ b/test/Sentry.Tests/HubTests.cs
@@ -524,6 +524,84 @@ public void CaptureEvent_Client_GetsHint()
Arg.Any(), Arg.Is(h => h == hint));
}
+ [Fact]
+ public void CaptureEvent_TerminalUnhandledException_AbortsActiveTransaction()
+ {
+ // Arrange
+ _fixture.Options.TracesSampleRate = 1.0;
+ var hub = _fixture.GetSut();
+
+ var transaction = hub.StartTransaction("test", "operation");
+ hub.ConfigureScope(scope => scope.Transaction = transaction);
+
+ var exception = new Exception("test");
+ exception.SetSentryMechanism("test", handled: false, terminal: true);
+
+ // Act
+ hub.CaptureEvent(new SentryEvent(exception));
+
+ // Assert
+ transaction.Status.Should().Be(SpanStatus.Aborted);
+ transaction.IsFinished.Should().BeTrue();
+ }
+
+ [Fact]
+ public void CaptureEvent_NonTerminalUnhandledException_DoesNotAbortActiveTransaction()
+ {
+ // Arrange
+ _fixture.Options.TracesSampleRate = 1.0;
+ var hub = _fixture.GetSut();
+
+ var transaction = hub.StartTransaction("test", "operation");
+ hub.ConfigureScope(scope => scope.Transaction = transaction);
+
+ var exception = new Exception("test");
+ exception.SetSentryMechanism("TestException", handled: false, terminal: false);
+
+ // Act
+ hub.CaptureEvent(new SentryEvent(exception));
+
+ // Assert
+ transaction.IsFinished.Should().BeFalse();
+ }
+
+ [Fact]
+ public void CaptureEvent_HandledException_DoesNotAbortActiveTransaction()
+ {
+ // Arrange
+ _fixture.Options.TracesSampleRate = 1.0;
+ var hub = _fixture.GetSut();
+
+ var transaction = hub.StartTransaction("test", "operation");
+ hub.ConfigureScope(scope => scope.Transaction = transaction);
+
+ var exception = new Exception("test");
+ exception.SetSentryMechanism("test", handled: true);
+
+ // Act
+ hub.CaptureEvent(new SentryEvent(exception));
+
+ // Assert
+ transaction.IsFinished.Should().BeFalse();
+ }
+
+ [Fact]
+ public void CaptureEvent_EventWithoutException_DoesNotAbortActiveTransaction()
+ {
+ // Arrange
+ _fixture.Options.TracesSampleRate = 1.0;
+ var hub = _fixture.GetSut();
+
+ var transaction = hub.StartTransaction("test", "operation");
+ hub.ConfigureScope(scope => scope.Transaction = transaction);
+
+ // Act
+ hub.CaptureEvent(new SentryEvent { Message = "test message" });
+
+ // Assert
+ transaction.IsFinished.Should().BeFalse();
+ }
+
[Fact]
public void AppDomainUnhandledExceptionIntegration_ActiveSession_UnhandledExceptionSessionEndedAsCrashed()
{
diff --git a/test/Sentry.Tests/Internals/MainExceptionProcessorTests.cs b/test/Sentry.Tests/Internals/MainExceptionProcessorTests.cs
index f5dedc0b4e..14151c21a8 100644
--- a/test/Sentry.Tests/Internals/MainExceptionProcessorTests.cs
+++ b/test/Sentry.Tests/Internals/MainExceptionProcessorTests.cs
@@ -103,6 +103,39 @@ public void Process_ExceptionWith_HandledTrue_WhenCaught()
Assert.Single(evt.SentryExceptions, p => p.Mechanism?.Handled == true);
}
+ [Fact]
+ public void Process_ExceptionWith_TerminalTrue_StoresInMechanismData()
+ {
+ var sut = _fixture.GetSut();
+ var evt = new SentryEvent();
+ var exp = new Exception();
+
+ exp.SetSentryMechanism("TestException", terminal: true);
+
+ sut.Process(exp, evt);
+
+ Assert.NotNull(evt.SentryExceptions);
+ var sentryException = evt.SentryExceptions.Single();
+ Assert.NotNull(sentryException.Mechanism?.Terminal);
+ Assert.True(sentryException.Mechanism?.Terminal);
+ }
+
+ [Fact]
+ public void Process_ExceptionWith_TerminalFalse_StoresInMechanismData()
+ {
+ var sut = _fixture.GetSut();
+ var evt = new SentryEvent();
+ var exp = new Exception();
+
+ exp.SetSentryMechanism("TestException", terminal: false);
+
+ sut.Process(exp, evt);
+
+ Assert.NotNull(evt.SentryExceptions);
+ var sentryException = evt.SentryExceptions.Single();
+ Assert.False(sentryException.Mechanism?.Terminal);
+ }
+
[Fact]
public void CreateSentryException_DataHasObjectAsKey_ItemIgnored()
{
diff --git a/test/Sentry.Tests/Protocol/Exceptions/MechanismTests.cs b/test/Sentry.Tests/Protocol/Exceptions/MechanismTests.cs
index e5ae10a885..1f310a859d 100644
--- a/test/Sentry.Tests/Protocol/Exceptions/MechanismTests.cs
+++ b/test/Sentry.Tests/Protocol/Exceptions/MechanismTests.cs
@@ -74,4 +74,35 @@ public static IEnumerable