From cc72c0ca7b41b437175adb928ac4629425857bc9 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 24 Aug 2026 13:58:11 +0200 Subject: [PATCH] fix(ktor): Exclude query parameters from span descriptions Parse Ktor client request URLs through the shared URL utility so span descriptions omit query parameters and fragments. Keep the raw URL for trace propagation and avoid introducing query span data. Refs #5666 Co-Authored-By: Claude --- .../ktorClient/SentryKtorClientPlugin.kt | 19 ++++++++++--------- .../ktorClient/SentryKtorClientPluginTest.kt | 10 ++++++++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/sentry-ktor-client/src/main/java/io/sentry/ktorClient/SentryKtorClientPlugin.kt b/sentry-ktor-client/src/main/java/io/sentry/ktorClient/SentryKtorClientPlugin.kt index 2f559f804fc..95cdfb5fdae 100644 --- a/sentry-ktor-client/src/main/java/io/sentry/ktorClient/SentryKtorClientPlugin.kt +++ b/sentry-ktor-client/src/main/java/io/sentry/ktorClient/SentryKtorClientPlugin.kt @@ -24,6 +24,7 @@ import io.sentry.util.Platform import io.sentry.util.PropagationTargetsUtils import io.sentry.util.SpanUtils import io.sentry.util.TracingUtils +import io.sentry.util.UrlUtils import kotlinx.coroutines.withContext /** Configuration for the Sentry Ktor client plugin. */ @@ -98,20 +99,20 @@ public val SentryKtorClientPlugin: ClientPlugin = val requestSpanKey = AttributeKey("SentryRequestSpan") onRequest { request, _ -> + val effectiveScopes = if (forceScopes) scopes else Sentry.getCurrentScopes() request.attributes.put( requestStartTimestampKey, - (if (forceScopes) scopes else Sentry.getCurrentScopes()).options.dateProvider.now(), + effectiveScopes.options.dateProvider.now(), ) val parentSpan: ISpan? = if (forceScopes) scopes.getSpan() - else { - val currentScopes = Sentry.getCurrentScopes() - if (Platform.isAndroid()) currentScopes.transaction else currentScopes.span - } + else if (Platform.isAndroid()) effectiveScopes.transaction else effectiveScopes.span val spanOp = "http.client" - val spanDescription = "${request.method.value.toString()} ${request.url.buildString()}" + val rawUrl = request.url.buildString() + val urlDetails = UrlUtils.parse(rawUrl, effectiveScopes.options.dataCollectionResolver) + val spanDescription = "${request.method.value.toString()} ${urlDetails.urlOrFallback}" val span: ISpan? = parentSpan?.startChild(spanOp, spanDescription) if (span != null) { span.spanContext.origin = TRACE_ORIGIN @@ -120,13 +121,13 @@ public val SentryKtorClientPlugin: ClientPlugin = if ( !SpanUtils.isIgnored( - (if (forceScopes) scopes else Sentry.getCurrentScopes()).options.getIgnoredSpanOrigins(), + effectiveScopes.options.getIgnoredSpanOrigins(), TRACE_ORIGIN, ) ) { TracingUtils.traceIfAllowed( - if (forceScopes) scopes else Sentry.getCurrentScopes(), - request.url.buildString(), + effectiveScopes, + rawUrl, request.headers.getAll(BaggageHeader.BAGGAGE_HEADER), span, ) diff --git a/sentry-ktor-client/src/test/java/io/sentry/ktorClient/SentryKtorClientPluginTest.kt b/sentry-ktor-client/src/test/java/io/sentry/ktorClient/SentryKtorClientPluginTest.kt index 8456f5658ee..38ffe609b2c 100644 --- a/sentry-ktor-client/src/test/java/io/sentry/ktorClient/SentryKtorClientPluginTest.kt +++ b/sentry-ktor-client/src/test/java/io/sentry/ktorClient/SentryKtorClientPluginTest.kt @@ -449,6 +449,16 @@ class SentryKtorClientPluginTest { assertTrue(httpClientSpan.isFinished) } + @Test + fun `span description excludes query parameters and fragment`(): Unit = runBlocking { + val sut = fixture.getSut() + sut.get(fixture.server.url("/hello?token=secret&page=1#results").toString()) + + val httpClientSpan = fixture.sentryTracer.children.first() + assertEquals("GET ${fixture.server.url("/hello")}", httpClientSpan.description) + assertNull(httpClientSpan.data[SpanDataConvention.HTTP_QUERY_KEY]) + } + @Test fun `finishes span setting throwable and status when request throws`(): Unit = runBlocking { val sut = fixture.getSut(socketPolicy = SocketPolicy.DISCONNECT_DURING_REQUEST_BODY)