Ktor client instrumentation (#7982)
Client implementation for Ktor 2.0. Resolves https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/4972. - Moved server instrumentation under `server` package - Implemented a plugin for ktor `HttpClient`
This commit is contained in:
parent
9a9a42b837
commit
3b4aeebd6d
|
@ -1,6 +1,6 @@
|
||||||
# Library Instrumentation for Ktor version 2.0 and higher
|
# Library Instrumentation for Ktor version 2.0 and higher
|
||||||
|
|
||||||
This package contains libraries to help instrument Ktor. Currently, only server instrumentation is supported.
|
This package contains libraries to help instrument Ktor. Server and client instrumentations are supported.
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ Initialize instrumentation by installing the `KtorServerTracing` feature. You mu
|
||||||
the feature.
|
the feature.
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
OpenTelemetry openTelemetry = initializeOpenTelemetryForMe()
|
val openTelemetry: OpenTelemetry = initializeOpenTelemetryForMe()
|
||||||
|
|
||||||
embeddedServer(Netty, 8080) {
|
embeddedServer(Netty, 8080) {
|
||||||
install(KtorServerTracing) {
|
install(KtorServerTracing) {
|
||||||
|
@ -43,3 +43,18 @@ embeddedServer(Netty, 8080) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Initializing client instrumentation
|
||||||
|
|
||||||
|
Initialize instrumentation by installing the `KtorClientTracing` feature. You must set the `OpenTelemetry` to use with
|
||||||
|
the feature.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val openTelemetry: OpenTelemetry = initializeOpenTelemetryForMe()
|
||||||
|
|
||||||
|
val client = HttpClient {
|
||||||
|
install(KtorClientTracing) {
|
||||||
|
setOpenTelemetry(openTelemetry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
@ -6,8 +6,11 @@ plugins {
|
||||||
id("org.jetbrains.kotlin.jvm")
|
id("org.jetbrains.kotlin.jvm")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val ktorVersion = "2.0.0"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
library("io.ktor:ktor-server-core:2.0.0")
|
library("io.ktor:ktor-client-core:$ktorVersion")
|
||||||
|
library("io.ktor:ktor-server-core:$ktorVersion")
|
||||||
|
|
||||||
implementation(project(":instrumentation:ktor:ktor-common:library"))
|
implementation(project(":instrumentation:ktor:ktor-common:library"))
|
||||||
implementation("io.opentelemetry:opentelemetry-extension-kotlin")
|
implementation("io.opentelemetry:opentelemetry-extension-kotlin")
|
||||||
|
@ -16,7 +19,8 @@ dependencies {
|
||||||
|
|
||||||
testImplementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
testImplementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||||
|
|
||||||
testLibrary("io.ktor:ktor-server-netty:2.0.0")
|
testLibrary("io.ktor:ktor-server-netty:$ktorVersion")
|
||||||
|
testLibrary("io.ktor:ktor-client-cio:$ktorVersion")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.ktor.v2_0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common properties for both client and server instrumentations
|
||||||
|
*/
|
||||||
|
internal object InstrumentationProperties {
|
||||||
|
|
||||||
|
internal const val INSTRUMENTATION_NAME = "io.opentelemetry.ktor-2.0"
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.ktor.v2_0.client
|
||||||
|
|
||||||
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.call.*
|
||||||
|
import io.ktor.client.plugins.*
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.client.statement.*
|
||||||
|
import io.ktor.util.*
|
||||||
|
import io.ktor.util.pipeline.*
|
||||||
|
import io.opentelemetry.context.Context
|
||||||
|
import io.opentelemetry.context.propagation.ContextPropagators
|
||||||
|
import io.opentelemetry.extension.kotlin.asContextElement
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class KtorClientTracing internal constructor(
|
||||||
|
private val instrumenter: Instrumenter<HttpRequestData, HttpResponse>,
|
||||||
|
private val propagators: ContextPropagators,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private fun createSpan(requestBuilder: HttpRequestBuilder): Context? {
|
||||||
|
val parentContext = Context.current()
|
||||||
|
val requestData = requestBuilder.build()
|
||||||
|
|
||||||
|
return if (instrumenter.shouldStart(parentContext, requestData)) {
|
||||||
|
instrumenter.start(parentContext, requestData)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun populateRequestHeaders(requestBuilder: HttpRequestBuilder, context: Context) {
|
||||||
|
propagators.textMapPropagator.inject(context, requestBuilder, KtorHttpHeadersSetter)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun endSpan(context: Context, call: HttpClientCall, error: Throwable?) {
|
||||||
|
endSpan(context, HttpRequestBuilder().takeFrom(call.request), call.response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun endSpan(context: Context, requestBuilder: HttpRequestBuilder, response: HttpResponse?, error: Throwable?) {
|
||||||
|
instrumenter.end(context, requestBuilder.build(), response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : HttpClientPlugin<KtorClientTracingBuilder, KtorClientTracing> {
|
||||||
|
|
||||||
|
private val openTelemetryContextKey = AttributeKey<Context>("OpenTelemetry")
|
||||||
|
|
||||||
|
override val key = AttributeKey<KtorClientTracing>("OpenTelemetry")
|
||||||
|
|
||||||
|
override fun prepare(block: KtorClientTracingBuilder.() -> Unit) = KtorClientTracingBuilder().apply(block).build()
|
||||||
|
|
||||||
|
override fun install(plugin: KtorClientTracing, scope: HttpClient) {
|
||||||
|
installSpanCreation(plugin, scope)
|
||||||
|
installSpanEnd(plugin, scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun installSpanCreation(plugin: KtorClientTracing, scope: HttpClient) {
|
||||||
|
val createSpanPhase = PipelinePhase("OpenTelemetryCreateSpan")
|
||||||
|
scope.sendPipeline.insertPhaseAfter(HttpSendPipeline.State, createSpanPhase)
|
||||||
|
|
||||||
|
scope.sendPipeline.intercept(createSpanPhase) {
|
||||||
|
val requestBuilder = context
|
||||||
|
val openTelemetryContext = plugin.createSpan(requestBuilder)
|
||||||
|
|
||||||
|
if (openTelemetryContext != null) {
|
||||||
|
try {
|
||||||
|
requestBuilder.attributes.put(openTelemetryContextKey, openTelemetryContext)
|
||||||
|
plugin.populateRequestHeaders(requestBuilder, openTelemetryContext)
|
||||||
|
|
||||||
|
withContext(openTelemetryContext.asContextElement()) { proceed() }
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
plugin.endSpan(openTelemetryContext, requestBuilder, null, e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
proceed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun installSpanEnd(plugin: KtorClientTracing, scope: HttpClient) {
|
||||||
|
val endSpanPhase = PipelinePhase("OpenTelemetryEndSpan")
|
||||||
|
scope.receivePipeline.insertPhaseBefore(HttpReceivePipeline.State, endSpanPhase)
|
||||||
|
|
||||||
|
scope.receivePipeline.intercept(endSpanPhase) {
|
||||||
|
val openTelemetryContext = it.call.attributes.getOrNull(openTelemetryContextKey)
|
||||||
|
|
||||||
|
if (openTelemetryContext != null) {
|
||||||
|
try {
|
||||||
|
withContext(openTelemetryContext.asContextElement()) { proceed() }
|
||||||
|
plugin.endSpan(openTelemetryContext, it.call, null)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
plugin.endSpan(openTelemetryContext, it.call, e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
proceed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.ktor.v2_0.client
|
||||||
|
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.client.statement.HttpResponse
|
||||||
|
import io.opentelemetry.api.OpenTelemetry
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor.alwaysClient
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor
|
||||||
|
import io.opentelemetry.instrumentation.ktor.v2_0.InstrumentationProperties.INSTRUMENTATION_NAME
|
||||||
|
|
||||||
|
class KtorClientTracingBuilder {
|
||||||
|
|
||||||
|
private var openTelemetry: OpenTelemetry? = null
|
||||||
|
private val additionalExtractors = mutableListOf<AttributesExtractor<in HttpRequestData, in HttpResponse>>()
|
||||||
|
private val httpAttributesExtractorBuilder = HttpClientAttributesExtractor.builder(
|
||||||
|
KtorHttpClientAttributesGetter,
|
||||||
|
KtorNetClientAttributesGetter,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun setOpenTelemetry(openTelemetry: OpenTelemetry) {
|
||||||
|
this.openTelemetry = openTelemetry
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setCapturedRequestHeaders(vararg headers: String) =
|
||||||
|
setCapturedRequestHeaders(headers.asList())
|
||||||
|
|
||||||
|
fun setCapturedRequestHeaders(headers: List<String>) {
|
||||||
|
httpAttributesExtractorBuilder.setCapturedRequestHeaders(headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setCapturedResponseHeaders(vararg headers: String) =
|
||||||
|
setCapturedResponseHeaders(headers.asList())
|
||||||
|
|
||||||
|
fun setCapturedResponseHeaders(headers: List<String>) {
|
||||||
|
httpAttributesExtractorBuilder.setCapturedResponseHeaders(headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addAttributesExtractors(vararg extractors: AttributesExtractor<in HttpRequestData, in HttpResponse>) =
|
||||||
|
addAttributesExtractors(extractors.asList())
|
||||||
|
|
||||||
|
fun addAttributesExtractors(extractors: Iterable<AttributesExtractor<in HttpRequestData, in HttpResponse>>) {
|
||||||
|
additionalExtractors += extractors
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun build(): KtorClientTracing {
|
||||||
|
val initializedOpenTelemetry = openTelemetry
|
||||||
|
?: throw IllegalArgumentException("OpenTelemetry must be set")
|
||||||
|
|
||||||
|
val instrumenterBuilder = Instrumenter.builder<HttpRequestData, HttpResponse>(
|
||||||
|
initializedOpenTelemetry,
|
||||||
|
INSTRUMENTATION_NAME,
|
||||||
|
HttpSpanNameExtractor.create(KtorHttpClientAttributesGetter),
|
||||||
|
)
|
||||||
|
|
||||||
|
val instrumenter = instrumenterBuilder
|
||||||
|
.setSpanStatusExtractor(HttpSpanStatusExtractor.create(KtorHttpClientAttributesGetter))
|
||||||
|
.addAttributesExtractor(httpAttributesExtractorBuilder.build())
|
||||||
|
.addAttributesExtractors(additionalExtractors)
|
||||||
|
.addOperationMetrics(HttpClientMetrics.get())
|
||||||
|
.buildInstrumenter(alwaysClient())
|
||||||
|
|
||||||
|
return KtorClientTracing(
|
||||||
|
instrumenter = instrumenter,
|
||||||
|
propagators = initializedOpenTelemetry.propagators,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.ktor.v2_0.client
|
||||||
|
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.client.statement.HttpResponse
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesGetter
|
||||||
|
|
||||||
|
internal object KtorHttpClientAttributesGetter : HttpClientAttributesGetter<HttpRequestData, HttpResponse> {
|
||||||
|
|
||||||
|
override fun getUrl(request: HttpRequestData) =
|
||||||
|
request.url.toString()
|
||||||
|
|
||||||
|
override fun getFlavor(request: HttpRequestData, response: HttpResponse?) =
|
||||||
|
null
|
||||||
|
|
||||||
|
override fun getMethod(request: HttpRequestData) =
|
||||||
|
request.method.value
|
||||||
|
|
||||||
|
override fun getRequestHeader(request: HttpRequestData, name: String) =
|
||||||
|
request.headers.getAll(name).orEmpty()
|
||||||
|
|
||||||
|
override fun getStatusCode(request: HttpRequestData, response: HttpResponse, error: Throwable?) =
|
||||||
|
response.status.value
|
||||||
|
|
||||||
|
override fun getResponseHeader(request: HttpRequestData, response: HttpResponse, name: String) =
|
||||||
|
response.headers.getAll(name).orEmpty()
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.ktor.v2_0.client
|
||||||
|
|
||||||
|
import io.ktor.client.request.HttpRequestBuilder
|
||||||
|
import io.opentelemetry.context.propagation.TextMapSetter
|
||||||
|
|
||||||
|
internal object KtorHttpHeadersSetter : TextMapSetter<HttpRequestBuilder> {
|
||||||
|
|
||||||
|
override fun set(carrier: HttpRequestBuilder?, key: String, value: String) {
|
||||||
|
carrier?.headers?.set(key, value)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.ktor.v2_0.client
|
||||||
|
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.client.statement.*
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesGetter
|
||||||
|
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NetTransportValues.IP_TCP
|
||||||
|
|
||||||
|
internal object KtorNetClientAttributesGetter : NetClientAttributesGetter<HttpRequestData, HttpResponse> {
|
||||||
|
|
||||||
|
override fun getTransport(request: HttpRequestData, response: HttpResponse?) = IP_TCP
|
||||||
|
|
||||||
|
override fun getPeerName(request: HttpRequestData) = request.url.host
|
||||||
|
|
||||||
|
override fun getPeerPort(request: HttpRequestData) = request.url.port
|
||||||
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.ktor.v2_0
|
package io.opentelemetry.instrumentation.ktor.v2_0.server
|
||||||
|
|
||||||
import io.ktor.server.request.*
|
import io.ktor.server.request.*
|
||||||
import io.opentelemetry.context.propagation.TextMapGetter
|
import io.opentelemetry.context.propagation.TextMapGetter
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.ktor.v2_0
|
package io.opentelemetry.instrumentation.ktor.v2_0.server
|
||||||
|
|
||||||
import io.ktor.server.plugins.*
|
import io.ktor.server.plugins.*
|
||||||
import io.ktor.server.request.*
|
import io.ktor.server.request.*
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.ktor.v2_0
|
package io.opentelemetry.instrumentation.ktor.v2_0.server
|
||||||
|
|
||||||
import io.ktor.server.request.*
|
import io.ktor.server.request.*
|
||||||
import io.opentelemetry.instrumentation.api.instrumenter.net.NetServerAttributesGetter
|
import io.opentelemetry.instrumentation.api.instrumenter.net.NetServerAttributesGetter
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.ktor.v2_0
|
package io.opentelemetry.instrumentation.ktor.v2_0.server
|
||||||
|
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import io.ktor.server.request.*
|
import io.ktor.server.request.*
|
||||||
|
@ -23,6 +23,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttribut
|
||||||
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerMetrics
|
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerMetrics
|
||||||
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor
|
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor
|
||||||
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor
|
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor
|
||||||
|
import io.opentelemetry.instrumentation.ktor.v2_0.InstrumentationProperties.INSTRUMENTATION_NAME
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class KtorServerTracing private constructor(
|
class KtorServerTracing private constructor(
|
||||||
|
@ -81,7 +82,6 @@ class KtorServerTracing private constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object Feature : BaseApplicationPlugin<Application, Configuration, KtorServerTracing> {
|
companion object Feature : BaseApplicationPlugin<Application, Configuration, KtorServerTracing> {
|
||||||
private val INSTRUMENTATION_NAME = "io.opentelemetry.ktor-2.0"
|
|
||||||
|
|
||||||
private val contextKey = AttributeKey<Context>("OpenTelemetry")
|
private val contextKey = AttributeKey<Context>("OpenTelemetry")
|
||||||
private val errorKey = AttributeKey<Throwable>("OpenTelemetryException")
|
private val errorKey = AttributeKey<Throwable>("OpenTelemetryException")
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.ktor.v2_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<String, String>) = 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.ktor.v2_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.HttpClientInstrumentationExtension
|
||||||
|
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.trace.attributes.SemanticAttributes.HTTP_FLAVOR
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import org.junit.jupiter.api.extension.RegisterExtension
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
class KtorHttpClientTest : AbstractHttpClientTest<HttpRequestBuilder>() {
|
||||||
|
|
||||||
|
override fun buildRequest(requestMethod: String, uri: URI, requestHeaders: MutableMap<String, String>) =
|
||||||
|
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<String, String>) = runBlocking {
|
||||||
|
CLIENT.request(request).status.value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sendRequestWithCallback(
|
||||||
|
request: HttpRequestBuilder,
|
||||||
|
method: String,
|
||||||
|
uri: URI,
|
||||||
|
headers: MutableMap<String, String>,
|
||||||
|
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) {
|
||||||
|
// this instrumentation creates a span per each physical request
|
||||||
|
// related issue https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/5722
|
||||||
|
disableTestRedirects()
|
||||||
|
|
||||||
|
setHttpAttributes { DEFAULT_HTTP_ATTRIBUTES - HTTP_FLAVOR }
|
||||||
|
|
||||||
|
setSingleConnectionFactory { host, port ->
|
||||||
|
KtorHttpClientSingleConnection(host, port) { installTracing() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
@RegisterExtension
|
||||||
|
private val TESTING = HttpClientInstrumentationExtension.forLibrary()
|
||||||
|
|
||||||
|
private val CLIENT = HttpClient(CIO) {
|
||||||
|
install(HttpRedirect)
|
||||||
|
|
||||||
|
installTracing()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun HttpClientConfig<*>.installTracing() {
|
||||||
|
install(KtorClientTracing) {
|
||||||
|
setOpenTelemetry(TESTING.openTelemetry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.ktor.v2_0
|
package io.opentelemetry.instrumentation.ktor.v2_0.server
|
||||||
|
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.ktor.v2_0
|
package io.opentelemetry.instrumentation.ktor.v2_0.server
|
||||||
|
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import io.opentelemetry.api.OpenTelemetry
|
import io.opentelemetry.api.OpenTelemetry
|
Loading…
Reference in New Issue