diff --git a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.apple.kt b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.apple.kt index 933ddae0..4eb6737c 100644 --- a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.apple.kt +++ b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.apple.kt @@ -1,11 +1,12 @@ package io.sentry.kotlin.multiplatform import Internal.Sentry.PrivateSentrySDKOnly +import Internal.Sentry.kSentryLevelError import cocoapods.Sentry.SentrySDK import io.sentry.kotlin.multiplatform.extensions.toCocoaBreadcrumb import io.sentry.kotlin.multiplatform.extensions.toCocoaUser import io.sentry.kotlin.multiplatform.extensions.toCocoaUserFeedback -import io.sentry.kotlin.multiplatform.nsexception.asNSException +import io.sentry.kotlin.multiplatform.nsexception.asSentryEvent import io.sentry.kotlin.multiplatform.nsexception.dropKotlinCrashEvent import io.sentry.kotlin.multiplatform.protocol.Breadcrumb import io.sentry.kotlin.multiplatform.protocol.SentryId @@ -75,15 +76,22 @@ internal actual class SentryBridge actual constructor(private val sentryInstance } actual fun captureException(throwable: Throwable): SentryId { - val cocoaSentryId = SentrySDK.captureException(throwable.asNSException(true)) + val event = throwable.asSentryEvent( + level = kSentryLevelError, + isHandled = true, + markThreadAsCrashed = false + ) + val cocoaSentryId = SentrySDK.captureEvent(event) return SentryId(cocoaSentryId.toString()) } actual fun captureException(throwable: Throwable, scopeCallback: ScopeCallback): SentryId { - val cocoaSentryId = SentrySDK.captureException( - throwable.asNSException(true), - configureScopeCallback(scopeCallback) + val event = throwable.asSentryEvent( + level = kSentryLevelError, + isHandled = true, + markThreadAsCrashed = false ) + val cocoaSentryId = SentrySDK.captureEvent(event, configureScopeCallback(scopeCallback)) return SentryId(cocoaSentryId.toString()) } diff --git a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/nsexception/SentryUnhandledExceptions.kt b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/nsexception/SentryUnhandledExceptions.kt index 1039b2bc..040d2213 100644 --- a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/nsexception/SentryUnhandledExceptions.kt +++ b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/nsexception/SentryUnhandledExceptions.kt @@ -16,6 +16,7 @@ package io.sentry.kotlin.multiplatform.nsexception import Internal.Sentry.NSExceptionKt_SentryCrashStackCursorFromNSException import Internal.Sentry.kSentryLevelFatal +import io.sentry.kotlin.multiplatform.CocoaSentryLevel import kotlinx.cinterop.invoke import platform.Foundation.NSException import platform.Foundation.NSNumber @@ -96,19 +97,29 @@ private fun Throwable.asSentryEnvelope(): CocoapodsSentryEnvelope { /** * Converts `this` [Throwable] to a [cocoapods.Sentry.SentryEvent]. + * + * @param level The Sentry level (e.g., kSentryLevelFatal for crashes, kSentryLevelError for handled) + * @param isHandled Whether this is a handled exception (false for crashes, true for captured exceptions) + * @param markThreadAsCrashed Whether to mark the current thread as crashed (true for crashes, false for handled) */ @Suppress("UnnecessaryOptInAnnotation") -private fun Throwable.asSentryEvent(): CocoapodsSentryEvent = - CocoapodsSentryEvent(kSentryLevelFatal).apply { +internal fun Throwable.asSentryEvent( + level: CocoaSentryLevel = kSentryLevelFatal, + isHandled: Boolean = false, + markThreadAsCrashed: Boolean = true +): CocoapodsSentryEvent = + CocoapodsSentryEvent(level).apply { @Suppress("UNCHECKED_CAST") val threads = threadInspector?.getCurrentThreadsWithStackTrace() as List? this.threads = threads val currentThread = threads?.firstOrNull { it.current?.boolValue ?: false }?.apply { - setCrashed(NSNumber(true)) - // Crashed threads shouldn't have a stacktrace, the thread_id should be set on the exception instead - // https://develop.sentry.dev/sdk/event-payloads/threads/ - stacktrace = null + if (markThreadAsCrashed) { + setCrashed(NSNumber(true)) + // Crashed threads shouldn't have a stacktrace, the thread_id should be set on the exception instead + // https://develop.sentry.dev/sdk/event-payloads/threads/ + stacktrace = null + } } debugMeta = threads?.let { InternalSentryDependencyContainer.sharedInstance().debugImageProvider.getDebugImagesForThreads( @@ -117,18 +128,19 @@ private fun Throwable.asSentryEvent(): CocoapodsSentryEvent = } exceptions = this@asSentryEvent .let { throwable -> throwable.causes.asReversed() + throwable } - .map { it.asNSException().asSentryException(currentThread?.threadId) } + .map { it.asNSException().asSentryException(currentThread?.threadId, isHandled) } } /** * Converts `this` [NSException] to a [io.sentry.kotlin.multiplatform.protocol.SentryException]. */ private fun NSException.asSentryException( - threadId: NSNumber? + threadId: NSNumber?, + isHandled: Boolean = false ): CocoapodsSentryException = CocoapodsSentryException(reason ?: "", name ?: "Throwable").apply { this.threadId = threadId mechanism = CocoapodsSentryMechanism("generic").apply { - setHandled(NSNumber(false)) + setHandled(NSNumber(isHandled)) } stacktrace = threadInspector?.stacktraceBuilder?.let { stacktraceBuilder -> val cursor = NSExceptionKt_SentryCrashStackCursorFromNSException(this@asSentryException)