-
Notifications
You must be signed in to change notification settings - Fork 875
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rename ktor tracing to ktor telemetry
- Loading branch information
Showing
33 changed files
with
977 additions
and
30 deletions.
There are no files selected for viewing
169 changes: 169 additions & 0 deletions
169
...library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v1_0/KtorServerTelemetry.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.instrumentation.ktor.v1_0 | ||
|
||
import io.ktor.application.* | ||
import io.ktor.request.* | ||
import io.ktor.response.* | ||
import io.ktor.routing.* | ||
import io.ktor.util.* | ||
import io.ktor.util.pipeline.* | ||
import io.opentelemetry.api.OpenTelemetry | ||
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 | ||
import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil | ||
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute | ||
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteSource | ||
import kotlinx.coroutines.withContext | ||
|
||
class KtorServerTelemetry private constructor( | ||
private val instrumenter: Instrumenter<ApplicationRequest, ApplicationResponse>, | ||
) { | ||
|
||
class Configuration { | ||
internal lateinit var builder: DefaultHttpServerInstrumenterBuilder<ApplicationRequest, ApplicationResponse> | ||
|
||
internal var spanKindExtractor: | ||
(SpanKindExtractor<ApplicationRequest>) -> SpanKindExtractor<ApplicationRequest> = { a -> a } | ||
|
||
fun setOpenTelemetry(openTelemetry: OpenTelemetry) { | ||
this.builder = | ||
DefaultHttpServerInstrumenterBuilder.create( | ||
INSTRUMENTATION_NAME, | ||
openTelemetry, | ||
KtorHttpServerAttributesGetter.INSTANCE | ||
) | ||
} | ||
|
||
fun setStatusExtractor( | ||
extractor: (SpanStatusExtractor<in ApplicationRequest, in ApplicationResponse>) -> SpanStatusExtractor<in ApplicationRequest, in ApplicationResponse> | ||
) { | ||
builder.setStatusExtractor { prevExtractor -> | ||
SpanStatusExtractor { spanStatusBuilder: SpanStatusBuilder, | ||
request: ApplicationRequest, | ||
response: ApplicationResponse?, | ||
throwable: Throwable? -> | ||
extractor(prevExtractor).extract(spanStatusBuilder, request, response, throwable) | ||
} | ||
} | ||
} | ||
|
||
fun setSpanKindExtractor(extractor: (SpanKindExtractor<ApplicationRequest>) -> SpanKindExtractor<ApplicationRequest>) { | ||
this.spanKindExtractor = extractor | ||
} | ||
|
||
fun addAttributeExtractor(extractor: AttributesExtractor<in ApplicationRequest, in ApplicationResponse>) { | ||
builder.addAttributesExtractor(extractor) | ||
} | ||
|
||
fun setCapturedRequestHeaders(requestHeaders: List<String>) { | ||
builder.setCapturedRequestHeaders(requestHeaders) | ||
} | ||
|
||
fun setCapturedResponseHeaders(responseHeaders: List<String>) { | ||
builder.setCapturedResponseHeaders(responseHeaders) | ||
} | ||
|
||
fun setKnownMethods(knownMethods: Set<String>) { | ||
builder.setKnownMethods(knownMethods) | ||
} | ||
|
||
internal fun isOpenTelemetryInitialized(): Boolean = this::builder.isInitialized | ||
} | ||
|
||
private fun start(call: ApplicationCall): Context? { | ||
val parentContext = Context.current() | ||
if (!instrumenter.shouldStart(parentContext, call.request)) { | ||
return null | ||
} | ||
|
||
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 : ApplicationFeature<Application, Configuration, KtorServerTelemetry> { | ||
private const val INSTRUMENTATION_NAME = "io.opentelemetry.ktor-1.0" | ||
|
||
private val contextKey = AttributeKey<Context>("OpenTelemetry") | ||
private val errorKey = AttributeKey<Throwable>("OpenTelemetryException") | ||
|
||
override val key: AttributeKey<KtorServerTelemetry> = AttributeKey("OpenTelemetry") | ||
|
||
override fun install(pipeline: Application, configure: Configuration.() -> Unit): KtorServerTelemetry { | ||
val configuration = Configuration().apply(configure) | ||
|
||
if (!configuration.isOpenTelemetryInitialized()) { | ||
throw IllegalArgumentException("OpenTelemetry must be set") | ||
} | ||
|
||
val instrumenter = InstrumenterUtil.buildUpstreamInstrumenter( | ||
configuration.builder.instrumenterBuilder(), | ||
ApplicationRequestGetter, | ||
configuration.spanKindExtractor(SpanKindExtractor.alwaysServer()) | ||
) | ||
|
||
val feature = KtorServerTelemetry(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() | ||
} | ||
} | ||
|
||
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 { | ||
proceed() | ||
} | ||
} | ||
|
||
pipeline.environment.monitor.subscribe(Routing.RoutingCallStarted) { call -> | ||
val context = call.attributes.getOrNull(contextKey) | ||
if (context != null) { | ||
HttpServerRoute.update(context, HttpServerRouteSource.SERVER, { _, arg -> arg.route.parent.toString() }, call) | ||
} | ||
} | ||
|
||
return feature | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
...c/main/kotlin/io/opentelemetry/instrumentation/ktor/client/AbstractKtorClientTelemetry.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.instrumentation.ktor.client | ||
|
||
import io.ktor.client.call.* | ||
import io.ktor.client.request.* | ||
import io.ktor.client.statement.* | ||
import io.opentelemetry.context.Context | ||
import io.opentelemetry.context.propagation.ContextPropagators | ||
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter | ||
|
||
abstract class AbstractKtorClientTelemetry( | ||
private val instrumenter: Instrumenter<HttpRequestData, HttpResponse>, | ||
private val propagators: ContextPropagators, | ||
) { | ||
|
||
internal 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 | ||
} | ||
} | ||
|
||
internal fun populateRequestHeaders(requestBuilder: HttpRequestBuilder, context: Context) { | ||
propagators.textMapPropagator.inject(context, requestBuilder, KtorHttpHeadersSetter) | ||
} | ||
|
||
internal fun endSpan(context: Context, call: HttpClientCall, error: Throwable?) { | ||
endSpan(context, HttpRequestBuilder().takeFrom(call.request), call.response, error) | ||
} | ||
|
||
internal fun endSpan(context: Context, requestBuilder: HttpRequestBuilder, response: HttpResponse?, error: Throwable?) { | ||
instrumenter.end(context, requestBuilder.build(), response, error) | ||
} | ||
} |
121 changes: 121 additions & 0 deletions
121
...kotlin/io/opentelemetry/instrumentation/ktor/client/AbstractKtorClientTelemetryBuilder.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.instrumentation.ktor.client | ||
|
||
import io.ktor.client.request.* | ||
import io.ktor.client.statement.* | ||
import io.ktor.http.* | ||
import io.opentelemetry.api.OpenTelemetry | ||
import io.opentelemetry.api.common.AttributesBuilder | ||
import io.opentelemetry.context.Context | ||
import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpClientInstrumenterBuilder | ||
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor | ||
import io.opentelemetry.instrumentation.ktor.internal.KtorBuilderUtil | ||
|
||
abstract class AbstractKtorClientTelemetryBuilder( | ||
private val instrumentationName: String | ||
) { | ||
companion object { | ||
init { | ||
KtorBuilderUtil.clientBuilderExtractor = { it.clientBuilder } | ||
} | ||
} | ||
|
||
internal lateinit var openTelemetry: OpenTelemetry | ||
protected lateinit var clientBuilder: DefaultHttpClientInstrumenterBuilder<HttpRequestData, HttpResponse> | ||
|
||
fun setOpenTelemetry(openTelemetry: OpenTelemetry) { | ||
this.openTelemetry = openTelemetry | ||
this.clientBuilder = DefaultHttpClientInstrumenterBuilder.create( | ||
instrumentationName, | ||
openTelemetry, | ||
KtorHttpClientAttributesGetter | ||
) | ||
} | ||
|
||
protected fun getOpenTelemetry(): OpenTelemetry { | ||
return openTelemetry | ||
} | ||
|
||
fun capturedRequestHeaders(vararg headers: String) = capturedRequestHeaders(headers.asIterable()) | ||
|
||
fun capturedRequestHeaders(headers: Iterable<String>) { | ||
clientBuilder.setCapturedRequestHeaders(headers.toList()) | ||
} | ||
|
||
fun capturedResponseHeaders(vararg headers: String) = capturedResponseHeaders(headers.asIterable()) | ||
|
||
fun capturedResponseHeaders(headers: Iterable<String>) { | ||
clientBuilder.setCapturedResponseHeaders(headers.toList()) | ||
} | ||
|
||
fun knownMethods(vararg methods: String) = knownMethods(methods.asIterable()) | ||
|
||
fun knownMethods(vararg methods: HttpMethod) = knownMethods(methods.asIterable()) | ||
|
||
@JvmName("knownMethodsJvm") | ||
fun knownMethods(methods: Iterable<HttpMethod>) = knownMethods(methods.map { it.value }) | ||
|
||
fun knownMethods(methods: Iterable<String>) { | ||
clientBuilder.setKnownMethods(methods.toSet()) | ||
} | ||
|
||
fun attributeExtractor(extractorBuilder: ExtractorBuilder.() -> Unit = {}) { | ||
val builder = ExtractorBuilder().apply(extractorBuilder).build() | ||
this.clientBuilder.addAttributeExtractor( | ||
object : AttributesExtractor<HttpRequestData, HttpResponse> { | ||
override fun onStart(attributes: AttributesBuilder, parentContext: Context, request: HttpRequestData) { | ||
builder.onStart(OnStartData(attributes, parentContext, request)) | ||
} | ||
|
||
override fun onEnd(attributes: AttributesBuilder, context: Context, request: HttpRequestData, response: HttpResponse?, error: Throwable?) { | ||
builder.onEnd(OnEndData(attributes, context, request, response, error)) | ||
} | ||
} | ||
) | ||
} | ||
|
||
class ExtractorBuilder { | ||
private var onStart: OnStartData.() -> Unit = {} | ||
private var onEnd: OnEndData.() -> Unit = {} | ||
|
||
fun onStart(block: OnStartData.() -> Unit) { | ||
onStart = block | ||
} | ||
|
||
fun onEnd(block: OnEndData.() -> Unit) { | ||
onEnd = block | ||
} | ||
|
||
internal fun build(): Extractor { | ||
return Extractor(onStart, onEnd) | ||
} | ||
} | ||
|
||
internal class Extractor(val onStart: OnStartData.() -> Unit, val onEnd: OnEndData.() -> Unit) | ||
|
||
data class OnStartData( | ||
val attributes: AttributesBuilder, | ||
val parentContext: Context, | ||
val request: HttpRequestData | ||
) | ||
|
||
data class OnEndData( | ||
val attributes: AttributesBuilder, | ||
val parentContext: Context, | ||
val request: HttpRequestData, | ||
val response: HttpResponse?, | ||
val error: Throwable? | ||
) | ||
|
||
/** | ||
* Can be used via the unstable method {@link | ||
* Experimental#setEmitExperimentalHttpClientMetrics(AbstractKtorClientTelemetryBuilder, boolean)}. | ||
*/ | ||
internal fun emitExperimentalHttpClientMetrics() { | ||
clientBuilder.setEmitExperimentalHttpClientMetrics(true) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.