Getting coroutine name for logging in Kotlin JS

Hi. For logging I would like to get the current thread & coroutine id from within a non-suspending function (logging library).

In the JVM world logging libraries can get the current thread and coroutine id with

Thread.currentThread().name

How can I do this in JS?
I know there is

console.log("current context", coroutineContext[CoroutineName])

but this requires a suspend function. I’ve digged around in Dispatchers.Main but couldn’t find anything.

I do not think that I understand what you want to do. If you are logging something in a coroutine, you are already in the suspended world. And you can call a non-suspending logging function from it.

I think OP’s case is that suspend function calls a not-suspend function and he would want to acquire a coroutine name from this non-suspend function.

Pass it as a parameter or make it suspending.

Yes exactly, the scenario is “suspend function calls a not-suspend function and he would want to acquire a coroutine name from this non-suspend function”.

The question is whether there is a way to get the coroutine id/name in there … without changing it to suspended obviously.

This is possible in JVM land via currentThread().name. Also having this globally accessible makes sense as there is a clear deterministic answer to this and forcing everyone to change libraries seems unnecessary.

Is there something similar to ThreadContextElement, but unrelated to JVM threads? I mean just a generic event listener with e.g. onResume(CoroutineContext) and onSuspend(CoroutineContext) functions. Contrary to ThreadContextElement, it could be available on other platforms than JVM.

I guess it would help solve OP’s and similar problems (but I imagine some users would abuse it).

It is probably possible to add hooks to the coroutine resuming, but I do not see how it could be used. It is much easier to add a logging method to the coroutine itself around the suspension point.

As for getting coroutine context from a non-suspending method, no, it is not possible. Contrary to the thread mechanical, where a thread is implicitly managed by VM in runtime, coroutines are explicitly managed in compile-time. So coroutine context in some sense is coroutine. And if it is not available, it could not be reached.

1 Like

I don’t really understand the internals of coroutines. In my naive mind I imagined the mechanism that invokes my coroutine could simply set some globally accessible variable “currentCoroutine”, then run my coroutine and finally set it to null again. Or invoke some hook. As JS is single threaded there is no other code to “jump” in between.

As I already said, coroutines are language feature, not VM/OS feature. In order to know which coroutine is called this piece of code, you need its coroutineContext programmatically. It could be obtained by either making it suspend, then it is passed to a function implicitly, or pass it explicitly.

And just in case, you can mark functions as suspend even if they do not do any suspended operations themselves at no cost. So the correct solution to your problem is to mark your logging functions suspend and access coroutineContext from it.

1 Like

Thanks for the advice. Not really happy that it’s not possible but changing the logging function to suspend is then probably the best solution.

Actually, bridging coroutines and existing non-suspendable code is one of my biggest concerns regarding coroutines right now. I explained my point here: https://www.reddit.com/r/Kotlin/comments/oawsdj/want_to_know_more_about_kotlin_coroutines/h3kj9oj/ . As I don’t really see how this problem could be entirely avoided, I’m not satisfied with answers like: you can’t do that, it is impossible, etc.

I tried to implement hooks for resuming of coroutines, as stated above and… it turned out to be much easier than I expected :slight_smile: I think I have a working example for you, I tested it both on JVM with single-threaded dispatcher and on JS and it seems to work fine:

var coroutineName: String? = null

class InterceptCoroutineName(
    private val dispatcher: ContinuationInterceptor
) : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        return dispatcher.interceptContinuation(object : Continuation<T> {
            override val context get() = continuation.context

            override fun resumeWith(result: Result<T>) {
                coroutineName = continuation.context[CoroutineName]?.name
                continuation.resumeWith(result)
            }
        })
    }
}

I tested this for JS with the following code:

GlobalScope.launch(InterceptCoroutineName(Dispatchers.Default)) {
    repeat(10) { i ->
        launch(CoroutineName("coroutine-$i")) {
            println("1: ${coroutineContext[CoroutineName]}")
            println("2: $coroutineName")
            delay(Random.nextLong(500, 1000))
            println("3: ${coroutineContext[CoroutineName]}")
            println("4: $coroutineName")
        }
    }
}

Note that InterceptCoroutineName replaces/wraps the dispatcher, so if you switch to another dispatcher, you will lose this feature. Also, this code works properly only because JS is single-threaded. Otherwise, concurrent coroutines would override the shared variable.

It seems very hacky at first, but I’m not sure if it really is. It uses public API only and it does not really exploit it - it just uses what this API makes available to us.

Also, please note that I didn’t test this thoroughly and I don’t have an expert knowledge about coroutines, so I can’t predict all consequences or side-effects of this technique. Use at your own risk!

2 Likes

Thank you. This is the solution I’ve been looking for. It doesn’t look too hacky to me.

1 Like