Skip to content

Commit 99336dd

Browse files
authored
Improve stacktrace of manually captured exceptions on iOS (#493)
* Update * Update CHANGELOG.md to reflect upcoming release changes, including improvements to iOS crash reports and stacktrace handling. Mark the release as "Unreleased" and add warnings regarding potential impacts on issue grouping for iOS events.
1 parent 6ec35e3 commit 99336dd

File tree

3 files changed

+63
-34
lines changed

3 files changed

+63
-34
lines changed

CHANGELOG.md

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
# Changelog
22

3-
## 0.22.0
3+
## Unreleased
4+
5+
⚠️ This release will affect issue grouping for iOS events as Sentry now captures correct stacktraces for manually captured and crashed iOS events.
46

57
### Features
68

79
- Improve iOS crash reports by adding scope data ([#491](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/491))
8-
- ⚠️ This change will most likely affect issue grouping as Sentry now properly symbolicates Kotlin iOS crashes
10+
- Improve stacktrace of manually captured exceptions on iOS ([#493](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/493))
11+
12+
## 0.22.0
13+
14+
### Features
915

1016
### Dependencies
1117

@@ -169,13 +175,15 @@ Potentially breaking: this release bumps the used Kotlin version to `2.1.21`.
169175
### Features
170176

171177
- Add experimental session replay options to common code ([#275](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/275))
178+
172179
```kotlin
173180
Sentry.init { options ->
174181
// Adjust these values for production
175182
options.sessionReplay.onErrorSampleRate = 1.0
176183
options.sessionReplay.sessionSampleRate = 1.0
177184
}
178185
```
186+
179187
- Add `Sentry.isEnabled()` API to common code ([#273](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/273))
180188
- Add `enableWatchdogTerminationTracking` in common options ([#281](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/281))
181189
- Add `diagnosticLevel` in common options ([#287](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/287))
@@ -213,12 +221,13 @@ Sentry.init { options ->
213221
- Enables auto installing of the required Sentry Cocoa SDK with Cocoapods (if Cocoapods plugin is enabled)
214222
- Configures linking for SPM (needed if you want to compile a dynamic framework with `isStatic = false`)
215223
- Configure via the `sentryKmp` configuration block in your build file
224+
216225
```kotlin
217226
// Example configuration in build.gradle.kts
218227
sentryKmp {
219228
// Disable auto installing the KMP SDK to commonMain
220229
autoInstall.commonMain.enabled = false
221-
}
230+
}
222231
```
223232

224233
### Dependencies
@@ -236,7 +245,7 @@ sentryKmp {
236245
### Features
237246

238247
- New Sentry KMP Gradle plugin ([#230](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/230))
239-
- Install via `plugins { id("io.sentry.kotlin.multiplatform.gradle") version "{version}" }`
248+
- Install via `plugins { id("io.sentry.kotlin.multiplatform.gradle") version "{version}" }`
240249
- Enables auto installing of the KMP SDK to commonMain (if all targets are supported)
241250
- Enables auto installing of the required Sentry Cocoa SDK with Cocoapods (if Cocoapods plugin is enabled)
242251
- Configures linking for SPM (needed if you want to compile a dynamic framework)
@@ -266,6 +275,7 @@ sentryKmp {
266275
- This allows you to initialize the SDK with platform-specific options that may not be available in the common code of the KMP SDK yet.
267276

268277
Usage:
278+
269279
```kotlin
270280
// build.gradle.kts
271281
kotlin {
@@ -284,7 +294,7 @@ fun init() {
284294
expect fun platformOptionsConfiguration(): PlatformOptionsConfiguration
285295

286296
// iOS
287-
actual fun createPlatformOptions(): PlatformOptionsConfiguration = {
297+
actual fun createPlatformOptions(): PlatformOptionsConfiguration = {
288298
dsn = "your_dsn"
289299
release = "1.0.0"
290300
// ...
@@ -366,8 +376,8 @@ pod("Sentry") {
366376
- [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#710)
367377
- [diff](https://github.com/getsentry/sentry-java/compare/6.33.1...7.1.0)
368378
- Bump Cocoa SDK from v8.4.0 to v8.17.1 ([#158](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/163))
369-
- [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8172)
370-
- [diff](https://github.com/getsentry/sentry-cocoa/compare/8.4.0...8.17.2)
379+
- [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8172)
380+
- [diff](https://github.com/getsentry/sentry-cocoa/compare/8.4.0...8.17.2)
371381
- Bump Kotlin version from v1.8.0 to v1.9.21 ([#146](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/146)
372382

373383
## 0.3.0
@@ -405,7 +415,7 @@ pod("Sentry") {
405415

406416
## 0.1.1
407417

408-
### Fixes
418+
### Fixes
409419

410420
- fix: beforeSend dropping events if not set in options ([#79](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/79))
411421

@@ -442,15 +452,14 @@ pod("Sentry") {
442452

443453
### Features
444454

445-
- JVM, Android, iOS, macOS, watchOS, tvOS integration
446-
- Sentry init and close
447-
- Capture Message
448-
- Capture Exception with proper stack traces
449-
- Custom unhandled exception handler on Cocoa to properly catch crashes and the stacktrace
450-
- Scope configuration globally and locally
451-
- User Feedback
452-
- Attachments to Scope
453-
- Screenshots option for Android and iOS
454-
- Add beforeBreadcrumb hook
455-
- Kotlin Multiplatform Sample project
456-
455+
- JVM, Android, iOS, macOS, watchOS, tvOS integration
456+
- Sentry init and close
457+
- Capture Message
458+
- Capture Exception with proper stack traces
459+
- Custom unhandled exception handler on Cocoa to properly catch crashes and the stacktrace
460+
- Scope configuration globally and locally
461+
- User Feedback
462+
- Attachments to Scope
463+
- Screenshots option for Android and iOS
464+
- Add beforeBreadcrumb hook
465+
- Kotlin Multiplatform Sample project

sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.apple.kt

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package io.sentry.kotlin.multiplatform
22

33
import Internal.Sentry.PrivateSentrySDKOnly
4+
import Internal.Sentry.kSentryLevelError
45
import cocoapods.Sentry.SentrySDK
56
import io.sentry.kotlin.multiplatform.extensions.toCocoaBreadcrumb
67
import io.sentry.kotlin.multiplatform.extensions.toCocoaUser
78
import io.sentry.kotlin.multiplatform.extensions.toCocoaUserFeedback
8-
import io.sentry.kotlin.multiplatform.nsexception.asNSException
9+
import io.sentry.kotlin.multiplatform.nsexception.asSentryEvent
910
import io.sentry.kotlin.multiplatform.nsexception.dropKotlinCrashEvent
1011
import io.sentry.kotlin.multiplatform.protocol.Breadcrumb
1112
import io.sentry.kotlin.multiplatform.protocol.SentryId
@@ -75,15 +76,22 @@ internal actual class SentryBridge actual constructor(private val sentryInstance
7576
}
7677

7778
actual fun captureException(throwable: Throwable): SentryId {
78-
val cocoaSentryId = SentrySDK.captureException(throwable.asNSException(true))
79+
val event = throwable.asSentryEvent(
80+
level = kSentryLevelError,
81+
isHandled = true,
82+
markThreadAsCrashed = false
83+
)
84+
val cocoaSentryId = SentrySDK.captureEvent(event)
7985
return SentryId(cocoaSentryId.toString())
8086
}
8187

8288
actual fun captureException(throwable: Throwable, scopeCallback: ScopeCallback): SentryId {
83-
val cocoaSentryId = SentrySDK.captureException(
84-
throwable.asNSException(true),
85-
configureScopeCallback(scopeCallback)
89+
val event = throwable.asSentryEvent(
90+
level = kSentryLevelError,
91+
isHandled = true,
92+
markThreadAsCrashed = false
8693
)
94+
val cocoaSentryId = SentrySDK.captureEvent(event, configureScopeCallback(scopeCallback))
8795
return SentryId(cocoaSentryId.toString())
8896
}
8997

sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/nsexception/SentryUnhandledExceptions.kt

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package io.sentry.kotlin.multiplatform.nsexception
1616

1717
import Internal.Sentry.NSExceptionKt_SentryCrashStackCursorFromNSException
1818
import Internal.Sentry.kSentryLevelFatal
19+
import io.sentry.kotlin.multiplatform.CocoaSentryLevel
1920
import kotlinx.cinterop.invoke
2021
import platform.Foundation.NSException
2122
import platform.Foundation.NSNumber
@@ -96,19 +97,29 @@ private fun Throwable.asSentryEnvelope(): CocoapodsSentryEnvelope {
9697

9798
/**
9899
* Converts `this` [Throwable] to a [cocoapods.Sentry.SentryEvent].
100+
*
101+
* @param level The Sentry level (e.g., kSentryLevelFatal for crashes, kSentryLevelError for handled)
102+
* @param isHandled Whether this is a handled exception (false for crashes, true for captured exceptions)
103+
* @param markThreadAsCrashed Whether to mark the current thread as crashed (true for crashes, false for handled)
99104
*/
100105
@Suppress("UnnecessaryOptInAnnotation")
101-
private fun Throwable.asSentryEvent(): CocoapodsSentryEvent =
102-
CocoapodsSentryEvent(kSentryLevelFatal).apply {
106+
internal fun Throwable.asSentryEvent(
107+
level: CocoaSentryLevel = kSentryLevelFatal,
108+
isHandled: Boolean = false,
109+
markThreadAsCrashed: Boolean = true
110+
): CocoapodsSentryEvent =
111+
CocoapodsSentryEvent(level).apply {
103112
@Suppress("UNCHECKED_CAST")
104113
val threads =
105114
threadInspector?.getCurrentThreadsWithStackTrace() as List<CocoapodsSentryThread>?
106115
this.threads = threads
107116
val currentThread = threads?.firstOrNull { it.current?.boolValue ?: false }?.apply {
108-
setCrashed(NSNumber(true))
109-
// Crashed threads shouldn't have a stacktrace, the thread_id should be set on the exception instead
110-
// https://develop.sentry.dev/sdk/event-payloads/threads/
111-
stacktrace = null
117+
if (markThreadAsCrashed) {
118+
setCrashed(NSNumber(true))
119+
// Crashed threads shouldn't have a stacktrace, the thread_id should be set on the exception instead
120+
// https://develop.sentry.dev/sdk/event-payloads/threads/
121+
stacktrace = null
122+
}
112123
}
113124
debugMeta = threads?.let {
114125
InternalSentryDependencyContainer.sharedInstance().debugImageProvider.getDebugImagesForThreads(
@@ -117,18 +128,19 @@ private fun Throwable.asSentryEvent(): CocoapodsSentryEvent =
117128
}
118129
exceptions = this@asSentryEvent
119130
.let { throwable -> throwable.causes.asReversed() + throwable }
120-
.map { it.asNSException().asSentryException(currentThread?.threadId) }
131+
.map { it.asNSException().asSentryException(currentThread?.threadId, isHandled) }
121132
}
122133

123134
/**
124135
* Converts `this` [NSException] to a [io.sentry.kotlin.multiplatform.protocol.SentryException].
125136
*/
126137
private fun NSException.asSentryException(
127-
threadId: NSNumber?
138+
threadId: NSNumber?,
139+
isHandled: Boolean = false
128140
): CocoapodsSentryException = CocoapodsSentryException(reason ?: "", name ?: "Throwable").apply {
129141
this.threadId = threadId
130142
mechanism = CocoapodsSentryMechanism("generic").apply {
131-
setHandled(NSNumber(false))
143+
setHandled(NSNumber(isHandled))
132144
}
133145
stacktrace = threadInspector?.stacktraceBuilder?.let { stacktraceBuilder ->
134146
val cursor = NSExceptionKt_SentryCrashStackCursorFromNSException(this@asSentryException)

0 commit comments

Comments
 (0)