Skip to content

Commit 3defa4a

Browse files
committed
Rename ktor tracing to ktor telemetry
1 parent 4af0dd5 commit 3defa4a

File tree

33 files changed

+977
-30
lines changed

33 files changed

+977
-30
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.ktor.v1_0
7+
8+
import io.ktor.application.*
9+
import io.ktor.request.*
10+
import io.ktor.response.*
11+
import io.ktor.routing.*
12+
import io.ktor.util.*
13+
import io.ktor.util.pipeline.*
14+
import io.opentelemetry.api.OpenTelemetry
15+
import io.opentelemetry.context.Context
16+
import io.opentelemetry.extension.kotlin.asContextElement
17+
import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpServerInstrumenterBuilder
18+
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor
19+
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter
20+
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor
21+
import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusBuilder
22+
import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor
23+
import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil
24+
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute
25+
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteSource
26+
import kotlinx.coroutines.withContext
27+
28+
class KtorServerTelemetry private constructor(
29+
private val instrumenter: Instrumenter<ApplicationRequest, ApplicationResponse>,
30+
) {
31+
32+
class Configuration {
33+
internal lateinit var builder: DefaultHttpServerInstrumenterBuilder<ApplicationRequest, ApplicationResponse>
34+
35+
internal var spanKindExtractor:
36+
(SpanKindExtractor<ApplicationRequest>) -> SpanKindExtractor<ApplicationRequest> = { a -> a }
37+
38+
fun setOpenTelemetry(openTelemetry: OpenTelemetry) {
39+
this.builder =
40+
DefaultHttpServerInstrumenterBuilder.create(
41+
INSTRUMENTATION_NAME,
42+
openTelemetry,
43+
KtorHttpServerAttributesGetter.INSTANCE
44+
)
45+
}
46+
47+
fun setStatusExtractor(
48+
extractor: (SpanStatusExtractor<in ApplicationRequest, in ApplicationResponse>) -> SpanStatusExtractor<in ApplicationRequest, in ApplicationResponse>
49+
) {
50+
builder.setStatusExtractor { prevExtractor ->
51+
SpanStatusExtractor { spanStatusBuilder: SpanStatusBuilder,
52+
request: ApplicationRequest,
53+
response: ApplicationResponse?,
54+
throwable: Throwable? ->
55+
extractor(prevExtractor).extract(spanStatusBuilder, request, response, throwable)
56+
}
57+
}
58+
}
59+
60+
fun setSpanKindExtractor(extractor: (SpanKindExtractor<ApplicationRequest>) -> SpanKindExtractor<ApplicationRequest>) {
61+
this.spanKindExtractor = extractor
62+
}
63+
64+
fun addAttributeExtractor(extractor: AttributesExtractor<in ApplicationRequest, in ApplicationResponse>) {
65+
builder.addAttributesExtractor(extractor)
66+
}
67+
68+
fun setCapturedRequestHeaders(requestHeaders: List<String>) {
69+
builder.setCapturedRequestHeaders(requestHeaders)
70+
}
71+
72+
fun setCapturedResponseHeaders(responseHeaders: List<String>) {
73+
builder.setCapturedResponseHeaders(responseHeaders)
74+
}
75+
76+
fun setKnownMethods(knownMethods: Set<String>) {
77+
builder.setKnownMethods(knownMethods)
78+
}
79+
80+
internal fun isOpenTelemetryInitialized(): Boolean = this::builder.isInitialized
81+
}
82+
83+
private fun start(call: ApplicationCall): Context? {
84+
val parentContext = Context.current()
85+
if (!instrumenter.shouldStart(parentContext, call.request)) {
86+
return null
87+
}
88+
89+
return instrumenter.start(parentContext, call.request)
90+
}
91+
92+
private fun end(context: Context, call: ApplicationCall, error: Throwable?) {
93+
instrumenter.end(context, call.request, call.response, error)
94+
}
95+
96+
companion object Feature : ApplicationFeature<Application, Configuration, KtorServerTelemetry> {
97+
private const val INSTRUMENTATION_NAME = "io.opentelemetry.ktor-1.0"
98+
99+
private val contextKey = AttributeKey<Context>("OpenTelemetry")
100+
private val errorKey = AttributeKey<Throwable>("OpenTelemetryException")
101+
102+
override val key: AttributeKey<KtorServerTelemetry> = AttributeKey("OpenTelemetry")
103+
104+
override fun install(pipeline: Application, configure: Configuration.() -> Unit): KtorServerTelemetry {
105+
val configuration = Configuration().apply(configure)
106+
107+
if (!configuration.isOpenTelemetryInitialized()) {
108+
throw IllegalArgumentException("OpenTelemetry must be set")
109+
}
110+
111+
val instrumenter = InstrumenterUtil.buildUpstreamInstrumenter(
112+
configuration.builder.instrumenterBuilder(),
113+
ApplicationRequestGetter,
114+
configuration.spanKindExtractor(SpanKindExtractor.alwaysServer())
115+
)
116+
117+
val feature = KtorServerTelemetry(instrumenter)
118+
119+
val startPhase = PipelinePhase("OpenTelemetry")
120+
pipeline.insertPhaseBefore(ApplicationCallPipeline.Monitoring, startPhase)
121+
pipeline.intercept(startPhase) {
122+
val context = feature.start(call)
123+
124+
if (context != null) {
125+
call.attributes.put(contextKey, context)
126+
withContext(context.asContextElement()) {
127+
try {
128+
proceed()
129+
} catch (err: Throwable) {
130+
// Stash error for reporting later since need ktor to finish setting up the response
131+
call.attributes.put(errorKey, err)
132+
throw err
133+
}
134+
}
135+
} else {
136+
proceed()
137+
}
138+
}
139+
140+
val postSendPhase = PipelinePhase("OpenTelemetryPostSend")
141+
pipeline.sendPipeline.insertPhaseAfter(ApplicationSendPipeline.After, postSendPhase)
142+
pipeline.sendPipeline.intercept(postSendPhase) {
143+
val context = call.attributes.getOrNull(contextKey)
144+
if (context != null) {
145+
var error: Throwable? = call.attributes.getOrNull(errorKey)
146+
try {
147+
proceed()
148+
} catch (t: Throwable) {
149+
error = t
150+
throw t
151+
} finally {
152+
feature.end(context, call, error)
153+
}
154+
} else {
155+
proceed()
156+
}
157+
}
158+
159+
pipeline.environment.monitor.subscribe(Routing.RoutingCallStarted) { call ->
160+
val context = call.attributes.getOrNull(contextKey)
161+
if (context != null) {
162+
HttpServerRoute.update(context, HttpServerRouteSource.SERVER, { _, arg -> arg.route.parent.toString() }, call)
163+
}
164+
}
165+
166+
return feature
167+
}
168+
}
169+
}

