otel_grpc 0.2.0
otel_grpc: ^0.2.0 copied to clipboard
OpenTelemetry instrumentation for `package:grpc`. Client and server interceptors emitting rpc.* spans, propagating W3C trace context via gRPC metadata, with recursion suppression.
otel_grpc example #
// example/lib/main.dart
import 'package:dartastic_opentelemetry/dartastic_opentelemetry.dart';
// `grpc` also exports a `Server` type; hide it if you import the OTel
// semconv `Server` attribute group in the same file.
import 'package:grpc/grpc.dart';
import 'package:otel_grpc/otel_grpc.dart';
// Generated by protoc from your .proto files:
// protoc --dart_out=grpc:lib/src/generated -Iprotos protos/greeter.proto
import 'src/generated/greeter.pbgrpc.dart';
Future<void> main() async {
// 1. Bring up OTel first so trace context is flowing before the
// first RPC.
await OTel.initialize(
serviceName: 'greeter-client',
);
// 2. Attach the client interceptor to the channel. Every unary and
// streaming call gets a CLIENT-kind span with the registry-current
// rpc.* attributes (rpc.system.name=grpc, fully-qualified
// rpc.method, rpc.response.status_code), and W3C
// traceparent/tracestate/baggage injected into the call metadata.
final channel = ClientChannel(
'api.example.com',
options: const ChannelOptions(),
interceptors: [
OTelGrpcClientInterceptor(
// Optional: interceptors can't see the channel, so pass the
// same host/port you gave the channel to get
// server.address / server.port on the spans. Omit them and
// the attributes are simply not set - never fabricated.
serverAddress: 'api.example.com',
serverPort: 443,
),
],
);
final greeter = GreeterClient(channel);
// 3. Calls made inside an active span parent into it; the receiving
// service joins the same trace via the injected traceparent.
final tracer = OTel.tracerProvider().getTracer('example');
await tracer.startActiveSpan(
name: 'say-hello',
fn: (span) async {
final reply = await greeter.sayHello(HelloRequest(name: 'world'));
print(reply.message);
},
);
await channel.shutdown();
}
Server side #
// example/lib/server.dart
import 'package:dartastic_opentelemetry/dartastic_opentelemetry.dart';
import 'package:grpc/grpc.dart';
import 'package:otel_grpc/otel_grpc.dart';
import 'src/generated/greeter.pbgrpc.dart';
class GreeterService extends GreeterServiceBase {
@override
Future<HelloReply> sayHello(ServiceCall call, HelloRequest request) async {
return HelloReply(message: 'Hello, ${request.name}!');
}
}
Future<void> main() async {
await OTel.initialize(
serviceName: 'greeter-server',
);
// The class-based ServerInterceptor wraps the whole handler
// invocation: SERVER-kind spans with real durations and real
// completion status, parented into the caller's trace via the
// W3C traceparent extracted from the client metadata.
final server = Server.create(
services: [GreeterService()],
serverInterceptors: [OTelGrpcServerInterceptor()],
);
await server.serve(port: 50051);
}
Suppressing instrumentation #
gRPC export over OTLP is itself gRPC. If a ClientChannel you control
both carries OTLP export traffic and has OTelGrpcClientInterceptor
attached, wrap each export call so the interceptor skips it - otherwise
every exported span creates another span:
await runWithoutGrpcInstrumentationAsync(() async {
await exporter.export(spans);
});