From 2fc1ed7633b3741bc3d8a49b65888cdb95ea5300 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 29 Jan 2025 19:42:58 +0100 Subject: [PATCH] Set mechanism `type` to `suppressed` for suppressed exceptions (#4125) * set mechanism type to suppressed for suppressed exceptions * changelog --- CHANGELOG.md | 2 + .../io/sentry/SentryExceptionFactory.java | 12 ++++-- .../io/sentry/SentryExceptionFactoryTest.kt | 39 ++++++++++++++----- 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5d824a9c4..011259b2d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ - Remove `java.lang.ClassNotFoundException` debug logs when searching for OpenTelemetry marker classes ([#4091](https://github.com/getsentry/sentry-java/pull/4091)) - There was up to three of these, one for `io.sentry.opentelemetry.agent.AgentMarker`, `io.sentry.opentelemetry.agent.AgentlessMarker` and `io.sentry.opentelemetry.agent.AgentlessSpringMarker`. - These were not indicators of something being wrong but rather the SDK looking at what is available at runtime to configure itself accordingly. +- Set mechanism `type` to `suppressed` for suppressed exceptions ([#4125](https://github.com/getsentry/sentry-java/pull/4125)) + - This helps to distinguish an exceptions cause from any suppressed exceptions in the Sentry UI ### Dependencies diff --git a/sentry/src/main/java/io/sentry/SentryExceptionFactory.java b/sentry/src/main/java/io/sentry/SentryExceptionFactory.java index a6138aa398..8b2b570946 100644 --- a/sentry/src/main/java/io/sentry/SentryExceptionFactory.java +++ b/sentry/src/main/java/io/sentry/SentryExceptionFactory.java @@ -137,14 +137,15 @@ public List getSentryExceptions(final @NotNull Throwable throwa @NotNull Deque extractExceptionQueue(final @NotNull Throwable throwable) { return extractExceptionQueueInternal( - throwable, new AtomicInteger(-1), new HashSet<>(), new ArrayDeque<>()); + throwable, new AtomicInteger(-1), new HashSet<>(), new ArrayDeque<>(), null); } Deque extractExceptionQueueInternal( final @NotNull Throwable throwable, final @NotNull AtomicInteger exceptionId, final @NotNull HashSet circularityDetector, - final @NotNull Deque exceptions) { + final @NotNull Deque exceptions, + @Nullable String mechanismTypeOverride) { Mechanism exceptionMechanism; Thread thread; @@ -154,6 +155,8 @@ Deque extractExceptionQueueInternal( // Stack the exceptions to send them in the reverse order while (currentThrowable != null && circularityDetector.add(currentThrowable)) { boolean snapshot = false; + final @NotNull String mechanismType = + mechanismTypeOverride == null ? "chained" : mechanismTypeOverride; if (currentThrowable instanceof ExceptionMechanismException) { // this is for ANR I believe ExceptionMechanismException exceptionMechanismThrowable = @@ -177,7 +180,7 @@ Deque extractExceptionQueueInternal( exceptions.addFirst(exception); if (exceptionMechanism.getType() == null) { - exceptionMechanism.setType("chained"); + exceptionMechanism.setType(mechanismType); } if (exceptionId.get() >= 0) { @@ -194,11 +197,12 @@ Deque extractExceptionQueueInternal( // exceptionMechanism.setExceptionGroup(true); for (Throwable suppressedThrowable : suppressed) { extractExceptionQueueInternal( - suppressedThrowable, exceptionId, circularityDetector, exceptions); + suppressedThrowable, exceptionId, circularityDetector, exceptions, "suppressed"); } } currentThrowable = currentThrowable.getCause(); parentId = currentExceptionId; + mechanismTypeOverride = null; } return exceptions; diff --git a/sentry/src/test/java/io/sentry/SentryExceptionFactoryTest.kt b/sentry/src/test/java/io/sentry/SentryExceptionFactoryTest.kt index 69a7721359..089680d40d 100644 --- a/sentry/src/test/java/io/sentry/SentryExceptionFactoryTest.kt +++ b/sentry/src/test/java/io/sentry/SentryExceptionFactoryTest.kt @@ -212,7 +212,7 @@ class SentryExceptionFactoryTest { @Test fun `when exception with mechanism suppressed exceptions, add them and show as group`() { val exception = Exception("message") - val suppressedException = Exception("suppressed") + val suppressedException = Exception("suppressed exception") exception.addSuppressed(suppressedException) val mechanism = Mechanism() @@ -225,19 +225,21 @@ class SentryExceptionFactoryTest { val suppressedInQueue = queue.pop() val mainInQueue = queue.pop() - assertEquals("suppressed", suppressedInQueue.value) + assertEquals("suppressed exception", suppressedInQueue.value) assertEquals(1, suppressedInQueue.mechanism?.exceptionId) assertEquals(0, suppressedInQueue.mechanism?.parentId) + assertEquals("suppressed", suppressedInQueue.mechanism?.type) assertEquals("message", mainInQueue.value) assertEquals(0, mainInQueue.mechanism?.exceptionId) + assertEquals("ANR", mainInQueue.mechanism?.type) // assertEquals(true, mainInQueue.mechanism?.isExceptionGroup) } @Test fun `nested exception that contains suppressed exceptions is marked as group`() { val exception = Exception("inner") - val suppressedException = Exception("suppressed") + val suppressedException = Exception("suppressed exception") exception.addSuppressed(suppressedException) val outerException = Exception("outer", exception) @@ -248,25 +250,28 @@ class SentryExceptionFactoryTest { val mainInQueue = queue.pop() val outerInQueue = queue.pop() - assertEquals("suppressed", suppressedInQueue.value) + assertEquals("suppressed exception", suppressedInQueue.value) assertEquals(2, suppressedInQueue.mechanism?.exceptionId) assertEquals(1, suppressedInQueue.mechanism?.parentId) + assertEquals("suppressed", suppressedInQueue.mechanism?.type) assertEquals("inner", mainInQueue.value) assertEquals(1, mainInQueue.mechanism?.exceptionId) assertEquals(0, mainInQueue.mechanism?.parentId) + assertEquals("chained", mainInQueue.mechanism?.type) // assertEquals(true, mainInQueue.mechanism?.isExceptionGroup) assertEquals("outer", outerInQueue.value) assertEquals(0, outerInQueue.mechanism?.exceptionId) assertNull(outerInQueue.mechanism?.parentId) + assertEquals("chained", outerInQueue.mechanism?.type) // assertNull(outerInQueue.mechanism?.isExceptionGroup) } @Test fun `nested exception within Mechanism that contains suppressed exceptions is marked as group`() { val exception = Exception("inner") - val suppressedException = Exception("suppressed") + val suppressedException = Exception("suppressed exception") exception.addSuppressed(suppressedException) val mechanism = Mechanism() @@ -281,18 +286,21 @@ class SentryExceptionFactoryTest { val mainInQueue = queue.pop() val outerInQueue = queue.pop() - assertEquals("suppressed", suppressedInQueue.value) + assertEquals("suppressed exception", suppressedInQueue.value) assertEquals(2, suppressedInQueue.mechanism?.exceptionId) assertEquals(1, suppressedInQueue.mechanism?.parentId) + assertEquals("suppressed", suppressedInQueue.mechanism?.type) assertEquals("inner", mainInQueue.value) assertEquals(1, mainInQueue.mechanism?.exceptionId) assertEquals(0, mainInQueue.mechanism?.parentId) + assertEquals("chained", mainInQueue.mechanism?.type) // assertEquals(true, mainInQueue.mechanism?.isExceptionGroup) assertEquals("outer", outerInQueue.value) assertEquals(0, outerInQueue.mechanism?.exceptionId) assertNull(outerInQueue.mechanism?.parentId) + assertEquals("ANR", outerInQueue.mechanism?.type) // assertNull(outerInQueue.mechanism?.isExceptionGroup) } @@ -303,7 +311,7 @@ class SentryExceptionFactoryTest { innerMostException.addSuppressed(innerMostSuppressed) val innerException = Exception("inner", innerMostException) - val innerSuppressed = Exception("suppressed") + val innerSuppressed = Exception("suppressed exception") innerException.addSuppressed(innerSuppressed) val outerException = Exception("outer", innerException) @@ -319,27 +327,32 @@ class SentryExceptionFactoryTest { assertEquals("innermostSuppressed", innerMostSuppressedInQueue.value) assertEquals(4, innerMostSuppressedInQueue.mechanism?.exceptionId) assertEquals(3, innerMostSuppressedInQueue.mechanism?.parentId) + assertEquals("suppressed", innerMostSuppressedInQueue.mechanism?.type) assertNull(innerMostSuppressedInQueue.mechanism?.isExceptionGroup) assertEquals("innermost", innerMostExceptionInQueue.value) assertEquals(3, innerMostExceptionInQueue.mechanism?.exceptionId) assertEquals(1, innerMostExceptionInQueue.mechanism?.parentId) + assertEquals("chained", innerMostExceptionInQueue.mechanism?.type) // assertEquals(true, innerMostExceptionInQueue.mechanism?.isExceptionGroup) - assertEquals("suppressed", innerSuppressedInQueue.value) + assertEquals("suppressed exception", innerSuppressedInQueue.value) assertEquals(2, innerSuppressedInQueue.mechanism?.exceptionId) assertEquals(1, innerSuppressedInQueue.mechanism?.parentId) + assertEquals("suppressed", innerSuppressedInQueue.mechanism?.type) assertNull(innerSuppressedInQueue.mechanism?.isExceptionGroup) assertEquals("inner", innerExceptionInQueue.value) assertEquals(1, innerExceptionInQueue.mechanism?.exceptionId) assertEquals(0, innerExceptionInQueue.mechanism?.parentId) + assertEquals("chained", innerExceptionInQueue.mechanism?.type) // assertEquals(true, innerExceptionInQueue.mechanism?.isExceptionGroup) assertEquals("outer", outerInQueue.value) assertEquals(0, outerInQueue.mechanism?.exceptionId) assertNull(outerInQueue.mechanism?.parentId) assertNull(outerInQueue.mechanism?.isExceptionGroup) + assertEquals("chained", outerInQueue.mechanism?.type) } @Test @@ -351,7 +364,7 @@ class SentryExceptionFactoryTest { innerMostException.addSuppressed(innerMostSuppressed) val innerException = Exception("inner", innerMostException) - val innerSuppressed = Exception("suppressed") + val innerSuppressed = Exception("suppressed exception") innerException.addSuppressed(innerSuppressed) val outerException = Exception("outer", innerException) @@ -369,31 +382,37 @@ class SentryExceptionFactoryTest { assertEquals(5, innerMostSuppressedNestedExceptionInQueue.mechanism?.exceptionId) assertEquals(4, innerMostSuppressedNestedExceptionInQueue.mechanism?.parentId) assertNull(innerMostSuppressedNestedExceptionInQueue.mechanism?.isExceptionGroup) + assertEquals("chained", innerMostSuppressedNestedExceptionInQueue.mechanism?.type) assertEquals("innermostSuppressed", innerMostSuppressedInQueue.value) assertEquals(4, innerMostSuppressedInQueue.mechanism?.exceptionId) assertEquals(3, innerMostSuppressedInQueue.mechanism?.parentId) assertNull(innerMostSuppressedInQueue.mechanism?.isExceptionGroup) + assertEquals("suppressed", innerMostSuppressedInQueue.mechanism?.type) assertEquals("innermost", innerMostExceptionInQueue.value) assertEquals(3, innerMostExceptionInQueue.mechanism?.exceptionId) assertEquals(1, innerMostExceptionInQueue.mechanism?.parentId) + assertEquals("chained", innerMostExceptionInQueue.mechanism?.type) // assertEquals(true, innerMostExceptionInQueue.mechanism?.isExceptionGroup) - assertEquals("suppressed", innerSuppressedInQueue.value) + assertEquals("suppressed exception", innerSuppressedInQueue.value) assertEquals(2, innerSuppressedInQueue.mechanism?.exceptionId) assertEquals(1, innerSuppressedInQueue.mechanism?.parentId) + assertEquals("suppressed", innerSuppressedInQueue.mechanism?.type) assertNull(innerSuppressedInQueue.mechanism?.isExceptionGroup) assertEquals("inner", innerExceptionInQueue.value) assertEquals(1, innerExceptionInQueue.mechanism?.exceptionId) assertEquals(0, innerExceptionInQueue.mechanism?.parentId) + assertEquals("chained", innerExceptionInQueue.mechanism?.type) // assertEquals(true, innerExceptionInQueue.mechanism?.isExceptionGroup) assertEquals("outer", outerInQueue.value) assertEquals(0, outerInQueue.mechanism?.exceptionId) assertNull(outerInQueue.mechanism?.parentId) assertNull(outerInQueue.mechanism?.isExceptionGroup) + assertEquals("chained", outerInQueue.mechanism?.type) } internal class InnerClassThrowable constructor(cause: Throwable? = null) : Throwable(cause)