instrumentation/ktor/ktor-1.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v1_0/KtorServerTracing.kt

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute
2525
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteSource
2626
import kotlinx.coroutines.withContext
2727

28+
@Deprecated("Use KtorServerTelemetry instead", ReplaceWith("KtorServerTelemetry"))
2829
class KtorServerTracing private constructor(
2930
private val instrumenter: Instrumenter<ApplicationRequest, ApplicationResponse>,
3031
) {

instrumentation/ktor/ktor-1.0/library/src/test/kotlin/io/opentelemetry/instrumentation/ktor/v1_0/KtorServerSpanKindExtractorTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class KtorServerSpanKindExtractorTest : AbstractHttpServerUsingTest<ApplicationE
5858

5959
override fun setupServer(): ApplicationEngine {
6060
return embeddedServer(Netty, port = port) {
61-
install(KtorServerTracing) {
61+
install(KtorServerTelemetry) {
6262
setOpenTelemetry(testing.openTelemetry)
6363
setSpanKindExtractor {
6464
SpanKindExtractor { req ->

instrumentation/ktor/ktor-1.0/library/src/test/kotlin/io/opentelemetry/instrumentation/ktor/v1_0/KtorTestUtil.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTes
1212
class KtorTestUtil {
1313
companion object {
1414
fun installOpenTelemetry(application: Application, openTelemetry: OpenTelemetry) {
15-
application.install(KtorServerTracing) {
15+
application.install(KtorServerTelemetry) {
1616
setOpenTelemetry(openTelemetry)
1717
setCapturedRequestHeaders(listOf(AbstractHttpServerTest.TEST_REQUEST_HEADER))
1818
setCapturedResponseHeaders(listOf(AbstractHttpServerTest.TEST_RESPONSE_HEADER))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.ktor.client
7+
8+
import io.ktor.client.call.*
9+
import io.ktor.client.request.*
10+
import io.ktor.client.statement.*
11+
import io.opentelemetry.context.Context
12+
import io.opentelemetry.context.propagation.ContextPropagators
13+
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter
14+
15+
abstract class AbstractKtorClientTelemetry(
16+
private val instrumenter: Instrumenter<HttpRequestData, HttpResponse>,
17+
private val propagators: ContextPropagators,
18+
) {
19+
20+
internal fun createSpan(requestBuilder: HttpRequestBuilder): Context? {
21+
val parentContext = Context.current()
22+
val requestData = requestBuilder.build()
23+
24+
return if (instrumenter.shouldStart(parentContext, requestData)) {
25+
instrumenter.start(parentContext, requestData)
26+
} else {
27+
null
28+
}
29+
}
30+
31+
internal fun populateRequestHeaders(requestBuilder: HttpRequestBuilder, context: Context) {
32+
propagators.textMapPropagator.inject(context, requestBuilder, KtorHttpHeadersSetter)
33+
}
34+
35+
internal fun endSpan(context: Context, call: HttpClientCall, error: Throwable?) {
36+
endSpan(context, HttpRequestBuilder().takeFrom(call.request), call.response, error)
37+
}
38+
39+
internal fun endSpan(context: Context, requestBuilder: HttpRequestBuilder, response: HttpResponse?, error: Throwable?) {
40+
instrumenter.end(context, requestBuilder.build(), response, error)
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.ktor.client
7+
8+
import io.ktor.client.request.*
9+
import io.ktor.client.statement.*
10+
import io.ktor.http.*
11+
import io.opentelemetry.api.OpenTelemetry
12+
import io.opentelemetry.api.common.AttributesBuilder
13+
import io.opentelemetry.context.Context
14+
import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpClientInstrumenterBuilder
15+
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor
16+
import io.opentelemetry.instrumentation.ktor.internal.KtorBuilderUtil
17+
18+
abstract class AbstractKtorClientTelemetryBuilder(
19+
private val instrumentationName: String
20+
) {
21+
companion object {
22+
init {
23+
KtorBuilderUtil.clientBuilderExtractor = { it.clientBuilder }
24+
}
25+
}
26+
27+
internal lateinit var openTelemetry: OpenTelemetry
28+
protected lateinit var clientBuilder: DefaultHttpClientInstrumenterBuilder<HttpRequestData, HttpResponse>
29+
30+
fun setOpenTelemetry(openTelemetry: OpenTelemetry) {
31+
this.openTelemetry = openTelemetry
32+
this.clientBuilder = DefaultHttpClientInstrumenterBuilder.create(
33+
instrumentationName,
34+
openTelemetry,
35+
KtorHttpClientAttributesGetter
36+
)
37+
}
38+
39+
protected fun getOpenTelemetry(): OpenTelemetry {
40+
return openTelemetry
41+
}
42+
43+
fun capturedRequestHeaders(vararg headers: String) = capturedRequestHeaders(headers.asIterable())
44+
45+
fun capturedRequestHeaders(headers: Iterable<String>) {
46+
clientBuilder.setCapturedRequestHeaders(headers.toList())
47+
}
48+
49+
fun capturedResponseHeaders(vararg headers: String) = capturedResponseHeaders(headers.asIterable())
50+
51+
fun capturedResponseHeaders(headers: Iterable<String>) {
52+
clientBuilder.setCapturedResponseHeaders(headers.toList())
53+
}
54+
55+
fun knownMethods(vararg methods: String) = knownMethods(methods.asIterable())
56+
57+
fun knownMethods(vararg methods: HttpMethod) = knownMethods(methods.asIterable())
58+
59+
@JvmName("knownMethodsJvm")
60+
fun knownMethods(methods: Iterable<HttpMethod>) = knownMethods(methods.map { it.value })
61+
62+
fun knownMethods(methods: Iterable<String>) {
63+
clientBuilder.setKnownMethods(methods.toSet())
64+
}
65+
66+
fun attributeExtractor(extractorBuilder: ExtractorBuilder.() -> Unit = {}) {
67+
val builder = ExtractorBuilder().apply(extractorBuilder).build()
68+
this.clientBuilder.addAttributeExtractor(
69+
object : AttributesExtractor<HttpRequestData, HttpResponse> {
70+
override fun onStart(attributes: AttributesBuilder, parentContext: Context, request: HttpRequestData) {
71+
builder.onStart(OnStartData(attributes, parentContext, request))
72+
}
73+
74+
override fun onEnd(attributes: AttributesBuilder, context: Context, request: HttpRequestData, response: HttpResponse?, error: Throwable?) {
75+
builder.onEnd(OnEndData(attributes, context, request, response, error))
76+
}
77+
}
78+
)
79+
}
80+
81+
class ExtractorBuilder {
82+
private var onStart: OnStartData.() -> Unit = {}
83+
private var onEnd: OnEndData.() -> Unit = {}
84+
85+
fun onStart(block: OnStartData.() -> Unit) {
86+
onStart = block
87+
}
88+
89+
fun onEnd(block: OnEndData.() -> Unit) {
90+
onEnd = block
91+
}
92+
93+
internal fun build(): Extractor {
94+
return Extractor(onStart, onEnd)
95+
}
96+
}
97+
98+
internal class Extractor(val onStart: OnStartData.() -> Unit, val onEnd: OnEndData.() -> Unit)
99+
100+
data class OnStartData(
101+
val attributes: AttributesBuilder,
102+
val parentContext: Context,
103+
val request: HttpRequestData
104+
)
105+
106+
data class OnEndData(
107+
val attributes: AttributesBuilder,
108+
val parentContext: Context,
109+
val request: HttpRequestData,
110+
val response: HttpResponse?,
111+
val error: Throwable?
112+
)
113+
114+
/**
115+
* Can be used via the unstable method {@link
116+
* Experimental#setEmitExperimentalHttpClientMetrics(AbstractKtorClientTelemetryBuilder, boolean)}.
117+
*/
118+
internal fun emitExperimentalHttpClientMetrics() {
119+
clientBuilder.setEmitExperimentalHttpClientMetrics(true)
120+
}
121+
}

instrumentation/ktor/ktor-2-common/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/client/AbstractKtorClientTracing.kt

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import io.opentelemetry.context.Context
1212
import io.opentelemetry.context.propagation.ContextPropagators
1313
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter
1414

15+
@Deprecated("Use AbstractKtorClientTelemetry instead", ReplaceWith("AbstractKtorClientTelemetry"))
1516
abstract class AbstractKtorClientTracing(
1617
private val instrumenter: Instrumenter<HttpRequestData, HttpResponse>,
1718
private val propagators: ContextPropagators,

0 commit comments

Comments
 (0)