An effective error stack in multiplatform

The well known and loved stack trace was just a spoil of the JVM, it does not exist on JS (idk about native), so it makes sense that it’s missing in common code.

Indeed when trying to println(e) an error in multiplatform it just prints out the message and not where it happened! Also printStackTrace() is JVM only.
I was wondering if there was a clever and concise way to create an error stack.

My first thought went to a hierarchical re-throw of errors where upper Throwable has the inner as cause and make their toString() pretty:

data class MyCoolError(override val cause: Throwable) : Throwable()

// somewhere in my sh***y code
try {
    possiblyErroringLine()
} catch (e: Throwable) {
    throw MyCoolError(e)
}

But this approach throws (pun intended) you into the try-catch hell once again since you need to wrap every possible line that may errors.

Now if you test your K/MPP library in JVM and let it crash you do get the stack trace printed. But what if you want to catch and log the errors in common code? There is no stack trace there!

Has anyone tackled this issue?

In short, the situation will be better in 1.4 with common printStackTrace() and stackTraceToString() functions:
https://youtrack.jetbrains.com/issue/KT-38044
https://youtrack.jetbrains.com/issue/KT-37603

3 Likes

Oh that is really nice :slight_smile: but what about JS? Does it just prints an empty string?

No, it obtains the stack from Error.stack property. It’s non-standard, but is de facto supported by JS engines:

2 Likes

Would like to mention that JS supports stack strings, but entirely lacks stack objects. So you can’t inspect it, only display it. There’s an old proposal to fix that, but it’s been dormant for years.

I use this log function to see expanded errors on the console. Also, I use a general wrapper for event handlers / async functions to call this in case of error:

fun log(ex: Throwable?): String {
    if (ex == null) return ""

    val stack = ex.asDynamic().stack
    return if (stack is String) {
        val error = js("Error()")
        error.name = ex.toString().substringBefore(':')
        error.message = ex.message?.substringAfter(':')
        error.stack = stack
        @Suppress("UnsafeCastFromDynamic") // magic, this whole function is pure magic
        console.error(error)
        error.toString()
    } else {
        console.log(ex)
        ex.toString()
    }
}