otel_serverpod 0.1.0-beta.1
otel_serverpod: ^0.1.0-beta.1 copied to clipboard
OpenTelemetry instrumentation for Serverpod. One call opens http.server semconv spans per endpoint method call (route = endpoint.method), db.* client spans for the database layer, and the standard htt [...]
otel_serverpod #
OpenTelemetry instrumentation for
Serverpod — server-side complement to the
various client-side wrappers (http, dio, grpc, chopper). One
http.server span per endpoint method call, with W3C traceparent
extracted from inbound headers so distributed traces stitch
end-to-end, plus the standard HTTP server metrics. Everything
follows the OpenTelemetry semantic conventions and is standard
OTLP.
import 'package:dartastic_opentelemetry/dartastic_opentelemetry.dart'
show OTel;
import 'package:otel_serverpod/otel_serverpod.dart';
import 'package:serverpod/serverpod.dart';
Future<void> run(List<String> args) async {
// Endpoint + auth come from the standard OTEL_EXPORTER_OTLP_*
// environment variables.
await OTel.initialize(serviceName: 'mypod');
final pod = Serverpod(
args,
Protocol(),
Endpoints(),
// Database spans (`find person`, `insert order`, ...) per session.
databaseInterceptor: OtelServerpod.databaseInterceptor,
);
// Endpoint spans and request metrics.
OtelServerpod.install(pod);
await pod.start();
}
Initialize OTel before OtelServerpod.install(pod) — install
fails fast with a StateError if the SDK is not initialized.
What one call gives you, zero config #
- One SERVER span per endpoint method call, named
POST endpoint.method(semconv{method} {route}form, e.g.POST greeting.hello) withhttp.route = endpoint.method— Serverpod routesPOST /<endpoint>with the method in the JSON body, so the span is renamed once dispatch resolves the route. - W3C context extraction (
traceparent/tracestate/baggage) from inbound headers. A client usingotel_http,otel_dio, orotel_chopperinjects those headers automatically, and the endpoint span stitches into the same trace. http.server.request.durationhistogram (registry bucket advice, labeled by method, route, and status code) and thehttp.server.active_requestsup-down counter.- Exceptions are recorded and rethrown, never swallowed. 5xx flips span status to Error; 4xx does not (per OTel HTTP semconv).
Database spans, one more line #
OtelServerpod.databaseInterceptor is a Serverpod constructor
parameter, so install cannot retrofit it:
Serverpod(args, Protocol(), Endpoints(),
databaseInterceptor: OtelServerpod.databaseInterceptor);
Every typed database operation becomes a CLIENT span named
{operation} {table} (find person, insert order) parented to
whatever span is active — the endpoint span for session operations
inside an endpoint call — plus a db.client.operation.duration
measurement. transaction wraps its callback so inner operations
become children of the transaction span automatically.
db.query.text privacy default #
db.query.text is stamped ONLY on the unsafe* raw-SQL
operations and ONLY when you opt in with
OtelServerpod.install(pod, captureDbStatements: true) — raw SQL
can carry sensitive literals, so the default is OFF. The typed CRUD
operations never carry query text.
Future calls #
Change extends FutureCall to extends OtelFutureCall and rename
your invoke override to run. Each invocation becomes an
INTERNAL span named after the registered future-call name; its
database operations become child spans.
class BirthdayReminder extends OtelFutureCall<Birthday> {
@override
Future<void> run(Session session, Birthday? object) async { ... }
}
What is instrumented, exactly #
| Signal | Name | Notes |
|---|---|---|
| Span | POST {endpoint}.{method} |
kind server, http.request.method, http.route, url.path, url.scheme, network.protocol.version, http.response.status_code, rpc.system.name = serverpod, rpc.method, error.type on failure |
| Span | {operation} {table} (e.g. find person) |
kind client, db.system.name, db.operation.name, db.collection.name, server.address, server.port; db.query.text only on unsafe* ops and only with captureDbStatements: true (default off) |
| Span | future-call name | kind internal, code.function.name, via OtelFutureCall |
| Metric | http.server.request.duration |
histogram, seconds, registry bucket advice |
| Metric | http.server.active_requests |
up-down counter |
| Metric | db.client.operation.duration |
histogram, seconds |
Names not covered by the semconv registry enums are exported as
consts on OtelServerpodSemantics.
Caveats #
- Serverpod version: requires the 4.0 line
(
>=4.0.0-beta.1 <4.1.0). Serverpod 3.4.x is not supported — thedatabaseInterceptorconstructor parameter and the stableDatabasesurface this package wraps exist only on 4.0. - Hot reload (dev mode) re-initializes Serverpod's endpoint
connectors, which drops the span naming (
POST greeting.helloreverts toPOST) until you restart the server. Middleware and metrics keep flowing. - Websocket method streams (
MethodStreamConnector) pass through untouched in this release — streaming keeps working, stream spans are a tracked follow-up.
License #
Apache 2.0