genkit_otel
OpenTelemetry GenAI semantic-conventions instrumentation for
Genkit Dart, built on the
dartastic_opentelemetry
SDK.
It plugs into Genkit's pluggable instrumentation system and emits telemetry that follows the OTel GenAI semantic conventions:
gen_ai.*client spans for model operations (chat <model>), with request config, token usage, and finish reasons.- The GenAI client metrics
gen_ai.client.token.usage(split bygen_ai.token.type) andgen_ai.client.operation.duration. - Optional message-content capture, either as span attributes or a dedicated
gen_ai.client.inference.operation.detailsevent. - Optional
execute_toolspans, and generic spans for other Genkit action types so the trace tree stays connected.
Usage
The application owns the OpenTelemetry SDK. Initialize it, then register the
provider before creating Genkit:
import 'package:dartastic_opentelemetry/dartastic_opentelemetry.dart';
import 'package:genkit/genkit.dart';
import 'package:genkit/telemetry.dart';
import 'package:genkit_otel/genkit_otel.dart';
Future<void> main() async {
// Reads OTEL_* env vars; defaults to http://localhost:4318.
await OTel.initialize();
configureInstrumentation(GenAiInstrumentation());
final ai = Genkit(/* plugins: [...] */);
// ...
await ai.shutdown();
await OTel.shutdown(); // flushes spans and metrics
}
When the SDK is not initialized, the provider is effectively a no-op.
SDK logging
dartastic's internal logger defaults to printing an
[ERROR] Tracer: Exception in withSpanAsync ... line for every exception that
flows through a span. The exception is still recorded and rethrown; the line is
just diagnostic noise. To quiet it, lower the level after OTel.initialize():
OTelLog.currentLevel = LogLevel.fatal; // or set OTEL_LOG_LEVEL=fatal
Content capture (PII)
Prompt and response content may contain PII, so capture is off by default.
contentCapturingMode mirrors the OTel GenAI ContentCapturingMode:
| Mode | Where content goes |
|---|---|
noContent (default) |
not captured |
spanOnly |
span attributes (gen_ai.*.messages) as JSON strings |
eventOnly |
a gen_ai.client.inference.operation.details log event |
spanAndEvent |
both |
GenAiInstrumentation(
contentCapturingMode: ContentCapturingMode.spanOnly,
);
When not supplied, the env var OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT
is consulted using the spec's UPPER_SNAKE tokens (NO_CONTENT, SPAN_ONLY,
EVENT_ONLY, SPAN_AND_EVENT); an explicit value overrides it. An unknown
token logs a one-time warning and falls back to NO_CONTENT.
EVENT_ONLYemits content on the OpenTelemetry logs signal (agen_ai.client.inference.operation.detailslog record), not on the span. Trace-only backends like Jaeger cannot display it (its GenAI tab reads span attributes; its "Trace Logs" tab reads span events, neither is the logs signal). UseSPAN_ONLYorSPAN_AND_EVENTfor Jaeger, or a logs backend (e.g. Loki, Elasticsearch/OpenSearch) forEVENT_ONLY.
Options
| Option | Default | Description |
|---|---|---|
contentCapturingMode |
env or noContent |
Where spec-shaped gen_ai.* message content is recorded (noContent/spanOnly/eventOnly/spanAndEvent). |
captureActionIO |
false |
Capture raw Genkit input/output as genkit.input/genkit.output on every span (debugging / Dev UI). |
emitMetrics |
true |
Emit token-usage and operation-duration metrics. |
emitToolSpans |
false |
Emit execute_tool spans for tool actions. |
scopeName |
genkit-genai |
Instrumentation scope for tracer/meter/logger. |
tracer / meter |
resolved lazily | Escape hatches to inject explicit instances. |
Example
See testapps/otel_jaeger
for a runnable sample that exports traces to Jaeger and metrics to a local
collector, with a Docker-free script that downloads and runs both.
Libraries
- genkit_otel
- OpenTelemetry GenAI semantic-conventions instrumentation for Genkit.