Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add extension functions for Ktor plugins #10963

Merged
merged 5 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ public static class SetupFunction implements Function1<KtorClientTracingBuilder,
public Unit invoke(KtorClientTracingBuilder builder) {
OpenTelemetry openTelemetry = GlobalOpenTelemetry.get();
builder.setOpenTelemetry(openTelemetry);
builder.setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders());
builder.setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders());
builder.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods());
builder.capturedRequestHeaders(CommonConfig.get().getClientRequestHeaders());
builder.capturedResponseHeaders(CommonConfig.get().getClientResponseHeaders());
builder.knownMethods(CommonConfig.get().getKnownHttpRequestMethods());

return kotlin.Unit.INSTANCE;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ public static class SetupFunction
public Unit invoke(KtorServerTracing.Configuration configuration) {
OpenTelemetry openTelemetry = GlobalOpenTelemetry.get();
configuration.setOpenTelemetry(openTelemetry);
configuration.setCapturedRequestHeaders(CommonConfig.get().getServerRequestHeaders());
configuration.setCapturedResponseHeaders(CommonConfig.get().getServerResponseHeaders());
configuration.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods());
configuration.capturedRequestHeaders(CommonConfig.get().getServerRequestHeaders());
configuration.capturedResponseHeaders(CommonConfig.get().getServerResponseHeaders());
configuration.knownMethods(CommonConfig.get().getKnownHttpRequestMethods());

return kotlin.Unit.INSTANCE;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ package io.opentelemetry.instrumentation.ktor.v2_0.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.semconv.http.HttpClientExperimentalMetrics
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpExperimentalAttributesExtractor
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor
Expand Down Expand Up @@ -37,23 +40,104 @@ class KtorClientTracingBuilder {
httpAttributesExtractorBuilder.setCapturedRequestHeaders(headers)
}

fun capturedRequestHeaders(vararg headers: String) {
capturedRequestHeaders(headers.asIterable())
}

fun capturedRequestHeaders(headers: Iterable<String>) {
setCapturedRequestHeaders(headers.toList())
}

fun setCapturedResponseHeaders(vararg headers: String) = setCapturedResponseHeaders(headers.asList())

