Skip to main content

datadog subscriber

Posts events to Datadog as logs (HTTP intake) and/or metrics (v2 series). Logs and metrics are independent — enable either, neither, or both.

Add to the subscribers block in config.yaml:

subscribers:
datadog:
enabled: true
site: "datadoghq.com" # datadoghq.eu, us3.datadoghq.com, us5.datadoghq.com, ap1.datadoghq.com, ddog-gov.com
# API key via env var DD_API_KEY (preferred) or set api_key here
api_key: ""
compression: "gzip" # "gzip" (default) or "none"
skip_verify: false
buffer_size: 1000

logs:
enabled: true
source: "openziti" # ddsource attribute
service: "ziti-controller" # service attribute; if empty, falls back to event namespace
hostname: "ziti-prod-01" # host attribute
tags: # static ddtags appended to every log
env: prod
cluster: us-east-1
namespace_filter: [] # only forward these namespaces; empty = all
exclude_fields: # drop fields before send (dotted paths supported)
- circuitId
- tags.sourceRouterId
# Per-event filter — drop events before they're batched/billed.
# See ../ "Per-Subscriber Filtering" for the full comparator list.
# Common cases:
include:
- { field: event_type, not_equals: created } # circuits: drop creates, keep failed/pathUpdated
exclude: []
batch_size: 100 # capped at 1000 (Datadog hard limit)
flush_interval: 5s
workers: 2

metrics:
enabled: false
metric_prefix: "openziti."
hostname: "ziti-prod-01"
tags:
env: prod
batch_size: 100
flush_interval: 10s
workers: 1
mappings:
- namespace: metrics
name_path: metric # name_path's value supplies the middle of the metric name
# values: field path → metric type (gauge | count | rate). For
# nested paths only the leaf is used as the metric-name suffix,
# so "metrics.mean" yields openziti.<metric>.mean.
values:
metrics.count: count
metrics.mean: gauge
metrics.m1_rate: rate
metrics.p99: gauge
tag_paths: # build tags from event fields
router: source_id
entity: source_entity_id
- namespace: usage
name_path: event_type # v2 usage (default): one event per counter, e.g. usage.ingress.tx
values:
usage: count # yields openziti.usage.ingress.tx.usage
tag_paths:
identity: identity_name
service: service_name

The logs path posts a JSON array to https://http-intake.logs.<site>/api/v2/logs with the DD-API-KEY header. The enriched event JSON is inlined as nested attributes — Datadog auto-facets nested fields, so per-event ids, names, and timings stay searchable in Logs Explorer without inflating tag cardinality.

The metrics path posts to https://api.<site>/api/v2/series. For each event matching a mapping's namespace, every entry in values produces one series with metric name <metric_prefix><name_part>.<value_key>, where the name part is either the value at name_path (e.g., link.latency) or the static name_suffix. Non-numeric or missing values are skipped silently.

With usage v3 (options: {version: 3} on the usage subscription), usage is a map of counters rather than a number, so usage: count yields nothing. Map each counter with its dot escaped instead, e.g. name_suffix: usage.ingress with values: {usage.ingress\.rx: count, usage.ingress\.tx: count}.

Cost and cardinality controls​

These settings limit what gets indexed in Datadog and prevent tag cardinality from inflating your bill:

  • Use exclude_fields to drop sessionId, circuitId, or any per-flow id Datadog would otherwise index.
  • Keep dynamic ids in tag_paths only when the cardinality is bounded (router/identity counts in the hundreds — not flow/session ids).
  • Set namespace_filter to send only the namespaces the customer actually queries; high-volume fabric.usage is the obvious one to either filter out or send via metrics instead of logs.
  • Use include/exclude to filter within a namespace by event field — e.g. send only event_type: failed circuits, or only service_name values matching a regex. Drops happen before batching, so filtered events never count against your ingest bill. See the Per-subscriber filtering section of the configuration reference.
  • Both channels gzip the body by default; set compression: none only when debugging.

The subscriber retries on 429 and 5xx up to 3 times with exponential backoff and respects the Retry-After header. 4xx responses (other than 429) are not retried — those indicate a payload or auth problem the customer needs to fix.

Tags vs. attributes​

Datadog treats logs and metrics differently. Knowing the model up front avoids both unusable dashboards and surprise bills.

Logs have two distinct dimension systems:

  • Tags (ddtags): Low-cardinality dimensions, appear in Live Tail and the index drop-down. The connector emits exactly: namespace:<event_namespace> plus everything you put in subscribers.datadog.logs.tags. Keep this small.
  • Attributes: Every nested JSON field in the event becomes searchable as @path.to.field. They don't cost anything by default; to use them in the left-side facet panel of Logs Explorer, promote them in Logs → Configuration → Facets.

Metrics have only tags. Each unique combination of tag values is a separate billed time series — every tag you add multiplies cardinality.

WhereWhat to doWhy
Logs → Configuration → FacetsPromote @service_name, @identity_name, @host_name, @edge_router_name, @event_type, @circuit_id, @metricThese are how operators slice circuits/usage in the UI. Free; just makes the left panel usable.
Logs → Configuration → IndexesAdd a sampling/exclusion filter for @namespace:metrics if you also send metrics via the metrics APIAvoids paying twice for the same data.
Logs → PipelinesAdd a remapper so @event_type → log status, e.g. *.failed → errorLets monitors fire on log severity.
Metrics → TagsInspect cardinality on openziti.usage.* and openziti.*.countCatches a tag explosion before the bill.

Tag cardinality reference​

For the metrics path, cardinality depends on network size — these are rough orders of magnitude:

FieldTypical cardinalitySafe as a metric tag?
env / cluster / region1–10✅ always
source_id / edge_router (router id)10s–100s✅ usually
service_name10s–1000s⚠️ usually safe; review
host_name10s–1000s⚠️ usually safe; review
identity_name100s–10000s+⚠️ depends on network — often the dominant cost driver
circuit_id / session_id / per-flow idsunbounded🚫 never — these belong only on logs as attributes

The example config above intentionally puts identity_name into metrics.tag_paths for illustration. In a network with many identities you may want to drop it from metrics tags and rely on the logs attribute (@identity_name) for per-identity drill-down.