- Elixir 37.2%
- Rust 31.7%
- Go 31.1%
|
|
||
|---|---|---|
| conformance | ||
| elixir | ||
| go | ||
| rust | ||
| .gitignore | ||
| CONTRACT.md | ||
| LICENSE | ||
| README.md | ||
serviceradar-sdk-otel
W3C trace-context propagation for messaging transports — small, per-language SDKs that keep your traces connected across NATS (and, next, Kafka) hops.
Why
OpenTelemetry auto-instrumentation covers HTTP and gRPC well: the client
injects traceparent, the server extracts it, and the trace stays whole.
Messaging hops are where traces silently fall apart — the producer's span
context never makes it into the message, so every consumer starts a fresh
root span and you get a pile of disconnected single-span traces instead of
one causal story.
For NATS specifically there is no official OTel instrumentation in any major language — no upstream package injects into or extracts from NATS message headers for you. ServiceRadar hit exactly this inside its own platform (every internal NATS hop produced orphan root spans) and fixed it with a small propagation layer. This repository packages that capability as a supported, standalone SDK for Go, Rust, and Elixir, so application, add-on, and edge authors get the same fix without reinventing it.
The carrier API concept
The core of each package is transport-agnostic: inject the active span
context into a string-keyed header map, extract a remote parent from
one. That header map is the carrier. Transports differ only in where the
carrier lives (NATS message headers, Kafka record headers, …), so adapters
are thin wrappers over the same two operations plus optional
producer/consumer span helpers with messaging.* semantic-convention
attributes.
The exact cross-language behavior (header keys, no-op/graceful-degradation
rules, span helper semantics) is specified in CONTRACT.md
and enforced by the shared vectors in
conformance/fixtures.json, which every
language's test suite consumes — inject in language A, extract in language
B, and the trace stays connected.
Status
Groundwork scaffold — APIs are usable but pre-release (0.1.0-dev),
nothing is published to a registry yet.
| Component | Status |
|---|---|
Behavior contract (CONTRACT.md) |
groundwork ✔ |
| Conformance fixtures | groundwork ✔ |
Go — otelmsg carrier core |
groundwork ✔ |
Go — natsotel NATS adapter |
groundwork ✔ |
| Rust — carrier core | groundwork ✔ |
Rust — nats feature (async-nats) |
groundwork ✔ |
Elixir — Propagation core |
groundwork ✔ |
| Elixir — Gnat adapter | groundwork ✔ |
| Publishing pipelines (hex / crates.io / Go tags) | not started |
| Kafka adapter | not started (see roadmap) |
Quickstarts
Go
Package otelmsg (carrier core) and natsotel (NATS adapter) live in one
module, github.com/carverauto/serviceradar-sdk-otel/go; the NATS
dependency is only pulled in if you import the adapter subpackage.
import (
"github.com/carverauto/serviceradar-sdk-otel/go" // package otelmsg
"github.com/carverauto/serviceradar-sdk-otel/go/natsotel"
"github.com/nats-io/nats.go"
)
// Producer: span + inject + publish in one call.
msg := &nats.Msg{Subject: "events.created", Data: payload}
err := natsotel.PublishMsg(ctx, nc, msg)
// Consumer: extract + consumer span around your handler.
sub, err := nc.Subscribe("events.created", natsotel.WrapMsgHandler(
func(ctx context.Context, msg *nats.Msg) error {
// ctx carries the producer's trace as remote parent
return handle(ctx, msg)
}))
// Or transport-free, with any header map:
carrier := otelmsg.MapCarrier{}
otelmsg.Inject(ctx, carrier) // adds traceparent/tracestate
ctx2 := otelmsg.Extract(ctx, carrier) // remote parent or unchanged ctx
Rust
Crate serviceradar-sdk-otel; enable the nats feature for the
async-nats adapter.
[dependencies]
serviceradar-sdk-otel = { version = "0.1.0-dev", features = ["nats"] }
use serviceradar_sdk_otel::{inject_context, extract, HeaderInjector, HeaderExtractor};
use serviceradar_sdk_otel::nats; // feature = "nats"
// Transport-free, over any HashMap<String, String>:
let mut headers = std::collections::HashMap::new();
inject_context(&cx, &mut HeaderInjector(&mut headers));
let parent_cx = extract(&HeaderExtractor(&headers));
// NATS adapter: async_nats::HeaderMap in/out + span builders.
let mut hm = async_nats::HeaderMap::new();
nats::inject_into_headers(&cx, &mut hm);
let parent_cx = nats::extract_from_headers(&hm);
let span = nats::producer_span_builder("events.created"); // messaging.* attrs preset
Elixir
Mix package serviceradar_sdk_otel. The Gnat adapter compiles only when
:gnat is present in your app (the dependency is optional).
alias ServiceRadar.SDK.Otel.Propagation
# Producer: stamp traceparent/tracestate onto NATS headers.
headers = Propagation.inject_headers([]) # list carrier (Gnat-style)
:ok = Gnat.pub(conn, "events.created", payload, headers: headers)
# Consumer: attach the remote parent before starting your span.
:ok = Propagation.extract_context(message.headers)
# Map carriers work too:
headers_map = Propagation.inject_map(%{})
# Optional span helpers with messaging.* semconv attributes:
alias ServiceRadar.SDK.Otel.MessagingSpan
MessagingSpan.with_producer_span("events.created", fn -> Gnat.pub(...) end)
MessagingSpan.with_consumer_span("events.created", fn -> handle(message) end)
ServiceRadar-ready defaults
The SDKs only propagate context; export is whatever your OTel SDK is
configured for. Against a ServiceRadar deployment, point your exporter at
the central collector — OTLP/gRPC on 4317 or OTLP/HTTP on 4318 — or at
the edge collector add-on's local endpoint
(http://localhost:4318 on the node running the add-on). With that wired,
a NATS producer/consumer pair using these SDKs shows up in the ServiceRadar
trace detail view as one multi-span trace.
Roadmap: Kafka
The carrier API was designed so a Kafka adapter (record headers are also a
string-keyed carrier) lands without breaking changes: same
inject/extract core, a kafkaotel/kafka-feature adapter with
messaging.system = "kafka" and topic-based destination naming. Tracked as
a follow-on task in the OpenSpec change below; not started here.
Provenance
This repository implements the OpenSpec change add-otel-messaging-sdk
in the ServiceRadar repository
(openspec/changes/add-otel-messaging-sdk/ — proposal and sdk-otel
spec). The Elixir core generalizes ServiceRadar's internal
ServiceRadar.Otel.Propagation module, which is the production-proven
reference implementation.
License
Apache-2.0 — see LICENSE.