fix(telemetry): map TraceCategory to OTel SpanKind in SpanGuard::span()

SpanGuard::span() hardcoded SpanKind::kInternal for every span. Tempo's
service-graph and spanmetrics RED calculations rely on kServer /
kConsumer / kClient / kProducer to classify inbound vs outbound vs
internal operations. With kInternal everywhere, the service graph
collapses to a single self-loop and RED metrics attribute all latency
to internal work.

Add categoryToSpanKind() mapping:
  - Rpc           -> kServer   (inbound synchronous request)
  - Peer          -> kConsumer (inbound async peer message)
  - Transactions  -> kInternal
  - Consensus     -> kInternal
  - Ledger        -> kInternal

Only the single-argument overload is affected; childSpan / linkedSpan
continue to default to kInternal because they represent in-process
continuations of an already-kinded parent.
This commit is contained in:
Pratik Mankawde
2026-05-14 15:53:59 +01:00
parent e21e7b0d51
commit 7ada57e2a8

View File

@@ -132,6 +132,32 @@ isCategoryEnabled(Telemetry const& tel, TraceCategory cat)
return false; // unreachable, silences compiler warning
}
namespace {
// Map a TraceCategory to an OTel SpanKind so Tempo's service-graph /
// RED metrics see the correct direction. RPC spans are emitted at the
// server entry point (handler dispatch), Peer spans at inbound-message
// receipt. Transactions / Consensus / Ledger are internal processing
// and keep the default kInternal.
otel_trace::SpanKind
categoryToSpanKind(TraceCategory cat)
{
switch (cat)
{
case TraceCategory::Rpc:
return otel_trace::SpanKind::kServer;
case TraceCategory::Peer:
return otel_trace::SpanKind::kConsumer;
case TraceCategory::Transactions:
case TraceCategory::Consensus:
case TraceCategory::Ledger:
return otel_trace::SpanKind::kInternal;
}
return otel_trace::SpanKind::kInternal; // unreachable
}
} // namespace
SpanGuard
SpanGuard::span(TraceCategory cat, std::string_view prefix, std::string_view name)
{
@@ -139,7 +165,7 @@ SpanGuard::span(TraceCategory cat, std::string_view prefix, std::string_view nam
if (!tel || !tel->isEnabled() || !isCategoryEnabled(*tel, cat))
return {};
auto fullName = std::string(prefix) + "." + std::string(name);
return SpanGuard(std::make_unique<Impl>(tel->startSpan(fullName)));
return SpanGuard(std::make_unique<Impl>(tel->startSpan(fullName, categoryToSpanKind(cat))));
}
// ===== Child / linked span creation ========================================