Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set mechanism type to suppressed for suppressed exceptions #4125

Merged
merged 4 commits into from
Jan 29, 2025
Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 8 additions & 4 deletions sentry/src/main/java/io/sentry/SentryExceptionFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,14 +137,15 @@ public List<SentryException> getSentryExceptions(final @NotNull Throwable throwa
@NotNull
Deque<SentryException> 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<SentryException> extractExceptionQueueInternal(
final @NotNull Throwable throwable,
final @NotNull AtomicInteger exceptionId,
final @NotNull HashSet<Throwable> circularityDetector,
final @NotNull Deque<SentryException> exceptions) {
final @NotNull Deque<SentryException> exceptions,
@Nullable String mechanismTypeOverride) {
Mechanism exceptionMechanism;
Thread thread;

Expand All @@ -154,6 +155,8 @@ Deque<SentryException> 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 =
Expand All @@ -177,7 +180,7 @@ Deque<SentryException> extractExceptionQueueInternal(
exceptions.addFirst(exception);

if (exceptionMechanism.getType() == null) {
exceptionMechanism.setType("chained");
exceptionMechanism.setType(mechanismType);
}

if (exceptionId.get() >= 0) {
Expand All @@ -194,11 +197,12 @@ Deque<SentryException> 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;
Expand Down
39 changes: 29 additions & 10 deletions sentry/src/test/java/io/sentry/SentryExceptionFactoryTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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)
Expand All @@ -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()
Expand All @@ -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)
}

Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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)
Expand Down
Loading