otel_serverpod 0.1.0-beta.1 copy "otel_serverpod: ^0.1.0-beta.1" to clipboard
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) with http.route = endpoint.method — Serverpod routes POST /<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 using otel_http, otel_dio, or otel_chopper injects those headers automatically, and the endpoint span stitches into the same trace.
  • http.server.request.duration histogram (registry bucket advice, labeled by method, route, and status code) and the http.server.active_requests up-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 — the databaseInterceptor constructor parameter and the stable Database surface 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.hello reverts to POST) 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

0
likes
150
points
59
downloads

Documentation

API reference

Publisher

verified publisherdartastic.io

Weekly Downloads

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 http.server.request.duration metrics. Works with any OTLP backend.

Homepage
Repository (GitHub)
View/report issues

License

Apache-2.0 (license)

Dependencies

dartastic_opentelemetry, dartastic_opentelemetry_api, serverpod

More

Packages that depend on otel_serverpod