TraceId is not correctly propagated to scope function after a reactive WebClient call.

Demo project is available here

current output:

[6750294cfcbdfb64f056ab0605a90d48-f056ab0605a90d48] com.example.demo.Service                 : Hello, World!
[6750294cfcbdfb64f056ab0605a90d48-f056ab0605a90d48] com.example.demo.Service                 : another log message
[6750294cfcbdfb64f056ab0605a90d48-f056ab0605a90d48] com.example.demo.ServiceClient           : Response received
[                                                 ] com.example.demo.ServiceClient           : Log from also block

expected:

[6750294cfcbdfb64f056ab0605a90d48-f056ab0605a90d48] com.example.demo.Service                 : Hello, World!
[6750294cfcbdfb64f056ab0605a90d48-f056ab0605a90d48] com.example.demo.Service                 : another log message
[6750294cfcbdfb64f056ab0605a90d48-f056ab0605a90d48] com.example.demo.ServiceClient           : Response received
[6750294cfcbdfb64f056ab0605a90d48-f056ab0605a90d48] com.example.demo.ServiceClient           : Log from also block

Comment From: philwebb

I believe this is the expected behavior, @bclozel or @sdeleuze might be able to confirm for sure. The observation is only active whilst the exchange is in progress (see this code).

Your .also { log.info("Log from also block") } call happens after the exchange has completed and therefore outside of the observation.

Comment From: philwebb

Actually, I take that back. I see now that the trace ID should remain since it's a child of the Service call. I think this will need input from the Framework or Micrometer team.

Comment From: bclozel

This log statement does not print the traceId because there is no traceId in the current coroutine execution context; "also" is a coroutine operator:

.also {
    log.info("Log from also block")
}

"map" is a reactor operator and reactor reinstates threadlocals registered with the context propagation library; this log statement prints the traceId because the TraceId has been reinstated by reactor as a threadlocal. This has been enabled with Hooks.enableAutomaticContextPropagation() in your main application class (also see https://docs.spring.io/spring-boot/appendix/application-properties/index.html#application-properties.core.spring.reactor.context-propagation if you would like to use a property instead):

.map {
    log.info("Response received")
    it
}

So to summarize: * blocking code is executed on a thread with ThreadLocals * reactor is non-blocking and operators can reinstate threadlocals in operators, by looking at the reactor context * coroutines are non-blocking and it can reinstate threadlocals in operators, but this must be done manually with the coroutine context

I've amended your sample with the PropagationContextElement class taken from https://github.com/spring-projects/spring-framework/issues/32165#issuecomment-1920986138 and the following:

suspend fun getString(): String {
    return withContext(PropagationContextElement(coroutineContext)) {
        client
            .get()
            .retrieve()
            .toEntity(String::class.java)
            .map {
                log.info("Response received")
                it
            }
            .awaitSingle()
            .body!!
            .also {
                log.info("Log from also block")
            }
    }
}

This logs:

2024-12-05T08:53:59.143+01:00  INFO [,67515c1779afeb3d47c600ee55d23b98,47c600ee55d23b98] 7306 --- [ctor-http-nio-3] [67515c1779afeb3d47c600ee55d23b98-47c600ee55d23b98] com.example.demo.Service                 : Hello, World!
2024-12-05T08:53:59.143+01:00  INFO [,67515c1779afeb3d47c600ee55d23b98,47c600ee55d23b98] 7306 --- [ctor-http-nio-3] [67515c1779afeb3d47c600ee55d23b98-47c600ee55d23b98] com.example.demo.Service                 : another log message
2024-12-05T08:53:59.406+01:00  INFO [,67515c1779afeb3d47c600ee55d23b98,47c600ee55d23b98] 7306 --- [ctor-http-nio-3] [67515c1779afeb3d47c600ee55d23b98-47c600ee55d23b98] com.example.demo.ServiceClient           : Response received
2024-12-05T08:53:59.407+01:00  INFO [,67515c1779afeb3d47c600ee55d23b98,47c600ee55d23b98] 7306 --- [ctor-http-nio-3] [67515c1779afeb3d47c600ee55d23b98-47c600ee55d23b98] com.example.demo.ServiceClient           : Log from also block

I'm closing this as a duplicate of I think this is a duplicate of https://github.com/spring-projects/spring-framework/issues/32165

If you believe this feature should be supported, please vote for https://github.com/Kotlin/kotlinx.coroutines/issues/4187