Observability — OpenTelemetry integration¶
As of the 2.1.0 release all four core SDKs (TypeScript, Python, Rust, Go) can emit traces and metrics to any OpenTelemetry-compatible backend. If you run Grafana, Jaeger, Honeycomb, Datadog, New Relic, or a self-hosted OTel Collector, you don't need any custom bridge — DCP-AI talks OTLP directly.
This page documents the three things you need to know:
- How the telemetry API maps to OTel spans and metrics.
- How to turn OTLP on (TypeScript and Python).
- Three copy-paste recipes for common backends.
What the SDK emits¶
| DCP operation | OTel primitive | Metric name |
|---|---|---|
startSpan / endSpan |
Span | dcp.<span-name> (attributes: dcp.service, dcp.duration_ms, any you attach) |
recordSignLatency(ms, alg) |
Histogram + Counter | dcp.sign.latency_ms + dcp.signatures.created |
recordVerifyLatency(ms, alg) |
Histogram + Counter | dcp.verify.latency_ms + dcp.signatures.verified |
recordKemLatency(ms, op) |
Histogram | dcp.kem.latency_ms (attr operation=encapsulate\|decapsulate) |
recordCheckpointLatency(ms, tier) |
Histogram | dcp.checkpoint.latency_ms (attr tier=routine\|standard\|elevated\|maximum) |
recordBundleVerify(ms, success, tier) |
Histogram + Counter | dcp.bundle_verify.latency_ms + dcp.bundles.verified |
recordCacheHit / recordCacheMiss |
In-memory counters (exposed via getMetricsSummary) |
cacheHitRate in summary |
recordA2ASession / recordA2AMessage |
Counter | dcp.a2a.sessions / dcp.a2a.messages |
recordError(op, err) |
Counter + span status | dcp.errors (attr operation) |
Resource attributes sent with every export:
- service.name → value of serviceName passed to init()
- service.version → version of the @dcp-ai/sdk / dcp-ai package
Turning it on¶
TypeScript¶
Install the optional OTel packages on top of the SDK (@dcp-ai/sdk here is the published version; with the SDK
built from a checkout, install only the @opentelemetry/* packages):
npm install @dcp-ai/sdk \
@opentelemetry/api \
@opentelemetry/sdk-trace-node \
@opentelemetry/sdk-metrics \
@opentelemetry/exporter-trace-otlp-http \
@opentelemetry/exporter-metrics-otlp-http \
@opentelemetry/resources \
@opentelemetry/semantic-conventions
Wire in your app (six lines):
import { dcpTelemetry } from '@dcp-ai/sdk';
await dcpTelemetry.init({
serviceName: 'my-agent',
enabled: true,
exporterType: 'otlp',
otlpEndpoint: 'http://localhost:4318', // or OTEL_EXPORTER_OTLP_ENDPOINT env var
});
With telemetry enabled, init() returns a Promise when exporterType === 'otlp'. Events emitted before successful OTLP initialization still update in-memory metrics and reach onEvent listeners, but are not replayed to OTLP. Await initialization before emitting events you want forwarded. Register an error listener beforehand: a resolved Promise alone does not confirm success, because initialization failures are reported as error events. Awaiting initialization does not guarantee export delivery; that also depends on the exporter and backend.
If you forget to install one of the OTel packages, init() emits a clear error event on the listener bus (you can see it by subscribing with dcpTelemetry.onEvent) with an installation command listing the optional OTel packages. The SDK keeps working; only the OTLP forwarding is disabled.
On shutdown, flush pending exports:
Python¶
Install the OTLP extra on top of the SDK (the published version; from a checkout,
pip install -e "/path/to/dcp-ai/sdks/python[otlp]"):
Wire in your app:
from dcp_ai import dcp_telemetry
dcp_telemetry.init(
service_name="my-agent",
enabled=True,
exporter_type="otlp",
otlp_endpoint="http://localhost:4318", # or OTEL_EXPORTER_OTLP_ENDPOINT env var
)
Same guarantees as TypeScript: default install is slim, OTel packages are imported lazily only when you flip exporter_type="otlp", and a missing dependency surfaces as an error event rather than an application crash.
Rust¶
Enable the optional otlp Cargo feature:
Wire in your app:
use dcp_ai::observability::{dcp_telemetry, ExporterType, TelemetryConfig};
dcp_telemetry().init(TelemetryConfig {
service_name: "my-agent".into(),
enabled: true,
exporter_type: ExporterType::Otlp,
otlp_endpoint: Some("http://localhost:4318".into()),
..Default::default()
});
The OTLP bridge exports from background threads (batch span processor and periodic metric reader over a blocking HTTP client), so it needs no Tokio runtime; call dcp_telemetry().shutdown() before your process exits to flush pending exports. Without the otlp feature, requesting ExporterType::Otlp emits a clear error event via on_event and recorders continue to populate the in-memory MetricsSummary.
Go¶
Build your binary with the otlp build tag:
Wire in your app:
import (
"github.com/dcp-ai-protocol/dcp-ai/sdks/go/v2/dcp/observability"
)
observability.Default().Init(observability.Config{
ServiceName: "my-agent",
Enabled: true,
ExporterType: observability.ExporterOTLP,
OTLPEndpoint: "http://localhost:4318",
})
defer observability.Default().Shutdown()
The go.opentelemetry.io/otel packages are listed in go.mod but the OTLP bridge file is gated by //go:build otlp — default (go build) binaries do not link the OTel runtime. Without the tag, selecting ExporterOTLP emits an otlp_init error event through the listener bus; the in-memory metrics still work.
Recipes¶
1. Local Jaeger (free, docker, 30 seconds)¶
docker run -d --name jaeger \
-p 16686:16686 \
-p 4318:4318 \
jaegertracing/all-in-one:latest
# In your app: otlpEndpoint: 'http://localhost:4318'
Open http://localhost:16686 — you'll see traces under the service name you set.
2. Grafana Cloud (managed, free tier)¶
- In Grafana Cloud, create an OpenTelemetry endpoint (Observability → OpenTelemetry → Create integration). You'll get a URL and an API token.
- In your app:
await dcpTelemetry.init({
serviceName: 'my-agent',
enabled: true,
exporterType: 'otlp',
otlpEndpoint: 'https://<your-instance>.grafana.net/otlp',
});
// Export OTEL_EXPORTER_OTLP_HEADERS="authorization=Basic <base64 of instanceID:token>"
// in the shell that runs your app. The OTel SDK picks it up automatically.
3. Honeycomb¶
export OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.io
export OTEL_EXPORTER_OTLP_HEADERS=x-honeycomb-team=<YOUR_API_KEY>
The SDK reads OTEL_EXPORTER_OTLP_ENDPOINT as a fallback when otlpEndpoint is omitted.
Opt-out paths¶
- No
initcall, orenabled: false: telemetry is off (the default); recorders return at once andonEventlisteners receive nothing. exporterType: 'none'withenabled: true: nothing is exported, but the in-memory metrics (getMetricsSummary) and theonEventlisteners still work.exporterType: 'console': each event is also printed to stdout as[DCP-AI Telemetry] <JSON>. Useful in development without running a collector.
Companion tools¶
The dcpTelemetry.onEvent(listener) channel stays available whether OTLP is on or off, so you can also build your own bridge — to a websocket debugger, a structured log file, or a custom dashboard. The Quick Start shows the telemetry API, and the
API reference the listener.