Skip to content

Ktor 3 client auto-instrumentation does not propagate trace context when used inside Ktor server #18602

Description

@ssmtlbn-ehex

Describe the bug

When using the OpenTelemetry Java Agent with a Ktor 3 application, outgoing requests made via HttpClient inside a Ktor server handler do not propagate the trace context.

  • Context.current() and Span.current() are valid inside the server handler
  • However, the outgoing Ktor client request does not include a traceparent header
  • No CLIENT span is created for the outgoing request

The Ktor client instrumentation module is applied according to debug logs.

Steps to reproduce

Minimal JUnit test:

@Test
fun `ktor client inside ktor server does send traceparent`() = testApplication {
    var receivedTraceparent: String? = null

    val stubPort = 9090
    val stubServer = embeddedServer(CIO, port = stubPort) {
        routing {
            get("/tracing-test") {
                receivedTraceparent = call.request.headers["traceparent"]
                call.respond(HttpStatusCode.OK)
            }
        }
    }.start(wait = false)

    val outboundClient = HttpClient(CIO)

    try {
        application {
            routing {
                get("/trigger") {
                    val currentSpan = Span.current()
                    val traceparent = Context.current()

                    println("Current span valid: ${currentSpan.spanContext.isValid}")
                    println("Current traceId: ${currentSpan.spanContext.traceId}")
                    println("Current traceparent: $traceparent")

                    outboundClient.get("http://localhost:$stubPort/tracing-test")

                    call.respond(HttpStatusCode.OK)
                }
            }
        }

        val testClient = createClient {}

        testClient.get("/trigger")

        println("Received traceparent: $receivedTraceparent")

        assertNotNull(receivedTraceparent, "traceparent should be propagated")
    } finally {
        outboundClient.close()
        stubServer.stop(1_000, 2_000)
    }
}

Expected behavior

  • A CLIENT span should be created for the outgoing Ktor request
  • The traceparent header should be injected automatically

Actual behavior

  • No CLIENT span is created
  • No traceparent header is sent

Javaagent or library instrumentation version

2.26.1, 2.27.0

Environment

JDK: 21
Kotlin: 2.3.21
Ktor: 3.4.3

Additional context

No response

Tip

React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it. Learn more here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions