Hello everyone, I’m currently using `StatusPages` ...
# ktor
l
Hello everyone, I’m currently using
StatusPages
and I want to extract some parameters from the
coroutineContext
but every time that I want to access to it from
StatusPages
, it’s null. If I try to access before
StatusPages
kicks in, I can find the value. Walking through the code I found that StatusPages is using
CallFailed
hook and I found the following:
Copy code
public object CallFailed : Hook<suspend (call: ApplicationCall, cause: Throwable) -> Unit> {

    private val phase = PipelinePhase("BeforeSetup")
    override fun install(
        pipeline: ApplicationCallPipeline,
        handler: suspend (call: ApplicationCall, cause: Throwable) -> Unit
    ) {
        pipeline.insertPhaseBefore(ApplicationCallPipeline.Setup, phase)
        pipeline.intercept(phase) {
            try {
                coroutineScope {
                    proceed() // Comment added by me: This is a suspend call
                }
            } catch (cause: Throwable) {
                handler(call, cause) // Comment added by me: This is a suspend call
                if (!call.response.isSent) throw cause
            }
        }
    }
}
Therefore, if I throw an exception, this one is intercepted by the catch and the
handler
function is executed without a specific
coroutineScope
. So, mine main question is: Shouldn’t the handler us the
coroutineScope
as well in order to inherit the scope from the caller? (my assumption is that during normal execution flow I can access the
coroutineContext
because of the
coroutineScope
call)
a
What are you trying to access from the context and where do you create it?
l
It’s a custom
CoroutineContext.Key
:
Copy code
class SpanPropagator {
    data class SpanPropagationContext(val span: Span) :
       AbstractCoroutineContextElement(SpanPropagationContext) {
       companion object Key : CoroutineContext.Key<SpanPropagationContext>
    }

    class Config

    companion object Plugin : BaseApplicationPlugin<Application, Config, SpanPropagator> {
       override val key: AttributeKey<SpanPropagator> = AttributeKey("SpanPropagator")

       override fun install(pipeline: Application, configure: Config.() -> Unit): SpanPropagator {
          val plugin = SpanPropagator()
          pipeline.intercept(ApplicationCallPipeline.Monitoring) {
             val tracer = GlobalTracer.get()
             val span =
                tracer.activeSpan() ?: GlobalTracer.get().buildSpan("server.request").start()

             val scope = tracer.activateSpan(span)

             try {
                withContext(coroutineContext + MDCContext() + SpanPropagationContext(span)) {
                   proceed()
                }
             } finally {
                scope.close()
                finish()
             }
          }
          return plugin
       }
    }
}

fun CoroutineContext.getSpan() = this[SpanPropagationContext]?.span
This is installed with
install
and then from
StatusPages
configuration I’m doing:
Copy code
val activeSpan = coroutineContext.getSpan()
a
Thanks, I’ll have a look. Before that though, are you not using Otel’s built-in Ktor tracing?
l
No, because we’re not using otel agent, we’re using datadog one that has some intersections with otel one. Anyways, is this the doc?
a
Ah, apologies. And yes that should be it.
l
I will take a look anyways, maybe Datadog supports otel agent
but nevertheless it would be amazing if
statusPages
can access the coroutineContext
a
It can access
coroutineContext
in general, I do something vaguely similar in that I’m accessing an element in the exception handler.
l
which version of ktor are you using? tbh, I don’t understand why there is that
coroutineContext
in the try but not in the catch
a
2.3.10 - if you mean the
coroutineScope
, it just creates a new
Job
for proceed. The outer
coroutineContext
is implicitly available in
handler
, so it’s likely not that. Note that the coroutine may
proceed
on a different thread.
👍 1
a
As a workaround, you can store data in the
Attributes
(
ApplicationCall.attributes
) which have a lifetime of the call.
l
hey @Aleksei Tirman [JB] that’s what I’m doing now as a workaround, but I have the span in the coroutineContext and in the call attributes, which is not ideal. Within the application/business code I access to the context and in
StatusPages
to the call attributes. Do you know if this is something expected or could be a bug in my code? perhaps I can create an empty ktor app with status page and test it
a
I've tried the following code:
Copy code
embeddedServer(Netty, port = 3333) {
    install(StatusPages) {
        exception<RuntimeException> { call, cause ->
            println(cause)
            println(kotlin.coroutines.coroutineContext)
        }
    }
    routing {
        get {
            throw RuntimeException()
        }
    }

    intercept(ApplicationCallPipeline.Monitoring) {
        withContext(SpanPropagationContext()) {
            println(coroutineContext)
            proceed()
        }
    }
}.start(wait = true)
As a result, I observe your described behavior but I am not sure if that's expected or not.
I've created an issue.
l
Thank you! I’ll give a 👍