Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/rickclephas/NSExceptionKt
A Kotlin Multiplatform Library to improve crash reports on Apple platforms
https://github.com/rickclephas/NSExceptionKt
kmm kmp kotlin kotlin-multiplatform kotlin-multiplatform-mobile kotlin-native
Last synced: 3 months ago
JSON representation
A Kotlin Multiplatform Library to improve crash reports on Apple platforms
- Host: GitHub
- URL: https://github.com/rickclephas/NSExceptionKt
- Owner: rickclephas
- License: mit
- Created: 2022-07-17T15:14:52.000Z (over 2 years ago)
- Default Branch: master
- Last Pushed: 2024-08-01T10:40:21.000Z (3 months ago)
- Last Synced: 2024-08-01T11:01:50.970Z (3 months ago)
- Topics: kmm, kmp, kotlin, kotlin-multiplatform, kotlin-multiplatform-mobile, kotlin-native
- Language: Kotlin
- Homepage:
- Size: 336 KB
- Stars: 150
- Watchers: 4
- Forks: 12
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
Awesome Lists containing this project
- kmp-awesome - NSExceptionKt - Better crash reports on Apple platforms (Libraries / 🔍 Analytics)
- mobile-awesome - NSExceptionKt - A Kotlin Multiplatform Library to improve crash reports on Apple platforms. (iOS / Android samples)
README
# NSExceptionKt
A Kotlin Multiplatform Library to improve crash reports on Apple platforms.
## Installation
Checkout the implementation specific `README`s for usage and installation details:
* [Crashlytics](NSExceptionKtCrashlytics/README.md)
* [Bugsnag](NSExceptionKtBugsnag/README.md)## Why this library?
If you have been developing applications for Apple platforms with Kotlin Native,
then you have likely encountered crashes like the following:```
Function doesn't have or inherit @Throws annotation and thus exception isn't propagated from Kotlin to Objective-C/Swift as NSError.
It is considered unexpected and unhandled instead. Program will be terminated.
Uncaught Kotlin exception: com.rickclephas.myapplication.Platform.TestException: Test exception 2
at 0 iosApp 0x1020e88e7 kfun:kotlin.Exception#(kotlin.String?;kotlin.Throwable?){} + 119
at 1 iosApp 0x1020e8b1b kfun:kotlin.RuntimeException#(kotlin.String?;kotlin.Throwable?){} + 119
at 2 iosApp 0x1020e8eff kfun:kotlin.IllegalArgumentException#(kotlin.String?;kotlin.Throwable?){} + 119
at 3 iosApp 0x1020d3273 kfun:com.rickclephas.myapplication.Platform.TestException#(kotlin.String?;kotlin.Throwable?){} + 119
... and 38 more stack frames
Caused by: kotlin.IllegalArgumentException: Test exception 1
at 0 iosApp 0x1020ee457 kfun:kotlin.Throwable#(kotlin.String?){} + 95
at 1 iosApp 0x1020e884b kfun:kotlin.Exception#(kotlin.String?){} + 91
at 2 iosApp 0x1020e8a7f kfun:kotlin.RuntimeException#(kotlin.String?){} + 91
at 3 iosApp 0x1020e8e63 kfun:kotlin.IllegalArgumentException#(kotlin.String?){} + 91
... and 39 more stack frames
```So far so good. You fix the crash and all is well. But what happens when such a crash occurs in production?!
Well, just like the above message said, the program will be [terminated](https://github.com/JetBrains/kotlin/blob/02901aeb106146274df7cff5686f8e376652fe2a/kotlin-native/runtime/src/main/cpp/Exceptions.cpp#L92).
And that is probably what you want anyway, so you open your favorite crash reporting tool and...:```
Exception Type: EXC_CRASH (SIGABRT)
Crashed Thread: 0Thread 0 Crashed:
0 libsystem_kernel.dylib 0x3980b2e60 __pthread_kill
1 libsystem_pthread.dylib 0x39815b3bc pthread_kill
2 libsystem_c.dylib 0x30018f3f4 abort
3 iosApp 0x20230fc2c konan::abort
4 iosApp 0x20231dca0 (anonymous namespace)::terminateWithUnhandledException::lambda::operator()
5 iosApp 0x20231db64 (anonymous namespace)::lambda::operator()
6 iosApp 0x20231d924 (anonymous namespace)::terminateWithUnhandledException
7 iosApp 0x20231d8ac (anonymous namespace)::processUnhandledException
8 iosApp 0x20231f73c kotlin::ProcessUnhandledException
9 iosApp 0x202322cb4 Kotlin_ObjCExport_trapOnUndeclaredException
```The good news: you know your app crashed 🥸. The bad news: there is no useful stacktrace 🥹.
Fortunately we can [log](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native/set-unhandled-exception-hook.html)
such unhandled exceptions in Kotlin before the app is terminated!## The challenges
While we can use any Objective-C crash reporting SDK we like,
the concept of error handling is very different between Kotlin and ObjC/Swift.> On the Kotlin side all exceptions are unchecked, but on the Swift side they are all checked.
> That's why we need that [`@Throws` annotation](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-throws/),
> which tells Kotlin what exception types a function is expected to throw.When you try to log your unhandled `Exception`s, you'll soon realise that most SDKs only expose APIs to log `NSError`s.
Unfortunately you can't just map an `Exception` to a `NSError`, for one you would lose the stacktrace.In the rare case that you can log your exception (e.g. with Crashlytics via the [ExceptionModel] class),
the logged exception will be marked as non-fatal and the real crash is logged as well (resulting in two reports).[ExceptionModel]: https://firebase.google.com/docs/reference/swift/firebasecrashlytics/api/reference/Classes/ExceptionModel
So we basically need to:
* log a fatal error from Kotlin
* attach a stacktrace to it
* preferably attach the caused by exceptions (with their stacktrace)
* prevent the Kotlin termination from being logged## Acknowledgments
[Kermit](https://github.com/touchlab/Kermit) by [Touchlab](https://touchlab.co/)
and specifically [@kpgalligan](https://twitter.com/kpgalligan) have been a great inspiration for this project.I would also like to thank Firebase, Bugsnag and Sentry for publishing the sources of their SDKs.
Without those sources projects like these wouldn't exist.