Datadog APM Complete Guide: Distributed Tracing, Service Maps, and Alerting
Application Performance Monitoring is not a luxury for production systems — it is the difference between knowing your service is slow and knowing why it is slow, which downstream call caused it, and which users were affected. Datadog APM gives you that visibility through distributed tracing, service maps, and anomaly-based alerting. This guide walks through every step from zero to a fully instrumented service.
What Datadog APM Actually Does
Before installing anything, it helps to understand what you are getting. Datadog APM works by injecting a tracing library into your application process. That library intercepts outgoing HTTP calls, database queries, cache operations, and message queue publishes, then wraps each one in a span. Spans are grouped into traces — a complete picture of everything that happened to serve one request, across every service it touched.
The result is:
- Distributed traces — follow a request from your API gateway through three microservices and a Redis cache, with timing for every hop
- Service maps — auto-generated dependency graphs showing which services call which, with error rates and latency on each edge
- Flame graphs — visual breakdown of where time goes inside a single trace
- Continuous profiling — CPU and memory profiles correlated with traces, so you can tie a slow trace to the specific line of code burning cycles
Installing the Datadog Agent
The Datadog Agent runs as a sidecar or host process and receives trace data from your application libraries. On a Linux host:
DD_API_KEY=<your_api_key> DD_SITE="datadoghq.com" bash -c "$(curl -L https://s3.amazonaws.com/dd-agent/scripts/install_script_agent7.sh)"In Kubernetes, the recommended approach is the Datadog Operator or Helm chart:
helm repo add datadog https://helm.datadoghq.com
helm repo update
helm install datadog-agent datadog/datadog \
--set datadog.apiKey=<your_api_key> \
--set datadog.apm.portEnabled=true \
--set datadog.apm.port=8126 \
--set agents.image.tag=7The agent listens on port 8126 (TCP) by default. Your application libraries send trace data to that port.
Verify the agent is receiving traces:
datadog-agent status | grep -A 10 "APM Agent"You should see Status: Running and a non-zero Traces received/s after you start generating traffic.
Instrumenting Your Application
Node.js
npm install dd-traceAdd this at the very top of your entry point, before any other imports:
const tracer = require('dd-trace').init({
service: 'my-api',
env: 'production',
version: '1.4.2',
logInjection: true, // injects trace_id into log lines
runtimeMetrics: true // CPU, GC, event loop metrics
});That is all you need for automatic instrumentation of Express, Fastify, Koa, Sequelize, Mongoose, Redis, and about 40 other libraries. Datadog's tracer hooks into Node's module loading system at startup.
Python
pip install ddtraceUse ddtrace-run to wrap your process:
DD_SERVICE=my-service DD_ENV=production DD_VERSION=1.4.2 ddtrace-run python app.pyOr instrument manually for fine-grained control:
from ddtrace import tracer
@tracer.wrap(service="payments", resource="charge_card")
def charge_card(amount, card_token):
# your code here
passJava
Add the Java agent JAR to your JVM startup flags:
java -javaagent:/path/to/dd-java-agent.jar \
-Ddd.service=my-service \
-Ddd.env=production \
-Ddd.version=1.4.2 \
-jar myapp.jarThe Java agent auto-instruments Spring Boot, Micronaut, Jersey, JDBC, Kafka, gRPC, and more.
Go
Go requires explicit instrumentation since there is no runtime hook mechanism:
import (
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
httptrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http"
)
func main() {
tracer.Start(
tracer.WithService("my-service"),
tracer.WithEnv("production"),
)
defer tracer.Stop()
// Wrap your HTTP mux
mux := httptrace.NewServeMux()
mux.HandleFunc("/api/orders", handleOrders)
http.ListenAndServe(":8080", mux)
}Unified Service Tagging
For APM data to correlate with logs, metrics, and synthetics, every trace needs three tags: service, env, and version. Set them consistently across all your services.
In Kubernetes, inject them via the Downward API so they match the pod spec automatically:
env:
- name: DD_SERVICE
valueFrom:
fieldRef:
fieldPath: metadata.labels['tags.datadoghq.com/service']
- name: DD_ENV
valueFrom:
fieldRef:
fieldPath: metadata.labels['tags.datadoghq.com/env']
- name: DD_VERSION
valueFrom:
fieldRef:
fieldPath: metadata.labels['tags.datadoghq.com/version']And on the pod template labels:
labels:
tags.datadoghq.com/service: "checkout"
tags.datadoghq.com/env: "production"
tags.datadoghq.com/version: "2.1.0"Reading the Service Map
Once traces start flowing, navigate to APM → Service Map in the Datadog UI. The map renders automatically — you do not configure it. Each node is a service; each edge is a call relationship with p50/p95/p99 latency and error rate overlaid.
Things to look for:
- Red edges — error rates above your SLO threshold
- Thick edges — high request volume, which makes latency degradation more impactful
- Unexpected connections — services calling each other in surprising ways, often revealing unintentional coupling
- Fan-out nodes — services with many downstream dependencies are typically your highest blast-radius components
Clicking a node opens the service overview with a latency histogram, top endpoints, top database queries, and recent errors.
Custom Spans and Business Context
Auto-instrumentation covers infrastructure calls. For business logic, add custom spans:
// Node.js example
const tracer = require('dd-trace');
async function processOrder(orderId) {
const span = tracer.startSpan('order.process', {
tags: {
'order.id': orderId,
'order.type': 'subscription',
}
});
try {
const order = await fetchOrder(orderId);
span.setTag('order.value', order.totalCents);
await chargePayment(order);
await fulfillOrder(order);
span.finish();
} catch (err) {
span.setTag('error', true);
span.setTag('error.message', err.message);
span.finish();
throw err;
}
}Adding order.value to spans lets you filter traces in the APM Explorer by order value — useful for correlating high-value order failures with specific infrastructure behavior.
Dashboards for APM Data
Datadog provides out-of-the-box APM service dashboards, but custom dashboards let you combine APM metrics with infrastructure and business metrics.
Key APM metrics available in dashboards:
| Metric | Description |
|---|---|
trace.web.request.hits |
Request rate per service/endpoint |
trace.web.request.duration |
Latency percentiles |
trace.web.request.errors |
Error rate |
trace.db.query.duration |
Database query latency |
runtime.node.event_loop.time |
Node.js event loop lag |
Example: a dashboard widget showing p99 latency for your checkout service broken down by endpoint:
avg:trace.express.request{service:checkout,env:production} by {resource_name}.rollup(p99, 60)Combine this with a deployment marker overlay so you can visually correlate latency spikes with code deploys.
Setting Up APM Alerts
Latency Alert
In Monitors → New Monitor → APM, create a trace analytics monitor:
- Monitor type: Trace Analytics
- Metric:
p99ofDuration - Filter:
service:checkout env:production - Alert threshold: > 2000ms for 5 consecutive minutes
- Warning threshold: > 1000ms
Error Rate Alert
sum:trace.web.request.errors{service:checkout,env:production}.as_rate()
/
sum:trace.web.request.hits{service:checkout,env:production}.as_rate()Alert when this ratio exceeds 1% for 3 consecutive minutes.
Anomaly Detection
For services with variable traffic patterns (e.g., spiky during business hours), use anomaly detection instead of static thresholds:
- Monitor type: Metric
- Metric:
avg:trace.web.request.duration.by.service{service:checkout} - Alert condition: Anomaly (agile algorithm, 3 standard deviations)
Anomaly detection learns your service's normal latency patterns — including day-of-week and time-of-day variations — and alerts only when behavior deviates meaningfully from the expected baseline.
Correlating Traces with Logs
With logInjection: true set in your tracer config (Node.js) or equivalent for other languages, every log line emitted during a traced request gets dd.trace_id and dd.span_id injected automatically.
In the Datadog Log Explorer, you can click View in Trace on any log line to jump directly to the trace that produced it. This is invaluable for debugging: you see the exact database query that was slow, the downstream service call that failed, and the log message your application emitted — all in one view.
Sampling and Cost Control
By default, Datadog's head-based sampler keeps 100% of traces during development but applies intelligent sampling in production. You can configure sampling rates per service:
tracer.init({
ingestion: {
sampleRate: 0.1, // keep 10% of all traces
rateLimit: 100 // but never more than 100 traces/sec
}
});For critical paths (checkout, payment processing), use priority sampling to ensure errors and slow traces are always kept:
span.setTag(tracer.priority.USER_KEEP); // always ingest this traceThe Datadog Agent also applies Adaptive Sampling — it automatically adjusts rates across your fleet to stay within your ingestion budget while preserving statistical accuracy.
Continuous Profiling
Enable profiling alongside APM to go from "this span took 800ms" to "this span spent 600ms in this specific function":
# Node.js
DD_PROFILING_ENABLED=true node app.js
# Python
DD_PROFILING_ENABLED=true ddtrace-run python app.pyProfiling data appears in APM → Profile Search. You can filter by service, version, and time range, then correlate profiles with specific slow traces using the trace ID.
Connecting APM to End-to-End Testing
APM tells you what is slow in production. End-to-end tests tell you what is broken before it reaches production. The two are complementary — when a synthetic or end-to-end test fails, APM traces from that test run can show exactly which service or database query caused the failure.
Tools like HelpMeTest run end-to-end tests continuously and can be used alongside Datadog APM: tests catch regressions before deploy while APM monitors behavior after deploy. When both agree — tests pass, APM looks healthy — you have genuine confidence in your service.
Summary
A production-ready Datadog APM setup requires:
- Agent deployed on every host or as a Kubernetes DaemonSet
- Tracer library initialized at application startup with
service,env, andversiontags - Unified Service Tagging so traces correlate with logs and metrics
- Custom spans on business-critical code paths
- Monitors for p99 latency, error rate, and anomaly detection
- Log injection enabled so you can jump from logs to traces
- Profiling enabled if you need line-level performance attribution
The investment pays off the first time you diagnose a production incident in 3 minutes instead of 3 hours.