Datadog
Agentless Datadog metrics exporter — pushes the unified Metrics-API to Datadog's /api/v2/series HTTP intake.
@voltro/plugin-datadog pushes the framework's metrics straight to Datadog over HTTP — no Agent, no OTLP collector. It periodically snapshots the unified Metrics-API (the same Effect MetricRegistry that @voltro/plugin-prometheus and the dashboard read) and POSTs to /api/v2/series.
Have a Datadog Agent (or run an OTLP collector)? Prefer the OTLP path: point
OTEL_EXPORTER_OTLP_ENDPOINTat the Agent's OTLP port — the framework's runtime metrics export covers it without this plugin. Reach for this plugin when you have NO Agent (serverless, edge, a box you don't control).
Wiring
// app.config.ts
import { datadogPlugin } from '@voltro/plugin-datadog'
export default {
type: 'api' as const,
name: 'api',
plugins: [
datadogPlugin({
// apiKey defaults to DD_API_KEY; without it the plugin is a no-op.
site: 'datadoghq.com', // DD_SITE; EU → 'datadoghq.eu', US3 → 'us3.datadoghq.com'
intervalMs: 30_000,
prefix: 'voltro.', // optional metric-name prefix
tags: ['env:prod', 'service:api'],
}),
],
}Without an API key the plugin logs a warning and stays inert — it never blocks boot, and a Datadog outage is fail-soft (logged, never thrown).
Mapping
Every sample is submitted as a Datadog gauge (type: 3) at its current value, with metric labels turned into key:value tags (plus your global tags):
| Framework metric | Datadog |
|---|---|
counter (e.g. voltro_rpc_requests_total) |
gauge at the cumulative value — use .as_count() / diff() in a Datadog query to recover the per-interval rate |
gauge (e.g. voltro_subscriptions_active) |
gauge at the current value |
histogram (e.g. voltro_rpc_duration_seconds) |
<name>.count, <name>.sum, <name>.avg (sum/count) |
Counters are submitted cumulatively (stateless exporter — no previous-value tracking); Datadog query functions recover the rate. Summary/frequency metrics aren't produced by the framework and are skipped.
Deep integration — logs + traces + profiling
Beyond agentless metrics, opt into the full trace-correlated stack:
datadogPlugin({
apiKey: env.DD_API_KEY,
service: 'api', env: 'prod', version: '1.4.2', // DD unified service tagging
logs: true, // forward the framework log sink → /api/v2/logs
traces: true, // framework spans → the Datadog Agent's OTLP receiver
agentUrl: 'http://localhost:4318', // or DD_TRACE_AGENT_URL
profiling: true, // dd-trace continuous profiler (profiler-only mode)
})- Logs (
logs:true) — every framework log line is batched + POSTed to Datadog's logs intake withdd.trace_id/dd.span_id(the lower 64 bits of the OTel trace id, as Datadog expects) so logs link to APM traces. Mapslevel → status,scope → an attribute, and the structured fields through. - Traces (
traces:true) — the framework's OTel spans are routed to the Datadog Agent's OTLP receiver (Datadog's official OpenTelemetry ingestion). This is the deep path: the framework keeps owning the tracer (the in-app Traces dashboard stays intact) and the Agent forwards to DD APM.dd-traceis not used as the tracer — it would have to become the global provider, which would break the in-app dashboard. - Profiling (
profiling:true) — the officialdd-tracecontinuous profiler, started in profiler-only mode (tracing:false) so it never hijacks the OTel tracer.
dd-trace + the OTel exporter packages are optional, lazy-loaded dependencies.
No Agent? Metrics + logs are agentless (HTTP intake). Traces need an Agent — point
agentUrlat it. If you already run an OTLP collector, you can also export traces+metrics via the framework's runtime OTLP path (OTEL_EXPORTER_OTLP_ENDPOINT) without this plugin.
Configuration
| Option | Type | Default | Notes |
|---|---|---|---|
apiKey |
string |
DD_API_KEY env |
Needed for metrics + logs (agentless). Traces use the Agent and don't need it. |
site |
string |
DD_SITE env / datadoghq.com |
EU → datadoghq.eu, US3 → us3.datadoghq.com, … |
intervalMs |
number |
30000 |
Metrics push interval. |
prefix |
string |
— | Prepended to every metric name (e.g. voltro.). |
tags |
string[] |
— | Added to every series + log (['env:prod','service:api']). |
logs |
boolean |
false |
Forward the framework log sink → /api/v2/logs. |
logsIntervalMs |
number |
5000 |
Logs batch flush interval. |
traces |
boolean |
false |
Route framework spans → the Agent's OTLP receiver. |
agentUrl |
string |
DD_TRACE_AGENT_URL / http://localhost:4318 |
/v1/traces is appended. |
profiling |
boolean |
false |
dd-trace continuous profiler (profiler-only mode). |
service / env / version |
string |
DD_SERVICE / DD_ENV / DD_VERSION |
DD unified service tagging — consistent across metrics, logs + traces. |
name |
string |
— | Disambiguates multiple instances. |
Troubleshooting
| Symptom | Cause / fix |
|---|---|
Boot logs datadog inactive |
No DD_API_KEY and neither traces nor profiling enabled — nothing to do. Set a key (metrics/logs) or traces/profiling. |
| Metrics/logs don't appear | apiKey missing or wrong site region. A failed POST is fail-soft — check voltro logs for datadog series/logs POST failed. |
traces: true but nothing in DD APM |
The Agent's OTLP receiver must be enabled and reachable at agentUrl (/v1/traces). No Agent → traces won't ship (metrics/logs still do). |
| Logs not linked to traces in DD | Correlation needs dd.trace_id — emitted only for log lines carrying fields.traceId (i.e. inside an rpc trace). Set service so DD groups them. |
| In-app Traces dashboard intact? | Yes — the contributed OTLP exporter is added alongside the framework's buffer sink; it never replaces the tracer. |
Permissions
The plugin declares network:outbound:<site> (metrics/logs) — plus network:outbound:<agent-host> when traces/profiling is on. It mounts no routes and intercepts nothing.