otel_grpc 0.1.0-beta.1
otel_grpc: ^0.1.0-beta.1 copied to clipboard
OpenTelemetry instrumentation for `package:grpc`. Client and server interceptors that emit rpc.* spans, inject/extract W3C trace context via gRPC metadata, and honor a context-based suppression flag t [...]
dartastic_grpc_otel #
OpenTelemetry instrumentation for package:grpc,
built on the Dartastic OpenTelemetry SDK.
Two interceptors:
OTelGrpcClientInterceptor— attach to aClientChannelto get aCLIENT-kind span per outbound RPC, with W3C trace context injected into the call metadata.OTelGrpcServerInterceptor— pass.interceptinto aServer'sinterceptors:list to get aSERVER-kind span per inbound RPC, with W3C trace context extracted from the metadata so the span joins the caller's trace.
final channel = ClientChannel(
'api.example.com',
options: ChannelOptions(credentials: ChannelCredentials.secure()),
interceptors: [OTelGrpcClientInterceptor()],
);
final server = Server.create(
services: [MyService()],
interceptors: [OTelGrpcServerInterceptor().intercept],
);
⚠️ Self-recursion: don't instrument your OTLP/gRPC export channel #
gRPC export over OTLP is itself gRPC. If you put
OTelGrpcClientInterceptor on a ClientChannel that's also used
by your OTLP/gRPC exporter, every span you export creates another
span (the export call), which gets exported, which creates another
span — instrumentation recursion until you blow the stack or
saturate your backend.
You're safe by default — the dartastic SDK's built-in
OTLP/gRPC exporter creates its own private ClientChannel that
your interceptor isn't attached to. The risk only appears if you
manually wire OTLP export over a ClientChannel you control. If
that's your setup, do one of:
- Don't attach the interceptor to that channel. Use a separate, dedicated channel for OTLP traffic.
- Wrap export calls in the suppression helper:
The interceptor checks a zone-scoped flag and bails before creating the span. Sync variant:import 'package:dartastic_grpc_otel/dartastic_grpc_otel.dart'; await runWithoutGrpcInstrumentationAsync(() async { await myOtlpClient.export(spans); });runWithoutGrpcInstrumentation.
Span shape #
Per the OTel RPC semantic conventions and gRPC sub-spec.
| Attribute | Source | Set on |
|---|---|---|
rpc.system |
constant grpc |
client + server |
rpc.service |
service portion of ClientMethod.path |
client only (server's ServiceMethod doesn't carry the parent Service) |
rpc.method |
method portion of the path / ServiceMethod.name |
client + server |
rpc.grpc.status_code |
numeric gRPC status (0 = OK) | both, on completion |
error.type |
exception's runtime class | client + server, on error |
- Span name:
<service>/<method>on the client (e.g.com.example.UserService/GetUser);<method>on the server. - Span kind:
CLIENTfor outbound,SERVERfor inbound. - Span status:
Errorfor any non-OK gRPC status or thrown exception; otherwise unset.
W3C trace context propagation #
- Outbound:
traceparent/tracestate/baggageare added toCallOptions.metadataso the receiving service joins the same trace. - Inbound: those same headers are extracted from
ServiceCall.clientMetadataand used to parent the server span.
If the receiving service also runs this package's server interceptor (or any OTel-aware gRPC server), spans on both sides join into a single distributed trace automatically.
Caveats #
- Both interceptors call
OTel.tracerProvider().getTracer(...)in their constructors —OTel.initialize()must have run first. - The server interceptor uses the typedef form of
Interceptor(the function), not the abstractServerInterceptorclass withintercept<Q,R>. The typedef only hooks the start of the call. The span is ended on the next microtask after the interceptor fires; that's accurate enough for unary calls, but long-lived streaming calls will see their span end before the stream actually completes. A future revision can swap to theServerInterceptorclass for full lifecycle wrapping. - Streaming client calls hang completion off
ResponseStream.trailers, which fires after the stream is closed (success) or errors (failure) — that's the correct end-of-call signal perpackage:grpc's contract.
License #
Apache 2.0 — see LICENSE.