From b0ca09936611ce1079da699d8a6144f78f321735 Mon Sep 17 00:00:00 2001 From: Asish Kumar Date: Mon, 6 Apr 2026 00:08:09 +0530 Subject: [PATCH] fix(tracing): omit http.route span attribute when route has no paths Per the OpenTelemetry semantic conventions, the `http.route` attribute must not be set on the span when the route is not known. Previously, Kong emitted an empty string as the value of `http.route` when the matched route had no paths defined, violating the specification. Only set the `http.route` attribute when the route has an actual path value, and add a regression test to verify the attribute is omitted for host-only routes. Fix #14845 Signed-off-by: Asish Kumar --- .../kong/fix-tracing-empty-http-route.yml | 6 +++ .../observability/tracing/instrumentation.lua | 5 ++- .../01-instrumentations_spec.lua | 45 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 changelog/unreleased/kong/fix-tracing-empty-http-route.yml diff --git a/changelog/unreleased/kong/fix-tracing-empty-http-route.yml b/changelog/unreleased/kong/fix-tracing-empty-http-route.yml new file mode 100644 index 00000000000..04f09786a39 --- /dev/null +++ b/changelog/unreleased/kong/fix-tracing-empty-http-route.yml @@ -0,0 +1,6 @@ +message: > + **Tracing**: Fixed an issue where the `http.route` span attribute was set + to an empty string when the route had no paths, instead of being omitted + as required by the OpenTelemetry specification. +type: bugfix +scope: Core diff --git a/kong/observability/tracing/instrumentation.lua b/kong/observability/tracing/instrumentation.lua index a3432ced7fb..34bb9b24047 100644 --- a/kong/observability/tracing/instrumentation.lua +++ b/kong/observability/tracing/instrumentation.lua @@ -329,7 +329,10 @@ function _M.runloop_before_header_filter() if root_span then root_span:set_attribute("http.status_code", ngx.status) local r = ngx.ctx.route - root_span:set_attribute("http.route", r and r.paths and r.paths[1] or "") + local route_path = r and r.paths and r.paths[1] + if route_path then + root_span:set_attribute("http.route", route_path) + end end end diff --git a/spec/02-integration/14-observability/01-instrumentations_spec.lua b/spec/02-integration/14-observability/01-instrumentations_spec.lua index 0d9af192799..a06a6957be0 100644 --- a/spec/02-integration/14-observability/01-instrumentations_spec.lua +++ b/spec/02-integration/14-observability/01-instrumentations_spec.lua @@ -518,6 +518,51 @@ for _, strategy in helpers.each_strategy() do end) describe("#regression", function () + describe("http.route attribute omitted when route has no paths", function () + lazy_setup(function() + setup_instrumentations("all", false, function(bp) + local http_srv = bp.services:insert({ + name = "host-only-service", + host = helpers.mock_upstream_host, + port = helpers.mock_upstream_port, + }) + + bp.routes:insert({ + service = http_srv, + protocols = { "http" }, + hosts = { "no-paths-route.test" }, + }) + end) + end) + + lazy_teardown(function() + helpers.stop_kong() + end) + + it("does not set http.route when route has no paths", function () + local thread = helpers.tcp_server(TCP_PORT) + local r = assert(proxy_client:send { + method = "GET", + path = "/", + headers = { + host = "no-paths-route.test", + } + }) + assert.res_status(200, r) + + -- Getting back the TCP server input + local ok, res = thread:join() + assert.True(ok) + assert.is_string(res) + + local spans = cjson.decode(res) + local kong_span = assert_has_spans("kong", spans, 1)[1] + + -- http.route must not be set when route has no paths + assert.is_nil(kong_span.attributes["http.route"]) + end) + end) + describe("nil attribute for dns_query when fail to query", function () lazy_setup(function() setup_instrumentations("dns_query", true, function(bp)