# Datadog

> Agentless Datadog metrics exporter — pushes the unified Metrics-API to Datadog's /api/v2/series HTTP intake.



---

<!-- source: en/plugins/datadog.md -->
## 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`](/docs/plugins/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](/docs/observability/overview#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

```ts
// 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:

```ts
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](/docs/observability/distributed-tracing) 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.
