loading...
Touchlab

Kotlin Native iOS Crash Reporting

kpgalligan profile image Kevin Galligan ・3 min read

Crash reporting tools are critical to the success of any mobile product. Knowing when things fail, and how, is information you need to make sure things are running smoothly.

The Problem

Kotlin code running on iOS has an issue specific to the design differences between the JVM and iOS. The JVM and Kotlin rely on exceptions, while iOS really does not.

When a crash happens on Android in the JVM, an exception will bubble up until something catches it. If it makes its way to the default exception handler, the app generally "crashes", with maybe a report to a crash reporting library.

On iOS, without exception propagation, the system simply "crashes". The crash reporting tool will capture the state of the threads at that moment.

For Kotlin, this introduces a problem. To stay consistent with Kotlin on the JVM, exceptions propagate in the same way on iOS. At the border between Swift/Objc and Kotlin, uncaught exceptions will trigger a crash. Because of how iOS crashes work, that looks like this:

Alt Text

They all end with konan::abort(), which internally force-kills the app. For crash reporting, that isn't super useful.

You can add the stack trace in the crash report log as a string, but you may lose detail, and in any case, the errors won't be grouped by the stack trace.

Kotlin 1.3.60 introduces crash data being written to the iOS internal crash log and better symbolication support. You can get the crash data from the local device, if you have access to it, or Apple's developer site, but neither is ideal in many cases.

Introducing CrashKiOS

CrashKiOS (Crash reporting for Kotlin iOS) is a simple library that let's you send symbolicated crash reports to crash reporting systems like Crashlytics and Bugsnag. It leverages the crash data improvements in Kotlin 1.3.60, and uses the existing crash reporting API's.

The end result is crash reports that look more like this:

Alt Text

You can find setup and config details in the README.

How it Works

Conceptually, the way it works is we add a default exception handler to the Kotlin code, which will be triggered if an exception winds its way to the interface between Kotlin and Swift/Objc.

We define an open class that gets called from that default exception handler. That class is extended in Swift (or Objc), and the report is sent to the crash reporting system.

There are two basic options. Get the raw Throwable, or get the parts of the crash necessary to be sent to the crash reporting tool.

fun crash(t: Throwable)

fun crashParts(addresses: List<Long>, exceptionType: String, message: String)

In our current implementations, reports are all "handled errors", as there is no published way to send a custom fatal crash, but we'll be looking into ways to do that in the future.

For both Crashlytics and Bugsnag, rather than provide the stack trace as a string, we supply the code addresses, which are translated back to the source code lines by way of dsym files. For Crashlytics, that looks like the following.

class CrashlyticsCrashHandler: CrashkiosCrashHandler {
    override func crashParts(
        addresses: [KotlinLong],
        exceptionType: String,
        message: String) {
        let clsStackTrace = addresses.map {
            CLSStackFrame(address: UInt(truncating: $0))
        }

        Crashlytics.sharedInstance().recordCustomExceptionName(
            exceptionType,
            reason: message,
            frameArray: clsStackTrace
        )
    }
}

clsStackTrace is a stack trace built from the code addresses. Both Crashlytics and Bugsnag can take that info and produce a stack trace. Presumably other crash reporting tools would work the same way.

Just FYI

The last couple stack lines are generally boilerplate exception initialization, which we strip out (the screenshot above was from an earlier version where those lines were left in). That logic may need more tweaking over time.

Currently, Kotlin debug builds give line numbers while release builds only provide function names. Hopefully we'll have better config for that after some more research.

We'll be making improvements over time as we learn more about how iOS deals with crash reports and how to better integrate with vaious crash reporting systems. If you'd like to add a different system, or have some ideas about how to improve support, please reach out (@kpgalligan on twitter, or find me in the Kotlin Slack).

Touchlab KMP Stuff!

Touchlab is publishing a Kotlin Multiplatform evaluation kit for orgs interested in the tech, called the KaMP Kit. You can get access by going to our signup form. Also, we're hiring mobile developers, in particular people experienced in or at least interested in multiplatform tech. See our careers page for more info.

Discussion

pic
Editor guide