Add extension functions for Ktor plugins (#10963)

This commit is contained in:
Mariia Skripchenko 2024-05-17 00:33:23 +02:00 committed by GitHub
parent 692739a364
commit 10224db9d0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 265 additions and 43 deletions

View File

@ -56,9 +56,9 @@ public class HttpClientInstrumentation implements TypeInstrumentation {
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;
}

View File

@ -50,9 +50,9 @@ public class ServerInstrumentation implements TypeInstrumentation {
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;
}

View File

@ -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
@ -31,36 +34,137 @@ class KtorClientTracingBuilder {
this.openTelemetry = openTelemetry
}
fun setCapturedRequestHeaders(vararg headers: String) = setCapturedRequestHeaders(headers.asList())
@Deprecated(
"Please use method `capturedRequestHeaders`",
ReplaceWith("capturedRequestHeaders(headers.asIterable())")
)
fun setCapturedRequestHeaders(vararg headers: String) = capturedRequestHeaders(headers.asIterable())
fun setCapturedRequestHeaders(headers: List<String>) {
httpAttributesExtractorBuilder.setCapturedRequestHeaders(headers)
@Deprecated(
"Please use method `capturedRequestHeaders`",
ReplaceWith("capturedRequestHeaders(headers)")
)
fun setCapturedRequestHeaders(headers: List<String>) = capturedRequestHeaders(headers)
fun capturedRequestHeaders(vararg headers: String) = capturedRequestHeaders(headers.asIterable())
fun capturedRequestHeaders(headers: Iterable<String>) {
httpAttributesExtractorBuilder.setCapturedRequestHeaders(headers.toList())
}
fun setCapturedResponseHeaders(vararg headers: String) = setCapturedResponseHeaders(headers.asList())
@Deprecated(
"Please use method `capturedResponseHeaders`",
ReplaceWith("capturedResponseHeaders(headers.asIterable())")
)
fun setCapturedResponseHeaders(vararg headers: String) = capturedResponseHeaders(headers.asIterable())
fun setCapturedResponseHeaders(headers: List<String>) {
httpAttributesExtractorBuilder.setCapturedResponseHeaders(headers)
@Deprecated(
"Please use method `capturedResponseHeaders`",
ReplaceWith("capturedResponseHeaders(headers)")
)
fun setCapturedResponseHeaders(headers: List<String>) = capturedResponseHeaders(headers)
fun capturedResponseHeaders(vararg headers: String) = capturedResponseHeaders(headers.asIterable())
fun capturedResponseHeaders(headers: Iterable<String>) {
httpAttributesExtractorBuilder.setCapturedResponseHeaders(headers.toList())
}
fun setKnownMethods(knownMethods: Set<String>) {
httpAttributesExtractorBuilder.setKnownMethods(knownMethods)
httpSpanNameExtractorBuilder.setKnownMethods(knownMethods)
@Deprecated(
"Please use method `knownMethods`",
ReplaceWith("knownMethods(knownMethods)")
)
fun setKnownMethods(knownMethods: Set<String>) = knownMethods(knownMethods)
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>) {
methods.toSet().apply {
httpAttributesExtractorBuilder.setKnownMethods(this)
httpSpanNameExtractorBuilder.setKnownMethods(this)
}
}
@Deprecated("Please use method `attributeExtractor`")
fun addAttributesExtractors(vararg extractors: AttributesExtractor<in HttpRequestData, in HttpResponse>) = addAttributesExtractors(extractors.asList())
@Deprecated("Please use method `attributeExtractor`")
fun addAttributesExtractors(extractors: Iterable<AttributesExtractor<in HttpRequestData, in HttpResponse>>) {
additionalExtractors += extractors
extractors.forEach {
attributeExtractor {
onStart { it.onStart(attributes, parentContext, request) }
onEnd { it.onEnd(attributes, parentContext, request, response, error) }
}
}
}
fun attributeExtractor(extractorBuilder: ExtractorBuilder.() -> Unit = {}) {
val builder = ExtractorBuilder().apply(extractorBuilder).build()
additionalExtractors.add(
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.
*
* @param emitExperimentalHttpClientMetrics `true` if the experimental HTTP client metrics are to be emitted.
*/
@Deprecated("Please use method `emitExperimentalHttpClientMetrics`")
fun setEmitExperimentalHttpClientMetrics(emitExperimentalHttpClientMetrics: Boolean) {
this.emitExperimentalHttpClientMetrics = emitExperimentalHttpClientMetrics
if (emitExperimentalHttpClientMetrics) {
emitExperimentalHttpClientMetrics()
}
}
fun emitExperimentalHttpClientMetrics() {
emitExperimentalHttpClientMetrics = true
}
internal fun build(): KtorClientTracing {

View File

@ -5,6 +5,7 @@
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.*
@ -12,11 +13,14 @@ 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
@ -53,32 +57,151 @@ class KtorServerTracing private constructor(
this.openTelemetry = openTelemetry
}
@Deprecated("Please use method `spanStatusExtractor`")
fun setStatusExtractor(
extractor: (SpanStatusExtractor<ApplicationRequest, ApplicationResponse>) -> SpanStatusExtractor<in ApplicationRequest, in ApplicationResponse>
) {
this.statusExtractor = extractor
spanStatusExtractor { prevStatusExtractor ->
extractor(prevStatusExtractor).extract(spanStatusBuilder, request, response, error)
}
}
fun spanStatusExtractor(extract: SpanStatusData.(SpanStatusExtractor<ApplicationRequest, ApplicationResponse>) -> Unit) {
statusExtractor = { prevExtractor ->
SpanStatusExtractor<ApplicationRequest, ApplicationResponse> { spanStatusBuilder: SpanStatusBuilder,
request: ApplicationRequest,
response: ApplicationResponse?,
throwable: Throwable? ->
extract(SpanStatusData(spanStatusBuilder, request, response, throwable), prevExtractor)
}
}
}
data class SpanStatusData(
val spanStatusBuilder: SpanStatusBuilder,
val request: ApplicationRequest,
val response: ApplicationResponse?,
val error: Throwable?
)
@Deprecated("Please use method `spanKindExtractor`")
fun setSpanKindExtractor(extractor: (SpanKindExtractor<ApplicationRequest>) -> SpanKindExtractor<ApplicationRequest>) {
this.spanKindExtractor = extractor
spanKindExtractor { prevSpanKindExtractor ->
extractor(prevSpanKindExtractor).extract(this)
}
}
fun spanKindExtractor(extract: ApplicationRequest.(SpanKindExtractor<ApplicationRequest>) -> SpanKind) {
spanKindExtractor = { prevExtractor ->
SpanKindExtractor<ApplicationRequest> { request: ApplicationRequest ->
extract(request, prevExtractor)
}
}
}
@Deprecated("Please use method `attributeExtractor`")
fun addAttributeExtractor(extractor: AttributesExtractor<in ApplicationRequest, in ApplicationResponse>) {
additionalExtractors.add(extractor)
attributeExtractor {
onStart {
extractor.onStart(attributes, parentContext, request)
}
onEnd {
extractor.onEnd(attributes, parentContext, request, response, error)
}
}
}
fun setCapturedRequestHeaders(requestHeaders: List<String>) {
httpAttributesExtractorBuilder.setCapturedRequestHeaders(requestHeaders)
fun attributeExtractor(extractorBuilder: ExtractorBuilder.() -> Unit = {}) {
val builder = ExtractorBuilder().apply(extractorBuilder).build()
additionalExtractors.add(
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))
}
}
)
}
fun setCapturedResponseHeaders(responseHeaders: List<String>) {
httpAttributesExtractorBuilder.setCapturedResponseHeaders(responseHeaders)
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)
}
}
fun setKnownMethods(knownMethods: Set<String>) {
httpAttributesExtractorBuilder.setKnownMethods(knownMethods)
httpSpanNameExtractorBuilder.setKnownMethods(knownMethods)
httpServerRouteBuilder.setKnownMethods(knownMethods)
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?
)
@Deprecated(
"Please use method `capturedRequestHeaders`",
ReplaceWith("capturedRequestHeaders(headers)")
)
fun setCapturedRequestHeaders(headers: List<String>) = capturedRequestHeaders(headers)
fun capturedRequestHeaders(vararg headers: String) = capturedRequestHeaders(headers.asIterable())
fun capturedRequestHeaders(headers: Iterable<String>) {
httpAttributesExtractorBuilder.setCapturedRequestHeaders(headers.toList())
}
@Deprecated(
"Please use method `capturedResponseHeaders`",
ReplaceWith("capturedResponseHeaders(headers)")
)
fun setCapturedResponseHeaders(headers: List<String>) = capturedResponseHeaders(headers)
fun capturedResponseHeaders(vararg headers: String) = capturedResponseHeaders(headers.asIterable())
fun capturedResponseHeaders(headers: Iterable<String>) {
httpAttributesExtractorBuilder.setCapturedResponseHeaders(headers.toList())
}
@Deprecated(
"Please use method `knownMethods`",
ReplaceWith("knownMethods(knownMethods)")
)
fun setKnownMethods(knownMethods: Set<String>) = knownMethods(knownMethods)
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>) {
methods.toSet().apply {
httpAttributesExtractorBuilder.setKnownMethods(this)
httpSpanNameExtractorBuilder.setKnownMethods(this)
httpServerRouteBuilder.setKnownMethods(this)
}
}
internal fun isOpenTelemetryInitialized(): Boolean = this::openTelemetry.isInitialized
@ -107,9 +230,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

View File

@ -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)
}
}
}

View File

@ -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
@ -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
}
}
}

View File

@ -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)
}
}
}