From 9f3f12963c96ce97b63c6ad436a0cc49c892a1b5 Mon Sep 17 00:00:00 2001 From: lcian Date: Thu, 6 Mar 2025 12:20:29 +0100 Subject: [PATCH 1/6] Add CoroutineExceptionHandler --- buildSrc/src/main/java/Config.kt | 2 ++ .../api/sentry-kotlin-extensions.api | 7 +++++ .../kotlin/SentryCoroutineExceptionHandler.kt | 26 +++++++++++++++++++ .../sentry-samples-android/build.gradle.kts | 3 +++ .../sentry/samples/android/CoroutinesUtil.kt | 16 ++++++++++++ .../sentry/samples/android/MainActivity.java | 5 ++++ .../src/main/res/layout/activity_main.xml | 6 +++++ .../src/main/res/values/strings.xml | 1 + 8 files changed, 66 insertions(+) create mode 100644 sentry-kotlin-extensions/src/main/java/io/sentry/kotlin/SentryCoroutineExceptionHandler.kt create mode 100644 sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/CoroutinesUtil.kt diff --git a/buildSrc/src/main/java/Config.kt b/buildSrc/src/main/java/Config.kt index 03b6849a6ba..30f3a5614a9 100644 --- a/buildSrc/src/main/java/Config.kt +++ b/buildSrc/src/main/java/Config.kt @@ -119,6 +119,8 @@ object Config { val coroutinesCore = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1" + val coroutinesAndroid = "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1" + val fragment = "androidx.fragment:fragment-ktx:1.3.5" val reactorCore = "io.projectreactor:reactor-core:3.5.3" diff --git a/sentry-kotlin-extensions/api/sentry-kotlin-extensions.api b/sentry-kotlin-extensions/api/sentry-kotlin-extensions.api index 0555383c1b1..e11ec192499 100644 --- a/sentry-kotlin-extensions/api/sentry-kotlin-extensions.api +++ b/sentry-kotlin-extensions/api/sentry-kotlin-extensions.api @@ -10,3 +10,10 @@ public final class io/sentry/kotlin/SentryContext : kotlin/coroutines/AbstractCo public synthetic fun updateThreadContext (Lkotlin/coroutines/CoroutineContext;)Ljava/lang/Object; } +public class io/sentry/kotlin/SentryCoroutineExceptionHandler : kotlin/coroutines/AbstractCoroutineContextElement, kotlinx/coroutines/CoroutineExceptionHandler { + public fun ()V + public fun (Lio/sentry/IScopes;)V + public synthetic fun (Lio/sentry/IScopes;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun handleException (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Throwable;)V +} + diff --git a/sentry-kotlin-extensions/src/main/java/io/sentry/kotlin/SentryCoroutineExceptionHandler.kt b/sentry-kotlin-extensions/src/main/java/io/sentry/kotlin/SentryCoroutineExceptionHandler.kt new file mode 100644 index 00000000000..aca2d39f31e --- /dev/null +++ b/sentry-kotlin-extensions/src/main/java/io/sentry/kotlin/SentryCoroutineExceptionHandler.kt @@ -0,0 +1,26 @@ +package io.sentry.kotlin + +import io.sentry.IScopes +import io.sentry.ScopesAdapter +import io.sentry.SentryEvent +import io.sentry.exception.ExceptionMechanismException +import io.sentry.protocol.Mechanism +import kotlinx.coroutines.CoroutineExceptionHandler +import org.jetbrains.annotations.ApiStatus +import kotlin.coroutines.AbstractCoroutineContextElement +import kotlin.coroutines.CoroutineContext + +@ApiStatus.Experimental +public open class SentryCoroutineExceptionHandler(private val scopes: IScopes = ScopesAdapter.getInstance()) : + AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler { + + override fun handleException(context: CoroutineContext, exception: Throwable) { + val mechanism = Mechanism().apply { + type = "CoroutineExceptionHandler" + } + // the current thread is not necessarily the one that threw the exception + val error = ExceptionMechanismException(mechanism, exception, Thread.currentThread()) + val event = SentryEvent(error) + scopes.captureEvent(event) + } +} diff --git a/sentry-samples/sentry-samples-android/build.gradle.kts b/sentry-samples/sentry-samples-android/build.gradle.kts index 04772af8aa0..8fae7e6393c 100644 --- a/sentry-samples/sentry-samples-android/build.gradle.kts +++ b/sentry-samples/sentry-samples-android/build.gradle.kts @@ -151,5 +151,8 @@ dependencies { implementation(Config.Libs.composeCoil) implementation(Config.Libs.sentryNativeNdk) + implementation(projects.sentryKotlinExtensions) + implementation(Config.Libs.coroutinesAndroid) + debugImplementation(Config.Libs.leakCanary) } diff --git a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/CoroutinesUtil.kt b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/CoroutinesUtil.kt new file mode 100644 index 00000000000..6e43574819d --- /dev/null +++ b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/CoroutinesUtil.kt @@ -0,0 +1,16 @@ +package io.sentry.samples.android + +import io.sentry.kotlin.SentryContext +import io.sentry.kotlin.SentryCoroutineExceptionHandler +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import java.lang.RuntimeException + +object CoroutinesUtil { + + fun throwInCoroutine() { + GlobalScope.launch(SentryContext() + SentryCoroutineExceptionHandler()) { + throw RuntimeException("Exception in coroutine") + } + } +} diff --git a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java index da52c72a68d..a8d72d66921 100644 --- a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java +++ b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.java @@ -254,6 +254,11 @@ public void run() { binding.openFrameDataForSpans.setOnClickListener( view -> startActivity(new Intent(this, FrameDataForSpansActivity.class))); + binding.throwInCoroutine.setOnClickListener( + view -> { + CoroutinesUtil.INSTANCE.throwInCoroutine(); + }); + setContentView(binding.getRoot()); } diff --git a/sentry-samples/sentry-samples-android/src/main/res/layout/activity_main.xml b/sentry-samples/sentry-samples-android/src/main/res/layout/activity_main.xml index 6fb8d028637..56b95fa1097 100644 --- a/sentry-samples/sentry-samples-android/src/main/res/layout/activity_main.xml +++ b/sentry-samples/sentry-samples-android/src/main/res/layout/activity_main.xml @@ -142,6 +142,12 @@ android:layout_height="wrap_content" android:text="@string/open_frame_data_for_spans"/> +