fun setCapturedResponseHeaders(headers: List<String>) {
httpAttributesExtractorBuilder.setCapturedResponseHeaders(headers)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marychatte would it make sense to deprecate the set*() methods?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thanks. I will do it


fun capturedResponseHeaders(vararg headers: String) {
capturedResponseHeaders(headers.asIterable())
}

fun capturedResponseHeaders(headers: Iterable<String>) {
setCapturedResponseHeaders(headers.toList())
}

fun setKnownMethods(knownMethods: Set<String>) {
httpAttributesExtractorBuilder.setKnownMethods(knownMethods)
httpSpanNameExtractorBuilder.setKnownMethods(knownMethods)
}

fun knownMethods(vararg methods: String) {
setKnownMethods(methods.toSet())
}

fun knownMethods(methods: Iterable<String>) {
setKnownMethods(methods.toSet())
}

fun knownMethods(vararg methods: HttpMethod) {
knownMethods(methods.asIterable())
}

@JvmName("knownMethodsJvm")
fun knownMethods(methods: Iterable<HttpMethod>) {
setKnownMethods(methods.map { it.value }.toSet())
}

fun addAttributesExtractors(vararg extractors: AttributesExtractor<in HttpRequestData, in HttpResponse>) = addAttributesExtractors(extractors.asList())

fun addAttributesExtractors(extractors: Iterable<AttributesExtractor<in HttpRequestData, in HttpResponse>>) {
additionalExtractors += extractors
}

fun attributeExtractor(extractorBuilder: ExtractorBuilder.() -> Unit = {}) {
val builder = ExtractorBuilder().apply(extractorBuilder).build()
addAttributesExtractors(
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?
)

/**
* Configures the instrumentation to emit experimental HTTP client metrics.
*
Expand All @@ -63,6 +147,10 @@ class KtorClientTracingBuilder {
this.emitExperimentalHttpClientMetrics = emitExperimentalHttpClientMetrics
}

fun emitExperimentalHttpClientMetrics() {
setEmitExperimentalHttpClientMetrics(true)
}

internal fun build(): KtorClientTracing {
val initializedOpenTelemetry = openTelemetry
?: throw IllegalArgumentException("OpenTelemetry must be set")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,22 @@

package io.opentelemetry.instrumentation.ktor.v2_0.server

import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.util.*
import io.ktor.util.pipeline.*
import io.opentelemetry.api.OpenTelemetry
import io.opentelemetry.api.common.AttributesBuilder
import io.opentelemetry.api.trace.SpanKind
import io.opentelemetry.context.Context
import io.opentelemetry.extension.kotlin.asContextElement
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.HttpServerAttributesExtractor
Expand Down Expand Up @@ -59,28 +63,135 @@ class KtorServerTracing private constructor(
this.statusExtractor = extractor
}

fun spanStatusExtractor(extract: SpanStatusData.() -> Unit) {
setStatusExtractor {
SpanStatusExtractor<ApplicationRequest, ApplicationResponse> { spanStatusBuilder: SpanStatusBuilder,
request: ApplicationRequest,
response: ApplicationResponse?,
throwable: Throwable? ->
extract(SpanStatusData(spanStatusBuilder, request, response, throwable))
}
}
}

data class SpanStatusData(
val spanStatusBuilder: SpanStatusBuilder,
val request: ApplicationRequest,
val response: ApplicationResponse?,
val error: Throwable?
)

fun setSpanKindExtractor(extractor: (SpanKindExtractor<ApplicationRequest>) -> SpanKindExtractor<ApplicationRequest>) {
this.spanKindExtractor = extractor
}

fun spanKindExtractor(extract: ApplicationRequest.() -> SpanKind) {
setSpanKindExtractor {
SpanKindExtractor<ApplicationRequest> { request: ApplicationRequest ->
extract(request)
}
}
}

fun addAttributeExtractor(extractor: AttributesExtractor<in ApplicationRequest, in ApplicationResponse>) {
additionalExtractors.add(extractor)
}

fun attributeExtractor(extractorBuilder: ExtractorBuilder.() -> Unit = {}) {
val builder = ExtractorBuilder().apply(extractorBuilder).build()
addAttributeExtractor(
object : AttributesExtractor<ApplicationRequest, ApplicationResponse> {
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))
}
}
)
}

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: ApplicationRequest
)

data class OnEndData(
val attributes: AttributesBuilder,
val parentContext: Context,
val request: ApplicationRequest,
val response: ApplicationResponse?,
val error: Throwable?
)

fun setCapturedRequestHeaders(requestHeaders: List<String>) {
httpAttributesExtractorBuilder.setCapturedRequestHeaders(requestHeaders)
}

fun capturedRequestHeaders(headers: Iterable<String>) {
setCapturedRequestHeaders(headers.toList())
}

fun capturedRequestHeaders(vararg headers: String) {
capturedRequestHeaders(headers.asIterable())
}

fun setCapturedResponseHeaders(responseHeaders: List<String>) {
httpAttributesExtractorBuilder.setCapturedResponseHeaders(responseHeaders)
}

fun capturedResponseHeaders(headers: Iterable<String>) {
setCapturedResponseHeaders(headers.toList())
}

fun capturedResponseHeaders(vararg headers: String) {
capturedResponseHeaders(headers.asIterable())
}

fun setKnownMethods(knownMethods: Set<String>) {
httpAttributesExtractorBuilder.setKnownMethods(knownMethods)
httpSpanNameExtractorBuilder.setKnownMethods(knownMethods)
httpServerRouteBuilder.setKnownMethods(knownMethods)
}

fun knownMethods(vararg methods: String) {
setKnownMethods(methods.toSet())
}

fun knownMethods(methods: Iterable<String>) {
setKnownMethods(methods.toSet())
}

@JvmName("knownMethodsJvm")
fun knownMethods(methods: Iterable<HttpMethod>) {
knownMethods(methods.map { it.value })
}

fun knownMethods(vararg methods: HttpMethod) {
knownMethods(methods.map { it.value })
}

internal fun isOpenTelemetryInitialized(): Boolean = this::openTelemetry.isInitialized
}

Expand All @@ -107,9 +218,7 @@ class KtorServerTracing private constructor(
override fun install(pipeline: Application, configure: Configuration.() -> Unit): KtorServerTracing {
val configuration = Configuration().apply(configure)

if (!configuration.isOpenTelemetryInitialized()) {
throw IllegalArgumentException("OpenTelemetry must be set")
}
require(configuration.isOpenTelemetryInitialized()) { "OpenTelemetry must be set" }

val httpAttributesGetter = KtorHttpServerAttributesGetter.INSTANCE

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ class KtorHttpClientTest : AbstractKtorHttpClientTest() {
override fun HttpClientConfig<*>.installTracing() {
install(KtorClientTracing) {
setOpenTelemetry(TESTING.openTelemetry)
setCapturedRequestHeaders(listOf(TEST_REQUEST_HEADER))
setCapturedResponseHeaders(listOf(TEST_RESPONSE_HEADER))
capturedRequestHeaders(TEST_REQUEST_HEADER)
capturedResponseHeaders(TEST_RESPONSE_HEADER)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.opentelemetry.api.trace.SpanKind
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension
import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerUsingTest
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension
Expand Down Expand Up @@ -60,13 +59,11 @@ class KtorServerSpanKindExtractorTest : AbstractHttpServerUsingTest<ApplicationE
return embeddedServer(Netty, port = port) {
install(KtorServerTracing) {
setOpenTelemetry(testing.openTelemetry)
setSpanKindExtractor {
SpanKindExtractor { req ->
if (req.uri.startsWith("/from-pubsub/")) {
SpanKind.CONSUMER
} else {
SpanKind.SERVER
}
spanKindExtractor {
if (uri.startsWith("/from-pubsub/")) {
SpanKind.CONSUMER
} else {
SpanKind.SERVER
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ class KtorTestUtil {
fun installOpenTelemetry(application: Application, openTelemetry: OpenTelemetry) {
application.install(KtorServerTracing) {
setOpenTelemetry(openTelemetry)
setCapturedRequestHeaders(listOf(AbstractHttpServerTest.TEST_REQUEST_HEADER))
setCapturedResponseHeaders(listOf(AbstractHttpServerTest.TEST_RESPONSE_HEADER))
capturedRequestHeaders(AbstractHttpServerTest.TEST_REQUEST_HEADER)
capturedResponseHeaders(AbstractHttpServerTest.TEST_RESPONSE_HEADER)
}
}
}
Expand Down
Loading