RJ Garcia
08/29/2026, 4:09 AM(authentication: jwt) show up within the generated span name which gets really noisy. Here's an example span name / http.route value:
GET /(authenticate auth-x-forwarded-client-certs)/MTLSRouteVisibilitySelector(visibility=Private)/MTLSRouteVisibilitySelector(visibility=Public)/api/v1/users
ideally, this would be just /api/v1/users.
the ktor otel server plugin just simply transforms the route into a string which includes all route nodes regardless if that node doesn't actually change the route path itself.
application.monitor.subscribe(RoutingRoot.RoutingCallStarted) { call ->
HttpServerRoute.update(Context.current(), HttpServerRouteSource.SERVER, { _, arg -> arg!!.route.parent.toString() }, call)
}
After digging in a bit, I think some logic that filtered out routes where the selector wasn't apart of RoutePathComponent would do the trick.
Would a PR be accepted that would:
1. change the default implementation of the http.route lambda to filter out routing nodes with non RoutePathComponent selectors
2. and support a new configuration item in the plugin that accepts a lambda of (Call) -> String to get the http.route from the call to allow overriding in case people want different behavior?RJ Garcia
08/29/2026, 4:55 AMprivate fun Route.filteredPath(): String {
val parentPath = parent?.filteredPath()
if (selector !is RoutePathComponent) {
return parentPath ?: ""
}
return when (parentPath) {
null -> when (selector) {
is TrailingSlashRouteSelector -> "/"
else -> "/$selector"
}
else -> when (selector) {
is TrailingSlashRouteSelector -> if (parentPath.endsWith('/')) parentPath else "$parentPath/"
else -> if (parentPath.endsWith('/')) "$parentPath$selector" else "$parentPath/$selector"
}
}
}
I don't love the duplication of the existing Route.toString(), this feels like this could be an extension function just shipped within ktor itself which could be used in the otel instrumentationBruce Hamilton
08/29/2026, 10:01 AM/**
* String representation of the path matched by this route.
*
* [Report a problem](<https://ktor.io/feedback/?fqname=io.ktor.server.routing.path>)
*/
public val Route.path: String
get() = path()
/**
* Constructs the full path of the routing node by combining the path of the parent node
* and the formatted path of this node's selector, using the provided format.
*
* [Report a problem](<https://ktor.io/feedback/?fqname=io.ktor.server.routing.path>)
*
* @param format formats each selector in the node's lineage. Defaults to [RoutePathFormat.Default].
* @return the full path of the routing node as a string.
*/
public fun Route.path(format: RoutePathFormat = RoutePathFormat.Default): String {
val parentPath = parent?.path(format)
val pathComponent = selector as? RoutePathComponent ?: return parentPath.orEmpty()
val formattedPath = format.format(pathComponent)
return when {
parentPath == null -> formattedPath
formattedPath.isEmpty() -> parentPath
parentPath.endsWith('/') || formattedPath.startsWith('/') -> "$parentPath$formattedPath"
else -> "$parentPath/$formattedPath"
}
}
It would make sense to use this instead of toString() in the otel pluginRJ Garcia
08/29/2026, 1:19 PMBruce Hamilton
08/29/2026, 1:25 PMRJ Garcia
08/29/2026, 2:37 PMRJ Garcia
09/04/2026, 2:02 AMBruce Hamilton
09/04/2026, 6:36 AM