Skip to content

OpenTelemetry

The rpc_dart_opentelemetry package integrates OpenTelemetry into rpc_dart. It wraps every RPC call in a span, propagates W3C Trace Context through RpcContext headers, and optionally records call metrics.


Setup

dependencies:
  rpc_dart_opentelemetry: ^0.1.0
  opentelemetry: ^0.18.0

1. Bootstrap the OTel SDK

import 'package:opentelemetry/api.dart';
import 'package:opentelemetry/sdk.dart';

// Console exporter for development; swap for CollectorExporter in production.
final tracerProvider = TracerProviderBase(
  processors: [SimpleSpanProcessor(ConsoleExporter())],
);
registerGlobalTracerProvider(tracerProvider);

// Shutdown on exit to flush buffered spans.
tracerProvider.shutdown();

2. Attach the interceptor

import 'package:rpc_dart_opentelemetry/rpc_dart_opentelemetry.dart';

final tracer = globalTracerProvider.getTracer('my-service', version: '1.0.0');

final endpoint = RpcResponderEndpoint(transport: transport)
  ..addInterceptor(OtelRpcInterceptor(tracer: tracer))
  ..registerServiceContract(MyResponder())
  ..start();

Every RPC call through this endpoint now produces a span automatically.


Span Details

Each span is named {serviceName}/{methodName} and carries these attributes:

Attribute Value
rpc.system 'rpc_dart'
rpc.service service name
rpc.method method name
rpc.call_type 'unary' / 'server_stream' / 'client_stream' / 'bidirectional_stream'
rpc.trace_id value from RpcContext.traceId (if present)

For streaming calls, rpc.stream.messages is added on completion with the total message count.

On error, the span records the exception and sets status to StatusCode.error.


Accessing the Span in Handlers

The interceptor stores the active span in RpcContext under OtelRpcKeys.span. Retrieve it in any handler to add custom attributes:

Future<Response> handle(Request request, {RpcContext? context}) async {
  final span = context?.getValue(OtelRpcKeys.span) as Span?;
  span?.setAttribute(Attribute.fromString('user.id', request.userId));
  span?.setAttribute(Attribute.fromInt('items.count', request.items.length));

  // ...
}

W3C Trace Context Propagation

Use RpcOtelPropagator to propagate trace context across service boundaries via traceparent / tracestate headers.

Client side — inject active span into outgoing context:

// Wrap the context before passing it to a caller method.
final ctx = RpcOtelPropagator.inject(RpcContext.empty());
final response = await caller.myMethod(request, context: ctx);

Server side — extraction is automatic. OtelRpcInterceptor calls RpcOtelPropagator.extract internally on every incoming call, so the span created on the server is automatically linked to the parent span from the caller.


Metrics (optional)

Pass a RpcOtelMetrics instance to record call counts and error counts:

import 'package:opentelemetry/api.dart';

final meter = globalMeterProvider.getMeter('my-service');

endpoint.addInterceptor(OtelRpcInterceptor(
  tracer: tracer,
  metrics: RpcOtelMetrics(meter: meter),
));

Two instruments are recorded per call:

Instrument Type Attributes
rpc_dart.calls.total Counter rpc.system, rpc.service, rpc.method
rpc_dart.errors.total Counter same