opentelemetry plugin emits no CLIENT span for the upstream call, so APISIX is disconnected on APM service maps #13828
Replies: 2 comments
|
One implementation detail matters for the service-map result: the CLIENT span needs to exist before the upstream request is sent, and its context needs to be injected into that request. Adding a child span retrospectively would leave the upstream SERVER span parented to the gateway SERVER span, so the missing edge could remain. In 3.17.0, the plugin injects the gateway span's context in For the normal NGINX proxy path, I'd start with this design:
I'd test the exported parent IDs as well as span kinds: That's a code-reading suggestion, not a tested patch or a statement about whether maintainers will accept the change. |


Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Summary
The
opentelemetryplugin emits exactly one span per request, withkind = span_kind.server, and never aCLIENTspan for the call APISIX makesto the upstream.
Tracing itself is unaffected — the waterfall is complete and correctly
parented. But APM tools that build a service map from
CLIENT → SERVERspanpairs cannot draw the gateway → upstream hop, so APISIX shows up as an island
next to the services it actually proxies to.
I would like to check whether adding such a span is something the project
would accept, and if so where the right place to start and finish it is.
What it looks like
Same application, same collector, same APM (HyperDX / ClickStack). Only the
gateway differs.
Fronted by APISIX — two disconnected clusters.
api-gatewayis joined tothe callers in front of it but not to
orders-servicebehind it.Fronted by Kong — one continuous chain, browser through to the backends.
Screenshots of both are in the comment below.
Why
Every cross-service parent/child pair in the trace data, with the span kinds:
The two missing edges are exactly the two whose parent is APISIX. Context
propagation is fine — the child spans exist and are correctly parented to the
gateway span. There is simply no
CLIENTspan for the proxy hop to anchor theedge to.
In the source
apisix/plugins/opentelemetry.lua, checked on 3.17.0 and on master — onerequest span:
span_kind.clientdoes not appear in the file at all.The internal tracer (
apisix/tracer.lua) can add child spans, but each one isserverorinternaland describes an APISIX phase rather than the upstreamcall:
The plugin schema exposes only
sampler,additional_attributesandadditional_header_prefix_attributes, so it cannot be enabled throughconfiguration either.
For comparison, Kong's OpenTelemetry plugin emits a
kong.balancerspan forthis hop, which is what makes the second screenshot connect end to end.
What I am proposing
A
CLIENTspan around the upstream request — started before proxying, finishedwhen the upstream response completes — as a child of the existing
SERVERspan, with the usual client attributes (
server.address,server.port,http.response.status_code, upstream/node identity).Besides fixing service maps, it would make the time APISIX spends waiting on
the upstream visible as its own span. Today that is only inferable from the gap
between the gateway span and the upstream service's server span.
I am happy to open a PR if the direction is agreeable. The part I would want
guidance on is which phase should own the span's start and finish
(
before_proxy/body_filter/log), given that the plugin currentlybuilds its child spans from
ngx.ctx.tracingatheader_filtertime.Environment
apache/apisixcontainer image, KubernetesAll reactions