Refine PropagationContextElement

This commit apply several refinements to PropagationContextElement:
 - Capture the ThreadLocal when instantiating the
   PropagationContextElement in order to support dispatchers switching
   threads
 - Remove the constructor parameter which is not idiomatic and breaks
   the support when switching threads, and use instead the
   updateThreadContext(context: CoroutineContext) parameter
 - Make the kotlinx-coroutines-reactor dependency optional
 - Make the properties private

The Javadoc and tests are also updated to use the
`Dispatchers.IO + PropagationContextElement()` pattern performed
outside of the suspending lambda, which is the typical use case.

Closes gh-35469
This commit is contained in:
Sébastien Deleuze
2025-09-12 17:26:07 +02:00
parent 20e1149dde
commit 2faed3cdbb
4 changed files with 81 additions and 76 deletions
@@ -257,29 +257,20 @@ For Kotlin `Flow`, a `Flow<T>.transactional` extension is provided.
Spring applications are xref:integration/observability.adoc[instrumented with Micrometer for Observability support].
For tracing support, the current observation is propagated through a `ThreadLocal` for blocking code,
or the Reactor `Context` for reactive pipelines. But the current observation also needs to be made available
in the execution context of a suspended function. Without that, the current "traceId" will not be automatically prepended
to logged statements from coroutines.
in the execution context of a suspended function. Without that, the current "traceId" will not be automatically
prepended to logged statements from coroutines.
The `org.springframework.core.PropagationContextElement` operator generally ensures that the
The {spring-framework-api-kdoc}/spring-core/org.springframework.core/-propagation-context-element/index.html[`PropagationContextElement`] operator generally ensures that the
{micrometer-context-propagation-docs}/[Micrometer Context Propagation library] works with Kotlin Coroutines.
The `PropagationContextElement` requires the following dependencies:
It requires the `io.micrometer:context-propagation` dependency and optionally the
`org.jetbrains.kotlinx:kotlinx-coroutines-reactor` one.
`build.gradle.kts`
[source,kotlin,indent=0]
----
dependencies {
implementation("io.micrometer:context-propagation:${contextPropagationVersion}")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${coroutinesVersion}")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:${coroutinesVersion}")
}
----
Applications can then use the `PropagationContextElement` operator to connect the `currentCoroutineContext()`
Applications can then use the `PropagationContextElement` to augment the `CoroutineContext`
with the context propagation mechanism:
include-code::./ContextPropagationSample[tag=context,indent=0]
Here, assuming that Micrometer Tracing is configured, the resulting logging statement
will show the current "traceId" and unlock better observability for your application.
Here, assuming that Micrometer Tracing is configured, the resulting logging statement will show the current "traceId"
and unlock better observability for your application.
@@ -16,8 +16,9 @@
package org.springframework.docs.languages.kotlin.coroutines.propagation
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.withContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.apache.commons.logging.Log
import org.apache.commons.logging.LogFactory
import org.springframework.core.PropagationContextElement
@@ -31,10 +32,15 @@ class ContextPropagationSample {
}
// tag::context[]
fun main() {
runBlocking(Dispatchers.IO + PropagationContextElement()) {
suspendingFunction()
}
}
suspend fun suspendingFunction() {
return withContext(PropagationContextElement(currentCoroutineContext())) {
logger.info("Suspending function with traceId")
}
delay(1)
logger.info("Suspending function with traceId")
}
// end::context[]
}