Setting Up Datadog Universal Service Tagging So Traces, Metrics, and Logs Actually Correlate
Unify Datadog traces, metrics, and logs under one service identity using the reserved env, service, and version tags — with env-var setup, log injection, and verification steps.
20 Mar 2026, 18:24 UTC

The problem: three signal types, three different service names
Your APM service catalog says checkout, your logs are tagged with the container image name, and your infrastructure metrics carry whatever the Agent auto-discovered. Every pivot between a trace and its logs returns nothing, and you end up debugging by timestamp and guesswork. Datadog's fix for this is Universal Service Tagging: three reserved tags — env, service, and version — that the platform treats as first-class dimensions across APM, logs, and metrics. When all three signal types carry the same values, the UI links them automatically.
The useful takeaway: set three environment variables on your application process, enable log injection, and verify the pipeline remappers. Most of the work is deciding on consistent values, not writing code.
Prerequisites
- A supported APM tracer (Java, Python, Node, Go, .NET, Ruby, or PHP) already sending traces. Exact variable names and defaults vary by tracer version, so confirm against the docs for your language.
- The Datadog Agent collecting logs, with a log pipeline you can edit (or permission to ask whoever owns it).
- A naming decision: one service name per logical service, one env vocabulary (
prod,staging), and a version string that changes every deploy (a git SHA or semver tag works well).
Step 1: Set the reserved tags on the application process
The recommended path is environment variables. Supported tracers pick these up with no code changes:
DD_ENV=prod
DD_SERVICE=checkout
DD_VERSION=1.4.2
In Kubernetes, put them in the pod spec so they follow the deployment rather than the image:
env:
- name: DD_ENV
valueFrom:
fieldRef:
fieldPath: metadata.labels['tags.datadoghq.com/env']
- name: DD_SERVICE
valueFrom:
fieldRef:
fieldPath: metadata.labels['tags.datadoghq.com/service']
- name: DD_VERSION
valueFrom:
fieldRef:
fieldPath: metadata.labels['tags.datadoghq.com/version']
Run this wherever you define the workload (deployment manifest, task definition, systemd unit). No special permissions beyond your normal deploy rights. Two cautions:
- Do not also override the service name in tracer initialization code. If code says
payments-apiwhile the environment sayscheckout, you get inconsistent identities across signal types — the exact failure you are trying to fix. - If you use admission-controller auto-instrumentation, it injects its own defaults. Check which source wins in your setup before assuming your values took effect.
Step 2: Derive tags from container labels when one image serves many roles
If the same image runs as multiple services (a common pattern for workers), hardcoding DD_SERVICE in the image is wrong. Instead, the Agent can derive tags from container labels such as com.datadoghq.tags.service or the equivalent Kubernetes annotations (tags.datadoghq.com/service). Set these per deployment, and the Agent applies them to container metrics and logs it collects.
Step 3: Enable trace-log correlation
Tag consistency gets you service-level pivots; trace ID injection gets you the exact log lines for a single request. Enable it on the tracer:
DD_LOGS_INJECTION=true
The tracer then writes dd.trace_id and dd.span_id into each log record (for structured logging this is automatic; for plain-text formats, check your tracer's log configuration). On the pipeline side, Datadog's standard attribute remappers map dd.env, dd.service, and dd.version onto the reserved tags. If you use a custom pipeline or a non-standard log format, open your pipeline in Logs > Configuration and confirm those remappers exist and run before any processor that depends on the tags.
Step 4: Verify the correlation actually works
Do not assume — check each pivot:
- Open APM > Services and confirm the service appears with the expected
envandversionfacets populated. - Open a trace, switch to the Logs tab, and confirm correlated log entries appear with a matching
dd.trace_id. An empty result here almost always means a service-name mismatch between traces and logs. - In Logs, query
service:checkout env:prodand confirm only that service's logs match — no strays from the image name or filename default. - Deploy a new version and confirm the
versiontag changes. This unlocks deployment tracking: comparing error rates and latency before and after a release in APM.
Failure modes and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Trace-to-log pivot returns nothing | Logs carry a different service value than traces (image name, filename, code override) | Align DD_SERVICE everywhere; remove in-code overrides; check pipeline remappers |
| Version facet missing in APM | DD_VERSION not set on the process, or set only on the container, not the app | Set it in the pod/task env, redeploy |
| Duplicate services in catalog | Auto-instrumentation defaults conflicting with manual values | Decide one source of truth for the tags |
One more limit to keep in mind: the reserved tags are cheap, but high-cardinality custom tags you add alongside them (user IDs, request IDs) can inflate custom metrics billing. Keep cardinality deliberate.
Rollback is trivial if a deploy goes wrong: the tags are configuration, so reverting the environment variables or labels and redeploying restores the previous identity — though historical data keeps whatever tags it was ingested with.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.