-
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
1,026 additions
and
29 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) | ||
} | ||
} |
Oops, something went wrong.