From 3bd752f84f12ea53c4e270cc1a7980b3a4f23108 Mon Sep 17 00:00:00 2001 From: "leonid.stashevsky" Date: Thu, 24 Oct 2024 09:55:56 +0300 Subject: [PATCH 1/6] Add Ktor 3.0 support --- .../ktor/v2_0/ServerInstrumentation.java | 7 +- .../ktor/ktor-3.0/testing/build.gradle.kts | 28 ++++ .../v3_0/client/AbstractKtorHttpClientTest.kt | 75 ++++++++++ .../KtorHttpClientInstrumentationTest.kt | 22 +++ .../client/KtorHttpClientSingleConnection.kt | 44 ++++++ .../v3_0/server/AbstractKtorHttpServerTest.kt | 141 ++++++++++++++++++ .../KtorHttpServerInstrumentationTest.kt | 33 ++++ settings.gradle.kts | 1 + 8 files changed, 349 insertions(+), 2 deletions(-) create mode 100644 instrumentation/ktor/ktor-3.0/testing/build.gradle.kts create mode 100644 instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/client/AbstractKtorHttpClientTest.kt create mode 100644 instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/client/KtorHttpClientInstrumentationTest.kt create mode 100644 instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/client/KtorHttpClientSingleConnection.kt create mode 100644 instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/AbstractKtorHttpServerTest.kt create mode 100644 instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/KtorHttpServerInstrumentationTest.kt diff --git a/instrumentation/ktor/ktor-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/ktor/v2_0/ServerInstrumentation.java b/instrumentation/ktor/ktor-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/ktor/v2_0/ServerInstrumentation.java index 835e247f004b..d97c7333bcb5 100644 --- a/instrumentation/ktor/ktor-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/ktor/v2_0/ServerInstrumentation.java +++ b/instrumentation/ktor/ktor-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/ktor/v2_0/ServerInstrumentation.java @@ -6,7 +6,7 @@ package io.opentelemetry.javaagent.instrumentation.ktor.v2_0; import static net.bytebuddy.matcher.ElementMatchers.isConstructor; -import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; import io.ktor.server.application.Application; import io.ktor.server.application.ApplicationPluginKt; @@ -25,7 +25,10 @@ public class ServerInstrumentation implements TypeInstrumentation { @Override public ElementMatcher typeMatcher() { - return named("io.ktor.server.engine.ApplicationEngineEnvironmentReloading"); + return namedOneOf( + "io.ktor.server.engine.ApplicationEngineEnvironmentReloading", // Ktor 2.0 + "io.ktor.server.engine.EmbeddedServer" // Ktor 3.0 + ); } @Override diff --git a/instrumentation/ktor/ktor-3.0/testing/build.gradle.kts b/instrumentation/ktor/ktor-3.0/testing/build.gradle.kts new file mode 100644 index 000000000000..d3fb3f3d0acc --- /dev/null +++ b/instrumentation/ktor/ktor-3.0/testing/build.gradle.kts @@ -0,0 +1,28 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + id("otel.java-conventions") + + id("org.jetbrains.kotlin.jvm") +} + +val ktorVersion = "3.0.0" + +dependencies { + api(project(":testing-common")) + + implementation("io.ktor:ktor-client-core:$ktorVersion") + implementation("io.ktor:ktor-server-core:$ktorVersion") + + implementation("io.opentelemetry:opentelemetry-extension-kotlin") + + compileOnly("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + compileOnly("io.ktor:ktor-server-netty:$ktorVersion") + compileOnly("io.ktor:ktor-client-cio:$ktorVersion") +} + +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_1_8) + } +} diff --git a/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/client/AbstractKtorHttpClientTest.kt b/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/client/AbstractKtorHttpClientTest.kt new file mode 100644 index 000000000000..9f19bb9ed5c5 --- /dev/null +++ b/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/client/AbstractKtorHttpClientTest.kt @@ -0,0 +1,75 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ktor.v3_0.client + +import io.ktor.client.* +import io.ktor.client.engine.cio.* +import io.ktor.client.plugins.* +import io.ktor.client.request.* +import io.ktor.http.* +import io.opentelemetry.context.Context +import io.opentelemetry.extension.kotlin.asContextElement +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientResult +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientTestOptions +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientTestOptions.DEFAULT_HTTP_ATTRIBUTES +import io.opentelemetry.semconv.NetworkAttributes +import kotlinx.coroutines.* +import java.net.URI + +abstract class AbstractKtorHttpClientTest : AbstractHttpClientTest() { + + private val client = HttpClient(CIO) { + install(HttpRedirect) + + installTracing() + } + + abstract fun HttpClientConfig<*>.installTracing() + + override fun buildRequest(requestMethod: String, uri: URI, requestHeaders: MutableMap) = HttpRequestBuilder(uri.toURL()).apply { + method = HttpMethod.parse(requestMethod) + + requestHeaders.forEach { (header, value) -> headers.append(header, value) } + } + + override fun sendRequest(request: HttpRequestBuilder, method: String, uri: URI, headers: MutableMap) = runBlocking { + client.request(request).status.value + } + + override fun sendRequestWithCallback( + request: HttpRequestBuilder, + method: String, + uri: URI, + headers: MutableMap, + httpClientResult: HttpClientResult, + ) { + CoroutineScope(Dispatchers.Default + Context.current().asContextElement()).launch { + try { + val statusCode = client.request(request).status.value + httpClientResult.complete(statusCode) + } catch (e: Throwable) { + httpClientResult.complete(e) + } + } + } + + override fun configure(optionsBuilder: HttpClientTestOptions.Builder) { + with(optionsBuilder) { + disableTestReadTimeout() + // this instrumentation creates a span per each physical request + // related issue https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/5722 + disableTestRedirects() + spanEndsAfterBody() + + setHttpAttributes { DEFAULT_HTTP_ATTRIBUTES - setOf(NetworkAttributes.NETWORK_PROTOCOL_VERSION) } + + setSingleConnectionFactory { host, port -> + KtorHttpClientSingleConnection(host, port) { installTracing() } + } + } + } +} diff --git a/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/client/KtorHttpClientInstrumentationTest.kt b/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/client/KtorHttpClientInstrumentationTest.kt new file mode 100644 index 000000000000..d32922a71e96 --- /dev/null +++ b/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/client/KtorHttpClientInstrumentationTest.kt @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ktor.v3_0.client + +import io.ktor.client.* +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientInstrumentationExtension +import org.junit.jupiter.api.extension.RegisterExtension + +class KtorHttpClientInstrumentationTest : AbstractKtorHttpClientTest() { + + companion object { + @JvmStatic + @RegisterExtension + private val TESTING = HttpClientInstrumentationExtension.forAgent() + } + + override fun HttpClientConfig<*>.installTracing() { + } +} diff --git a/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/client/KtorHttpClientSingleConnection.kt b/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/client/KtorHttpClientSingleConnection.kt new file mode 100644 index 000000000000..f76f6975a9f9 --- /dev/null +++ b/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/client/KtorHttpClientSingleConnection.kt @@ -0,0 +1,44 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ktor.v3_0.client + +import io.ktor.client.* +import io.ktor.client.engine.cio.* +import io.ktor.client.request.* +import io.opentelemetry.instrumentation.testing.junit.http.SingleConnection +import kotlinx.coroutines.runBlocking + +class KtorHttpClientSingleConnection( + private val host: String, + private val port: Int, + private val installTracing: HttpClientConfig<*>.() -> Unit, +) : SingleConnection { + + private val client: HttpClient + + init { + val engine = CIO.create { + maxConnectionsCount = 1 + } + + client = HttpClient(engine) { + installTracing() + } + } + + override fun doRequest(path: String, requestHeaders: MutableMap) = runBlocking { + val request = HttpRequestBuilder( + scheme = "http", + host = host, + port = port, + path = path, + ).apply { + requestHeaders.forEach { (name, value) -> headers.append(name, value) } + } + + client.request(request).status.value + } +} diff --git a/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/AbstractKtorHttpServerTest.kt b/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/AbstractKtorHttpServerTest.kt new file mode 100644 index 000000000000..607e4427d5ef --- /dev/null +++ b/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/AbstractKtorHttpServerTest.kt @@ -0,0 +1,141 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ktor.v3_0.server + +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.engine.* +import io.ktor.server.netty.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import io.opentelemetry.api.trace.Span +import io.opentelemetry.api.trace.SpanKind +import io.opentelemetry.api.trace.StatusCode +import io.opentelemetry.context.Context +import io.opentelemetry.extension.kotlin.asContextElement +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint +import io.opentelemetry.semconv.ServerAttributes +import kotlinx.coroutines.withContext +import java.util.concurrent.ExecutionException +import java.util.concurrent.TimeUnit + +abstract class AbstractKtorHttpServerTest : AbstractHttpServerTest>() { + + abstract fun getTesting(): InstrumentationExtension + + abstract fun installOpenTelemetry(application: Application) + + override fun setupServer(): EmbeddedServer<*, *> { + return embeddedServer(Netty, port = port) { + installOpenTelemetry(this) + + routing { + get(ServerEndpoint.SUCCESS.path) { + controller(ServerEndpoint.SUCCESS) { + call.respondText(ServerEndpoint.SUCCESS.body, status = HttpStatusCode.fromValue(ServerEndpoint.SUCCESS.status)) + } + } + + get(ServerEndpoint.REDIRECT.path) { + controller(ServerEndpoint.REDIRECT) { + call.respondRedirect(ServerEndpoint.REDIRECT.body) + } + } + + get(ServerEndpoint.ERROR.path) { + controller(ServerEndpoint.ERROR) { + call.respondText(ServerEndpoint.ERROR.body, status = HttpStatusCode.fromValue(ServerEndpoint.ERROR.status)) + } + } + + get(ServerEndpoint.EXCEPTION.path) { + controller(ServerEndpoint.EXCEPTION) { + throw Exception(ServerEndpoint.EXCEPTION.body) + } + } + + get("/query") { + controller(ServerEndpoint.QUERY_PARAM) { + call.respondText("some=${call.request.queryParameters["some"]}", status = HttpStatusCode.fromValue(ServerEndpoint.QUERY_PARAM.status)) + } + } + + get("/path/{id}/param") { + controller(ServerEndpoint.PATH_PARAM) { + call.respondText( + call.parameters["id"] + ?: "", + status = HttpStatusCode.fromValue(ServerEndpoint.PATH_PARAM.status), + ) + } + } + + get("/child") { + controller(ServerEndpoint.INDEXED_CHILD) { + ServerEndpoint.INDEXED_CHILD.collectSpanAttributes { call.request.queryParameters[it] } + call.respondText(ServerEndpoint.INDEXED_CHILD.body, status = HttpStatusCode.fromValue(ServerEndpoint.INDEXED_CHILD.status)) + } + } + + get("/captureHeaders") { + controller(ServerEndpoint.CAPTURE_HEADERS) { + call.response.header("X-Test-Response", call.request.header("X-Test-Request") ?: "") + call.respondText(ServerEndpoint.CAPTURE_HEADERS.body, status = HttpStatusCode.fromValue(ServerEndpoint.CAPTURE_HEADERS.status)) + } + } + } + }.start() + } + + override fun stopServer(server: EmbeddedServer<*, *>) { + server.stop(0, 10, TimeUnit.SECONDS) + } + + // Copy in HttpServerTest.controller but make it a suspending function + private suspend fun controller(endpoint: ServerEndpoint, wrapped: suspend () -> Unit) { + assert(Span.current().spanContext.isValid, { "Controller should have a parent span. " }) + if (endpoint == ServerEndpoint.NOT_FOUND) { + wrapped() + } + val span = getTesting().openTelemetry.getTracer("test").spanBuilder("controller").setSpanKind(SpanKind.INTERNAL).startSpan() + try { + withContext(Context.current().with(span).asContextElement()) { + wrapped() + } + span.end() + } catch (e: Exception) { + span.setStatus(StatusCode.ERROR) + span.recordException(if (e is ExecutionException) e.cause ?: e else e) + span.end() + throw e + } + } + + override fun configure(options: HttpServerTestOptions) { + options.setTestPathParam(true) + + options.setHttpAttributes { + HttpServerTestOptions.DEFAULT_HTTP_ATTRIBUTES - ServerAttributes.SERVER_PORT + } + + options.setExpectedHttpRoute { endpoint, method -> + when (endpoint) { + ServerEndpoint.PATH_PARAM -> "/path/{id}/param" + else -> expectedHttpRoute(endpoint, method) + } + } + + // ktor does not have a controller lifecycle so the server span ends immediately when the + // response is sent, which is before the controller span finishes. + options.setVerifyServerSpanEndTime(false) + + options.setResponseCodeOnNonStandardHttpMethod(405) + } +} diff --git a/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/KtorHttpServerInstrumentationTest.kt b/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/KtorHttpServerInstrumentationTest.kt new file mode 100644 index 000000000000..be98755f200c --- /dev/null +++ b/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/KtorHttpServerInstrumentationTest.kt @@ -0,0 +1,33 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ktor.v3_0.server + +import io.ktor.server.application.* +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions +import org.junit.jupiter.api.extension.RegisterExtension + +class KtorHttpServerInstrumentationTest : AbstractKtorHttpServerTest() { + + companion object { + @JvmStatic + @RegisterExtension + val TESTING: InstrumentationExtension = HttpServerInstrumentationExtension.forAgent() + } + + override fun getTesting(): InstrumentationExtension { + return TESTING + } + + override fun installOpenTelemetry(application: Application) { + } + + override fun configure(options: HttpServerTestOptions) { + super.configure(options) + options.setTestException(false) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index c9e944fdb48c..6306b9203c9c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -402,6 +402,7 @@ include(":instrumentation:ktor:ktor-1.0:library") include(":instrumentation:ktor:ktor-2.0:javaagent") include(":instrumentation:ktor:ktor-2.0:library") include(":instrumentation:ktor:ktor-2.0:testing") +include(":instrumentation:ktor:ktor-3.0:testing") include(":instrumentation:ktor:ktor-common:library") include(":instrumentation:kubernetes-client-7.0:javaagent") include(":instrumentation:kubernetes-client-7.0:javaagent-unit-tests") From 014f333ff0cce4108cef59cab5ada8760d86c2f4 Mon Sep 17 00:00:00 2001 From: "leonid.stashevsky" Date: Tue, 29 Oct 2024 12:53:05 +0100 Subject: [PATCH 2/6] fixup! Add Ktor 3.0 support --- .../ktor/v3_0/server/AbstractKtorHttpServerTest.kt | 2 +- .../ktor/v3_0/client/KtorHttpClientInstrumentationTest.kt | 0 .../ktor/v3_0/server/KtorHttpServerInstrumentationTest.kt | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) rename instrumentation/ktor/ktor-3.0/testing/src/{main => test}/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/client/KtorHttpClientInstrumentationTest.kt (100%) rename instrumentation/ktor/ktor-3.0/testing/src/{main => test}/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/KtorHttpServerInstrumentationTest.kt (88%) diff --git a/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/AbstractKtorHttpServerTest.kt b/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/AbstractKtorHttpServerTest.kt index 607e4427d5ef..0f5ccacfac56 100644 --- a/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/AbstractKtorHttpServerTest.kt +++ b/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/AbstractKtorHttpServerTest.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.ktor.v3_0.server +package io.opentelemetry.instrumentation.ktor.v3_0.server.io.opentelemetry.instrumentation.ktor.v3_0.server import io.ktor.http.* import io.ktor.server.application.* diff --git a/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/client/KtorHttpClientInstrumentationTest.kt b/instrumentation/ktor/ktor-3.0/testing/src/test/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/client/KtorHttpClientInstrumentationTest.kt similarity index 100% rename from instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/client/KtorHttpClientInstrumentationTest.kt rename to instrumentation/ktor/ktor-3.0/testing/src/test/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/client/KtorHttpClientInstrumentationTest.kt diff --git a/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/KtorHttpServerInstrumentationTest.kt b/instrumentation/ktor/ktor-3.0/testing/src/test/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/KtorHttpServerInstrumentationTest.kt similarity index 88% rename from instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/KtorHttpServerInstrumentationTest.kt rename to instrumentation/ktor/ktor-3.0/testing/src/test/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/KtorHttpServerInstrumentationTest.kt index be98755f200c..164d6b2b5350 100644 --- a/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/KtorHttpServerInstrumentationTest.kt +++ b/instrumentation/ktor/ktor-3.0/testing/src/test/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/KtorHttpServerInstrumentationTest.kt @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.ktor.v3_0.server import io.ktor.server.application.* +import io.opentelemetry.instrumentation.ktor.v3_0.server.io.opentelemetry.instrumentation.ktor.v3_0.server.AbstractKtorHttpServerTest import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions From d50e2b43981609414f8c9ed8f4159d3debe7410c Mon Sep 17 00:00:00 2001 From: "leonid.stashevsky" Date: Tue, 29 Oct 2024 12:58:09 +0100 Subject: [PATCH 3/6] fixup! fixup! Add Ktor 3.0 support --- .../instrumentation/ktor/v2_0/ServerInstrumentation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/ktor/ktor-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/ktor/v2_0/ServerInstrumentation.java b/instrumentation/ktor/ktor-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/ktor/v2_0/ServerInstrumentation.java index d97c7333bcb5..1ac271a6e448 100644 --- a/instrumentation/ktor/ktor-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/ktor/v2_0/ServerInstrumentation.java +++ b/instrumentation/ktor/ktor-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/ktor/v2_0/ServerInstrumentation.java @@ -28,7 +28,7 @@ public ElementMatcher typeMatcher() { return namedOneOf( "io.ktor.server.engine.ApplicationEngineEnvironmentReloading", // Ktor 2.0 "io.ktor.server.engine.EmbeddedServer" // Ktor 3.0 - ); + ); } @Override From 5f51e779a98cd10cc6e6921fe97045c36f1e78b7 Mon Sep 17 00:00:00 2001 From: "leonid.stashevsky" Date: Thu, 31 Oct 2024 10:05:57 +0100 Subject: [PATCH 4/6] fixup! fixup! fixup! Add Ktor 3.0 support --- .../ktor/v2_0/ServerInstrumentation.java | 9 +- .../ktor/v2_0/internal/KtorBuilderUtil.kt | 4 +- .../ktor/v2_0/server/KtorServerTracing.kt | 382 ++++++++---------- .../ktor/v2_0/server/KtorTracer.kt | 29 ++ .../ktor/ktor-3.0/testing/build.gradle.kts | 4 + .../v3_0/server/AbstractKtorHttpServerTest.kt | 2 +- .../KtorHttpClientInstrumentationTest.kt | 8 +- .../KtorHttpServerInstrumentationTest.kt | 16 +- 8 files changed, 240 insertions(+), 214 deletions(-) create mode 100644 instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/server/KtorTracer.kt diff --git a/instrumentation/ktor/ktor-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/ktor/v2_0/ServerInstrumentation.java b/instrumentation/ktor/ktor-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/ktor/v2_0/ServerInstrumentation.java index 1ac271a6e448..dc461ee56917 100644 --- a/instrumentation/ktor/ktor-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/ktor/v2_0/ServerInstrumentation.java +++ b/instrumentation/ktor/ktor-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/ktor/v2_0/ServerInstrumentation.java @@ -12,7 +12,8 @@ import io.ktor.server.application.ApplicationPluginKt; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.ktor.v2_0.internal.KtorBuilderUtil; -import io.opentelemetry.instrumentation.ktor.v2_0.server.KtorServerTracing; +import io.opentelemetry.instrumentation.ktor.v2_0.server.KtorServerTracingBuilder; +import io.opentelemetry.instrumentation.ktor.v2_0.server.KtorServerTracingKt; import io.opentelemetry.javaagent.bootstrap.internal.AgentCommonConfig; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; @@ -42,15 +43,15 @@ public static class ConstructorAdvice { @Advice.OnMethodExit public static void onExit(@Advice.FieldValue("_applicationInstance") Application application) { - ApplicationPluginKt.install(application, KtorServerTracing.Feature, new SetupFunction()); + ApplicationPluginKt.install(application, KtorServerTracingKt.getKtorServerTracing(), new SetupFunction()); } } public static class SetupFunction - implements Function1 { + implements Function1 { @Override - public Unit invoke(KtorServerTracing.Configuration configuration) { + public Unit invoke(KtorServerTracingBuilder configuration) { configuration.setOpenTelemetry(GlobalOpenTelemetry.get()); KtorBuilderUtil.serverBuilderExtractor .invoke(configuration) diff --git a/instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/internal/KtorBuilderUtil.kt b/instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/internal/KtorBuilderUtil.kt index 10bb56d5a720..c6ec5a53c9b6 100644 --- a/instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/internal/KtorBuilderUtil.kt +++ b/instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/internal/KtorBuilderUtil.kt @@ -12,7 +12,7 @@ import io.ktor.server.response.* import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpClientInstrumenterBuilder import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpServerInstrumenterBuilder import io.opentelemetry.instrumentation.ktor.v2_0.client.KtorClientTracingBuilder -import io.opentelemetry.instrumentation.ktor.v2_0.server.KtorServerTracing +import io.opentelemetry.instrumentation.ktor.v2_0.server.KtorServerTracingBuilder /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -20,5 +20,5 @@ import io.opentelemetry.instrumentation.ktor.v2_0.server.KtorServerTracing */ object KtorBuilderUtil { lateinit var clientBuilderExtractor: (KtorClientTracingBuilder) -> DefaultHttpClientInstrumenterBuilder - lateinit var serverBuilderExtractor: (KtorServerTracing.Configuration) -> DefaultHttpServerInstrumenterBuilder + lateinit var serverBuilderExtractor: (KtorServerTracingBuilder) -> DefaultHttpServerInstrumenterBuilder } diff --git a/instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/server/KtorServerTracing.kt b/instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/server/KtorServerTracing.kt index 66d0324a60e1..4a9763a979ef 100644 --- a/instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/server/KtorServerTracing.kt +++ b/instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/server/KtorServerTracing.kt @@ -6,10 +6,12 @@ package io.opentelemetry.instrumentation.ktor.v2_0.server import io.ktor.http.* +import io.ktor.serialization.* import io.ktor.server.application.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* +import io.ktor.server.routing.Routing.Plugin.RoutingCallStarted import io.ktor.util.* import io.ktor.util.pipeline.* import io.opentelemetry.api.OpenTelemetry @@ -19,7 +21,6 @@ import io.opentelemetry.context.Context import io.opentelemetry.extension.kotlin.asContextElement import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpServerInstrumenterBuilder import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor -import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusBuilder import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor @@ -30,263 +31,236 @@ import io.opentelemetry.instrumentation.ktor.v2_0.InstrumentationProperties.INST import io.opentelemetry.instrumentation.ktor.v2_0.internal.KtorBuilderUtil import kotlinx.coroutines.withContext -class KtorServerTracing private constructor( - private val instrumenter: Instrumenter, -) { - - class Configuration { - companion object { - init { - KtorBuilderUtil.serverBuilderExtractor = { it.serverBuilder } - } +class KtorServerTracingBuilder { + companion object { + init { + KtorBuilderUtil.serverBuilderExtractor = { it.serverBuilder } } + } - internal lateinit var serverBuilder: DefaultHttpServerInstrumenterBuilder + internal lateinit var serverBuilder: DefaultHttpServerInstrumenterBuilder - internal var spanKindExtractor: - (SpanKindExtractor) -> SpanKindExtractor = { a -> a } + internal var spanKindExtractor: + (SpanKindExtractor) -> SpanKindExtractor = { a -> a } - fun setOpenTelemetry(openTelemetry: OpenTelemetry) { - this.serverBuilder = - DefaultHttpServerInstrumenterBuilder.create( - INSTRUMENTATION_NAME, - openTelemetry, - KtorHttpServerAttributesGetter.INSTANCE - ) - } + fun setOpenTelemetry(openTelemetry: OpenTelemetry) { + this.serverBuilder = + DefaultHttpServerInstrumenterBuilder.create( + INSTRUMENTATION_NAME, + openTelemetry, + KtorHttpServerAttributesGetter.INSTANCE + ) + } - @Deprecated("Please use method `spanStatusExtractor`") - fun setStatusExtractor( - extractor: (SpanStatusExtractor) -> SpanStatusExtractor - ) { - spanStatusExtractor { prevStatusExtractor -> - extractor(prevStatusExtractor).extract(spanStatusBuilder, request, response, error) - } + @Deprecated("Please use method `spanStatusExtractor`") + fun setStatusExtractor( + extractor: (SpanStatusExtractor) -> SpanStatusExtractor + ) { + spanStatusExtractor { prevStatusExtractor -> + extractor(prevStatusExtractor).extract(spanStatusBuilder, request, response, error) } + } - fun spanStatusExtractor(extract: SpanStatusData.(SpanStatusExtractor) -> Unit) { - serverBuilder.setStatusExtractor { prevExtractor -> - SpanStatusExtractor { spanStatusBuilder: SpanStatusBuilder, - request: ApplicationRequest, - response: ApplicationResponse?, - throwable: Throwable? -> - extract( - SpanStatusData(spanStatusBuilder, request, response, throwable), - prevExtractor - ) - } + fun spanStatusExtractor(extract: SpanStatusData.(SpanStatusExtractor) -> Unit) { + serverBuilder.setStatusExtractor { prevExtractor -> + SpanStatusExtractor { spanStatusBuilder: SpanStatusBuilder, + request: ApplicationRequest, + response: ApplicationResponse?, + throwable: Throwable? -> + extract( + SpanStatusData(spanStatusBuilder, request, response, throwable), + prevExtractor + ) } } + } - data class SpanStatusData( - val spanStatusBuilder: SpanStatusBuilder, - val request: ApplicationRequest, - val response: ApplicationResponse?, - val error: Throwable? - ) - - @Deprecated("Please use method `spanKindExtractor`") - fun setSpanKindExtractor(extractor: (SpanKindExtractor) -> SpanKindExtractor) { - spanKindExtractor { prevSpanKindExtractor -> - extractor(prevSpanKindExtractor).extract(this) - } + data class SpanStatusData( + val spanStatusBuilder: SpanStatusBuilder, + val request: ApplicationRequest, + val response: ApplicationResponse?, + val error: Throwable? + ) + + @Deprecated("Please use method `spanKindExtractor`") + fun setSpanKindExtractor(extractor: (SpanKindExtractor) -> SpanKindExtractor) { + spanKindExtractor { prevSpanKindExtractor -> + extractor(prevSpanKindExtractor).extract(this) } + } - fun spanKindExtractor(extract: ApplicationRequest.(SpanKindExtractor) -> SpanKind) { - spanKindExtractor = { prevExtractor -> - SpanKindExtractor { request: ApplicationRequest -> - extract(request, prevExtractor) - } + fun spanKindExtractor(extract: ApplicationRequest.(SpanKindExtractor) -> SpanKind) { + spanKindExtractor = { prevExtractor -> + SpanKindExtractor { request: ApplicationRequest -> + extract(request, prevExtractor) } } + } - @Deprecated("Please use method `attributeExtractor`") - fun addAttributeExtractor(extractor: AttributesExtractor) { - attributeExtractor { - onStart { - extractor.onStart(attributes, parentContext, request) - } - onEnd { - extractor.onEnd(attributes, parentContext, request, response, error) - } + @Deprecated("Please use method `attributeExtractor`") + fun addAttributeExtractor(extractor: AttributesExtractor) { + attributeExtractor { + onStart { + extractor.onStart(attributes, parentContext, request) + } + onEnd { + extractor.onEnd(attributes, parentContext, request, response, error) } } + } - fun attributeExtractor(extractorBuilder: ExtractorBuilder.() -> Unit = {}) { - val builder = ExtractorBuilder().apply(extractorBuilder).build() - serverBuilder.addAttributesExtractor( - object : AttributesExtractor { - override fun onStart(attributes: AttributesBuilder, parentContext: Context, request: ApplicationRequest) { - builder.onStart(OnStartData(attributes, parentContext, request)) - } - - override fun onEnd(attributes: AttributesBuilder, context: Context, request: ApplicationRequest, response: ApplicationResponse?, error: Throwable?) { - builder.onEnd(OnEndData(attributes, context, request, response, error)) - } + fun attributeExtractor(extractorBuilder: ExtractorBuilder.() -> Unit = {}) { + val builder = ExtractorBuilder().apply(extractorBuilder).build() + serverBuilder.addAttributesExtractor( + object : AttributesExtractor { + override fun onStart(attributes: AttributesBuilder, parentContext: Context, request: ApplicationRequest) { + builder.onStart(OnStartData(attributes, parentContext, request)) } - ) - } - - class ExtractorBuilder { - private var onStart: OnStartData.() -> Unit = {} - private var onEnd: OnEndData.() -> Unit = {} - fun onStart(block: OnStartData.() -> Unit) { - onStart = block + override fun onEnd(attributes: AttributesBuilder, context: Context, request: ApplicationRequest, response: ApplicationResponse?, error: Throwable?) { + builder.onEnd(OnEndData(attributes, context, request, response, error)) + } } + ) + } - fun onEnd(block: OnEndData.() -> Unit) { - onEnd = block - } + class ExtractorBuilder { + private var onStart: OnStartData.() -> Unit = {} + private var onEnd: OnEndData.() -> Unit = {} - internal fun build(): Extractor { - return Extractor(onStart, onEnd) - } + fun onStart(block: OnStartData.() -> Unit) { + onStart = block } - internal class Extractor(val onStart: OnStartData.() -> Unit, val onEnd: OnEndData.() -> Unit) + fun onEnd(block: OnEndData.() -> Unit) { + onEnd = block + } - data class OnStartData( - val attributes: AttributesBuilder, - val parentContext: Context, - val request: ApplicationRequest - ) + internal fun build(): Extractor { + return Extractor(onStart, onEnd) + } + } - data class OnEndData( - val attributes: AttributesBuilder, - val parentContext: Context, - val request: ApplicationRequest, - val response: ApplicationResponse?, - val error: Throwable? - ) + internal class Extractor(val onStart: OnStartData.() -> Unit, val onEnd: OnEndData.() -> Unit) - @Deprecated( - "Please use method `capturedRequestHeaders`", - ReplaceWith("capturedRequestHeaders(headers)") - ) - fun setCapturedRequestHeaders(headers: List) = capturedRequestHeaders(headers) + data class OnStartData( + val attributes: AttributesBuilder, + val parentContext: Context, + val request: ApplicationRequest + ) - fun capturedRequestHeaders(vararg headers: String) = capturedRequestHeaders(headers.asIterable()) + data class OnEndData( + val attributes: AttributesBuilder, + val parentContext: Context, + val request: ApplicationRequest, + val response: ApplicationResponse?, + val error: Throwable? + ) - fun capturedRequestHeaders(headers: Iterable) { - serverBuilder.setCapturedRequestHeaders(headers.toList()) - } + @Deprecated( + "Please use method `capturedRequestHeaders`", + ReplaceWith("capturedRequestHeaders(headers)") + ) + fun setCapturedRequestHeaders(headers: List) = capturedRequestHeaders(headers) - @Deprecated( - "Please use method `capturedResponseHeaders`", - ReplaceWith("capturedResponseHeaders(headers)") - ) - fun setCapturedResponseHeaders(headers: List) = capturedResponseHeaders(headers) + fun capturedRequestHeaders(vararg headers: String) = capturedRequestHeaders(headers.asIterable()) - fun capturedResponseHeaders(vararg headers: String) = capturedResponseHeaders(headers.asIterable()) + fun capturedRequestHeaders(headers: Iterable) { + serverBuilder.setCapturedRequestHeaders(headers.toList()) + } - fun capturedResponseHeaders(headers: Iterable) { - serverBuilder.setCapturedResponseHeaders(headers.toList()) - } + @Deprecated( + "Please use method `capturedResponseHeaders`", + ReplaceWith("capturedResponseHeaders(headers)") + ) + fun setCapturedResponseHeaders(headers: List) = capturedResponseHeaders(headers) - @Deprecated( - "Please use method `knownMethods`", - ReplaceWith("knownMethods(knownMethods)") - ) - fun setKnownMethods(knownMethods: Set) = knownMethods(knownMethods) + fun capturedResponseHeaders(vararg headers: String) = capturedResponseHeaders(headers.asIterable()) - fun knownMethods(vararg methods: String) = knownMethods(methods.asIterable()) + fun capturedResponseHeaders(headers: Iterable) { + serverBuilder.setCapturedResponseHeaders(headers.toList()) + } - fun knownMethods(vararg methods: HttpMethod) = knownMethods(methods.asIterable()) + @Deprecated( + "Please use method `knownMethods`", + ReplaceWith("knownMethods(knownMethods)") + ) + fun setKnownMethods(knownMethods: Set) = knownMethods(knownMethods) - @JvmName("knownMethodsJvm") - fun knownMethods(methods: Iterable) = knownMethods(methods.map { it.value }) + fun knownMethods(vararg methods: String) = knownMethods(methods.asIterable()) - fun knownMethods(methods: Iterable) { - methods.toSet().apply { - serverBuilder.setKnownMethods(this) - } - } + fun knownMethods(vararg methods: HttpMethod) = knownMethods(methods.asIterable()) - /** - * {@link #setOpenTelemetry(OpenTelemetry)} sets the serverBuilder to a non-null value. - */ - internal fun isOpenTelemetryInitialized(): Boolean = this::serverBuilder.isInitialized - } + @JvmName("knownMethodsJvm") + fun knownMethods(methods: Iterable) = knownMethods(methods.map { it.value }) - private fun start(call: ApplicationCall): Context? { - val parentContext = Context.current() - if (!instrumenter.shouldStart(parentContext, call.request)) { - return null + fun knownMethods(methods: Iterable) { + methods.toSet().apply { + serverBuilder.setKnownMethods(this) } - - return instrumenter.start(parentContext, call.request) - } - - private fun end(context: Context, call: ApplicationCall, error: Throwable?) { - instrumenter.end(context, call.request, call.response, error) } - companion object Feature : BaseApplicationPlugin { - - private val contextKey = AttributeKey("OpenTelemetry") - private val errorKey = AttributeKey("OpenTelemetryException") + /** + * {@link #setOpenTelemetry(OpenTelemetry)} sets the serverBuilder to a non-null value. + */ + internal fun isOpenTelemetryInitialized(): Boolean = this::serverBuilder.isInitialized +} - override val key: AttributeKey = AttributeKey("OpenTelemetry") +val KtorServerTracing = createRouteScopedPlugin("OpenTelemetry", ::KtorServerTracingBuilder) { + require(pluginConfig.isOpenTelemetryInitialized()) { "OpenTelemetry must be set" } - override fun install(pipeline: Application, configure: Configuration.() -> Unit): KtorServerTracing { - val configuration = Configuration().apply(configure) + val contextKey = AttributeKey("OpenTelemetry") + val errorKey = AttributeKey("OpenTelemetryException") - require(configuration.isOpenTelemetryInitialized()) { "OpenTelemetry must be set" } + val instrumenter = InstrumenterUtil.buildUpstreamInstrumenter( + pluginConfig.serverBuilder.instrumenterBuilder(), + ApplicationRequestGetter, + pluginConfig.spanKindExtractor(SpanKindExtractor.alwaysServer()) + ) - val instrumenter = InstrumenterUtil.buildUpstreamInstrumenter( - configuration.serverBuilder.instrumenterBuilder(), - ApplicationRequestGetter, - configuration.spanKindExtractor(SpanKindExtractor.alwaysServer()) - ) + val tracer = KtorTracer(instrumenter) + val startPhase = PipelinePhase("OpenTelemetry") - val feature = KtorServerTracing(instrumenter) - - val startPhase = PipelinePhase("OpenTelemetry") - pipeline.insertPhaseBefore(ApplicationCallPipeline.Monitoring, startPhase) - pipeline.intercept(startPhase) { - val context = feature.start(call) - - if (context != null) { - call.attributes.put(contextKey, context) - withContext(context.asContextElement()) { - try { - proceed() - } catch (err: Throwable) { - // Stash error for reporting later since need ktor to finish setting up the response - call.attributes.put(errorKey, err) - throw err - } - } - } else { - proceed() - } - } + application.insertPhaseBefore(ApplicationCallPipeline.Monitoring, startPhase) + application.intercept(startPhase) { + val context = tracer.start(call) - val postSendPhase = PipelinePhase("OpenTelemetryPostSend") - pipeline.sendPipeline.insertPhaseAfter(ApplicationSendPipeline.After, postSendPhase) - pipeline.sendPipeline.intercept(postSendPhase) { - val context = call.attributes.getOrNull(contextKey) - if (context != null) { - var error: Throwable? = call.attributes.getOrNull(errorKey) - try { - proceed() - } catch (t: Throwable) { - error = t - throw t - } finally { - feature.end(context, call, error) - } - } else { + if (context != null) { + call.attributes.put(contextKey, context) + withContext(context.asContextElement()) { + try { proceed() + } catch (err: Throwable) { + // Stash error for reporting later since need ktor to finish setting up the response + call.attributes.put(errorKey, err) + throw err } } + } else { + proceed() + } + } - pipeline.environment.monitor.subscribe(Routing.RoutingCallStarted) { call -> - HttpServerRoute.update(Context.current(), HttpServerRouteSource.SERVER, { _, arg -> arg.route.parent.toString() }, call) + val postSendPhase = PipelinePhase("OpenTelemetryPostSend") + application.sendPipeline.insertPhaseAfter(ApplicationSendPipeline.After, postSendPhase) + application.sendPipeline.intercept(postSendPhase) { + val context = call.attributes.getOrNull(contextKey) + if (context != null) { + var error: Throwable? = call.attributes.getOrNull(errorKey) + try { + proceed() + } catch (t: Throwable) { + error = t + throw t + } finally { + tracer.end(context, call, error) } - - return feature + } else { + proceed() } } + + application.environment.monitor.subscribe(RoutingCallStarted) { call -> + HttpServerRoute.update(Context.current(), HttpServerRouteSource.SERVER, { _, arg -> arg.route.parent.toString() }, call) + } } diff --git a/instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/server/KtorTracer.kt b/instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/server/KtorTracer.kt new file mode 100644 index 000000000000..e239b2b7c99e --- /dev/null +++ b/instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/server/KtorTracer.kt @@ -0,0 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ktor.v2_0.server + +import io.ktor.server.application.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.opentelemetry.context.Context +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter + +internal class KtorTracer( + private val instrumenter: Instrumenter, +) { + fun start(call: ApplicationCall): Context? { + val parentContext = Context.current() + if (!instrumenter.shouldStart(parentContext, call.request)) { + return null + } + + return instrumenter.start(parentContext, call.request) + } + + fun end(context: Context, call: ApplicationCall, error: Throwable?) { + instrumenter.end(context, call.request, call.response, error) + } +} diff --git a/instrumentation/ktor/ktor-3.0/testing/build.gradle.kts b/instrumentation/ktor/ktor-3.0/testing/build.gradle.kts index d3fb3f3d0acc..ac2bae285ba6 100644 --- a/instrumentation/ktor/ktor-3.0/testing/build.gradle.kts +++ b/instrumentation/ktor/ktor-3.0/testing/build.gradle.kts @@ -19,6 +19,10 @@ dependencies { compileOnly("org.jetbrains.kotlin:kotlin-stdlib-jdk8") compileOnly("io.ktor:ktor-server-netty:$ktorVersion") compileOnly("io.ktor:ktor-client-cio:$ktorVersion") + + testImplementation(project(":instrumentation:ktor:ktor-2.0:library")) + testImplementation("io.ktor:ktor-server-netty:$ktorVersion") + testImplementation("io.ktor:ktor-client-cio:$ktorVersion") } kotlin { diff --git a/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/AbstractKtorHttpServerTest.kt b/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/AbstractKtorHttpServerTest.kt index 0f5ccacfac56..607e4427d5ef 100644 --- a/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/AbstractKtorHttpServerTest.kt +++ b/instrumentation/ktor/ktor-3.0/testing/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/AbstractKtorHttpServerTest.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.ktor.v3_0.server.io.opentelemetry.instrumentation.ktor.v3_0.server +package io.opentelemetry.instrumentation.ktor.v3_0.server import io.ktor.http.* import io.ktor.server.application.* diff --git a/instrumentation/ktor/ktor-3.0/testing/src/test/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/client/KtorHttpClientInstrumentationTest.kt b/instrumentation/ktor/ktor-3.0/testing/src/test/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/client/KtorHttpClientInstrumentationTest.kt index d32922a71e96..b92e8e20aea9 100644 --- a/instrumentation/ktor/ktor-3.0/testing/src/test/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/client/KtorHttpClientInstrumentationTest.kt +++ b/instrumentation/ktor/ktor-3.0/testing/src/test/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/client/KtorHttpClientInstrumentationTest.kt @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.ktor.v3_0.client import io.ktor.client.* +import io.opentelemetry.instrumentation.ktor.v2_0.client.KtorClientTracing import io.opentelemetry.instrumentation.testing.junit.http.HttpClientInstrumentationExtension import org.junit.jupiter.api.extension.RegisterExtension @@ -14,9 +15,14 @@ class KtorHttpClientInstrumentationTest : AbstractKtorHttpClientTest() { companion object { @JvmStatic @RegisterExtension - private val TESTING = HttpClientInstrumentationExtension.forAgent() + private val TESTING = HttpClientInstrumentationExtension.forLibrary() } override fun HttpClientConfig<*>.installTracing() { + install(KtorClientTracing) { + setOpenTelemetry(TESTING.openTelemetry) + capturedRequestHeaders(TEST_REQUEST_HEADER) + capturedResponseHeaders(TEST_RESPONSE_HEADER) + } } } diff --git a/instrumentation/ktor/ktor-3.0/testing/src/test/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/KtorHttpServerInstrumentationTest.kt b/instrumentation/ktor/ktor-3.0/testing/src/test/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/KtorHttpServerInstrumentationTest.kt index 164d6b2b5350..ed8c85615f76 100644 --- a/instrumentation/ktor/ktor-3.0/testing/src/test/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/KtorHttpServerInstrumentationTest.kt +++ b/instrumentation/ktor/ktor-3.0/testing/src/test/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/KtorHttpServerInstrumentationTest.kt @@ -6,7 +6,12 @@ package io.opentelemetry.instrumentation.ktor.v3_0.server import io.ktor.server.application.* -import io.opentelemetry.instrumentation.ktor.v3_0.server.io.opentelemetry.instrumentation.ktor.v3_0.server.AbstractKtorHttpServerTest +import io.ktor.server.routing.* +import io.ktor.server.routing.RoutingRoot.Plugin.RoutingCallStarted +import io.opentelemetry.context.Context +import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute +import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteSource +import io.opentelemetry.instrumentation.ktor.v2_0.server.KtorServerTracing import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions @@ -17,7 +22,7 @@ class KtorHttpServerInstrumentationTest : AbstractKtorHttpServerTest() { companion object { @JvmStatic @RegisterExtension - val TESTING: InstrumentationExtension = HttpServerInstrumentationExtension.forAgent() + val TESTING: InstrumentationExtension = HttpServerInstrumentationExtension.forLibrary() } override fun getTesting(): InstrumentationExtension { @@ -25,6 +30,13 @@ class KtorHttpServerInstrumentationTest : AbstractKtorHttpServerTest() { } override fun installOpenTelemetry(application: Application) { + application.apply { + install(KtorServerTracing) { + setOpenTelemetry(TESTING.openTelemetry) + capturedRequestHeaders(TEST_REQUEST_HEADER) + capturedResponseHeaders(TEST_RESPONSE_HEADER) + } + } } override fun configure(options: HttpServerTestOptions) { From 53ba03a0ebc8e3c94a654770e5d131c47599983c Mon Sep 17 00:00:00 2001 From: "leonid.stashevsky" Date: Thu, 31 Oct 2024 11:43:38 +0100 Subject: [PATCH 5/6] Workaround binary breaking change --- .../ktor/v2_0/server/KtorServerTracing.kt | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/server/KtorServerTracing.kt b/instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/server/KtorServerTracing.kt index 4a9763a979ef..e6dd2f3dfbcb 100644 --- a/instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/server/KtorServerTracing.kt +++ b/instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/server/KtorServerTracing.kt @@ -5,8 +5,8 @@ package io.opentelemetry.instrumentation.ktor.v2_0.server +import io.ktor.events.EventDefinition import io.ktor.http.* -import io.ktor.serialization.* import io.ktor.server.application.* import io.ktor.server.request.* import io.ktor.server.response.* @@ -260,7 +260,26 @@ val KtorServerTracing = createRouteScopedPlugin("OpenTelemetry", ::KtorServerTra } } - application.environment.monitor.subscribe(RoutingCallStarted) { call -> - HttpServerRoute.update(Context.current(), HttpServerRouteSource.SERVER, { _, arg -> arg.route.parent.toString() }, call) + val callStartedEvent = runCatching { + RoutingCallStarted // Ktor 2.0 + }.getOrElse { + // Ktor 3.0 + val routingRoot = Class.forName("io.ktor.server.routing.RoutingRoot") + val callStartedGetter = routingRoot.getDeclaredMethod("access\$getRoutingCallStarted\$cp") + callStartedGetter.invoke(null) as EventDefinition + } + + application.environment.monitor.subscribe(callStartedEvent) { call -> + HttpServerRoute.update(Context.current(), HttpServerRouteSource.SERVER, { _, arg -> + runCatching { + // Ktor 2.0 + (call as RoutingApplicationCall).route.parent.toString() + }.getOrElse { + // Ktor 3.0 + val route = arg::class.java.getDeclaredMethod("getRoute").invoke(arg) + val parent = route::class.java.getDeclaredMethod("getParent").invoke(route) + parent.toString() + } + }, call) } } From 4b7ea34b5cfbb70d9a0ae31d73c6b0288de06afd Mon Sep 17 00:00:00 2001 From: "leonid.stashevsky" Date: Thu, 31 Oct 2024 11:44:23 +0100 Subject: [PATCH 6/6] Fix codestyle --- .../instrumentation/ktor/v2_0/ServerInstrumentation.java | 6 +++--- .../ktor/v3_0/server/KtorHttpServerInstrumentationTest.kt | 4 ---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/instrumentation/ktor/ktor-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/ktor/v2_0/ServerInstrumentation.java b/instrumentation/ktor/ktor-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/ktor/v2_0/ServerInstrumentation.java index dc461ee56917..28745e3e6f2a 100644 --- a/instrumentation/ktor/ktor-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/ktor/v2_0/ServerInstrumentation.java +++ b/instrumentation/ktor/ktor-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/ktor/v2_0/ServerInstrumentation.java @@ -43,12 +43,12 @@ public static class ConstructorAdvice { @Advice.OnMethodExit public static void onExit(@Advice.FieldValue("_applicationInstance") Application application) { - ApplicationPluginKt.install(application, KtorServerTracingKt.getKtorServerTracing(), new SetupFunction()); + ApplicationPluginKt.install( + application, KtorServerTracingKt.getKtorServerTracing(), new SetupFunction()); } } - public static class SetupFunction - implements Function1 { + public static class SetupFunction implements Function1 { @Override public Unit invoke(KtorServerTracingBuilder configuration) { diff --git a/instrumentation/ktor/ktor-3.0/testing/src/test/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/KtorHttpServerInstrumentationTest.kt b/instrumentation/ktor/ktor-3.0/testing/src/test/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/KtorHttpServerInstrumentationTest.kt index ed8c85615f76..b51f97d331ff 100644 --- a/instrumentation/ktor/ktor-3.0/testing/src/test/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/KtorHttpServerInstrumentationTest.kt +++ b/instrumentation/ktor/ktor-3.0/testing/src/test/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/server/KtorHttpServerInstrumentationTest.kt @@ -7,10 +7,6 @@ package io.opentelemetry.instrumentation.ktor.v3_0.server import io.ktor.server.application.* import io.ktor.server.routing.* -import io.ktor.server.routing.RoutingRoot.Plugin.RoutingCallStarted -import io.opentelemetry.context.Context -import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute -import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteSource import io.opentelemetry.instrumentation.ktor.v2_0.server.KtorServerTracing import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension