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_ENDPOINT at 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 with dd.trace_id / dd.span_id (the lower 64 bits of the OTel trace id, as Datadog expects) so logs link to APM traces. Maps level → 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-trace is not used as the tracer — it would have to become the global provider, which would break the in-app dashboard.
  • Profiling (profiling:true) — the official dd-trace continuous 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 agentUrl at 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.