Datadog APM Complete Guide: Distributed Tracing, Service Maps, and Alerting

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=7

The 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-trace

Add 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 ddtrace

Use ddtrace-run to wrap your process:

DD_SERVICE=my-service DD_ENV=production DD_VERSION=1.4.2 ddtrace-run python app.py

Or 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
    pass

Java

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.jar

The 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: p99 of Duration
  • 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 trace

The 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.py

Profiling 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:

  1. Agent deployed on every host or as a Kubernetes DaemonSet
  2. Tracer library initialized at application startup with service, env, and version tags
  3. Unified Service Tagging so traces correlate with logs and metrics
  4. Custom spans on business-critical code paths
  5. Monitors for p99 latency, error rate, and anomaly detection
  6. Log injection enabled so you can jump from logs to traces
  7. 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.

Read more

Start now free