Distributed Tracing in AWS: X-Ray, Sampling, and Troubleshooting
Distributed tracing records the full journey of a request as it moves through every service in your system, with timing data at each step. When something is slow or broken in a multi-service app, it tells you exactly which service is to blame. This page covers AWS X-Ray end to end: how tracing works, how to enable it, how to read a trace, and how to choose between X-Ray and OpenTelemetry.
Simple explanation
Imagine your request is a package moving through a delivery network. Without tracing, you only know the package left the warehouse and eventually arrived (or did not). With tracing, every scan at every facility is recorded: departed warehouse 9:02 AM, arrived sorting hub 10:15 AM, delayed at customs for three hours, delivered 2:30 PM. You see exactly where the delay happened and for how long.
Distributed tracing does the same for requests. Each service records a timestamp, its duration, and whether it succeeded. AWS X-Ray collects all those records and assembles them into one timeline.
This is especially valuable in microservices architectures, where a single user action can trigger five or ten separate service calls. Without tracing, you are left manually correlating timestamps across multiple log groups, hoping the clocks align.
Why distributed tracing matters
A typical request might touch an API Gateway, a Lambda function, a DynamoDB table, and an external payment API. If the user sees a five-second response time, which hop caused it?
CloudWatch metrics show each service in isolation. CloudWatch Logs show each service’s text output. Neither one shows you the chain. You would need to manually correlate timestamps across multiple log groups and hope they tell a coherent story.
Distributed tracing solves this directly. Open one trace and see: API Gateway took 5ms, Lambda initialization took 300ms, the DynamoDB query took 4,600ms, the external API took 80ms. The culprit is obvious without any manual correlation.
AWS X-Ray also generates a service map: a live diagram of all services and the connections between them, with latency and error rates on each edge. This is useful not just for debugging but for understanding what your system actually looks like after months of changes.
How distributed tracing works in AWS
Here is a concrete end-to-end example using a common AWS pattern:
- API Gateway receives the user request and generates a unique trace ID. It adds the ID to an HTTP header called
X-Amzn-Trace-Id, then forwards the request to Lambda. - Lambda reads the trace header, records a segment (its own start time, end time, and result), and makes a call to DynamoDB through the AWS SDK. The SDK automatically creates a subsegment for the DynamoDB call and passes the trace ID along.
- DynamoDB is a managed service, so X-Ray records the call as a subsegment on Lambda’s segment. You see the query duration directly on the Lambda timeline, not as a separate entry.
- Lambda also calls an external payment API over HTTPS. The X-Ray SDK creates a subsegment for the outbound HTTP call and records its duration and response status.
- X-Ray collects all segments and subsegments, links them by trace ID, and assembles the full trace. You see a Gantt-style timeline: the full request took 680ms, of which 550ms was the DynamoDB query.
Think of the trace ID like a baton in a relay race. The first runner (API Gateway) picks it up and passes it to the next runner (Lambda), who passes it to the next (DynamoDB call), and so on. X-Ray is the camera crew filming every handoff. If a runner drops the baton or runs slowly, you see exactly which leg of the race caused the problem.
The AWS SDK handles trace propagation automatically for supported services like DynamoDB, SQS, SNS, and S3. You only need to write custom instrumentation code for external HTTP calls or specific code blocks you want to measure separately.
Core concepts: traces, segments, and subsegments
X-Ray uses a three-level hierarchy to organize trace data:
Trace
A trace is the complete record of one request from start to finish, identified by a unique 96-bit trace ID that is propagated as an HTTP header across every service the request passes through. By collecting all segments that share the same trace ID, X-Ray assembles the full picture.
Segment
A segment is one service’s contribution to the trace. Each Lambda function, ECS task, or EC2 instance creates a segment when it handles the request. The segment records: service name, start and end time, whether it succeeded or faulted, and any annotations or metadata you attach.
Subsegment
A subsegment is a unit of work inside a segment. A DynamoDB query, an outbound HTTP call, or a custom code block wrapped with the SDK each appear as subsegments nested inside the parent segment. They let you see not just “Lambda took 800ms” but “Lambda took 800ms, of which 650ms was a DynamoDB GetItem call.”
Annotations and metadata
X-Ray lets you attach extra data to any segment. Annotations are indexed key-value pairs you can filter on in the console, useful for customer ID, order ID, or feature flag names. Metadata is arbitrary JSON stored but not indexed, useful for larger debugging details you want to inspect manually but do not need to search on.
from aws_xray_sdk.core import xray_recorder
# Searchable annotation — filterable in the X-Ray console
xray_recorder.current_segment().put_annotation('customer_id', '12345')
# Non-indexed metadata — stored but not searchable
xray_recorder.current_segment().put_metadata('order_details', {
'items': 3,
'total': 49.99
})How trace propagation works
Propagation is what keeps all segments linked to the same trace across service boundaries. X-Ray
uses an HTTP header called X-Amzn-Trace-Id:
X-Amzn-Trace-Id: Root=1-5e272119-2c3c0ade7f1a9f1234567890;Parent=53995c3f42cd8ad8;Sampled=1When a request enters through API Gateway, X-Ray generates the root trace ID. API Gateway adds the header before forwarding to Lambda. Lambda reads the header, creates its segment linked to that trace ID, then passes the same header when calling downstream services. Each downstream service does the same.
The Sampled=1 flag tells downstream services whether to record their segments.
If the value is 0, they skip recording. The sampling decision is made once at
the entry point and flows to every downstream service automatically.
Sampling rules
X-Ray does not trace every request. The default rule traces the first request each second, then 5% of additional requests. You can define custom sampling rules that match on service name, URL path, HTTP method, or other attributes to trace more (or fewer) requests for specific paths.
# List current sampling rules
aws xray get-sampling-rules
# Create a rule that traces 20% of POST requests to /checkout
aws xray create-sampling-rule --cli-input-json '{
"SamplingRule": {
"RuleName": "checkout-tracing",
"ResourceARN": "*",
"Priority": 1,
"FixedRate": 0.20,
"ReservoirSize": 5,
"ServiceName": "payment-service",
"ServiceType": "*",
"Host": "*",
"HTTPMethod": "POST",
"URLPath": "/checkout",
"Version": 1
}
}'It sounds useful to trace everything, but at high traffic volumes it generates enormous data, adds measurable latency, and produces a large bill. The default 5% rule plus a targeted custom rule for your most important paths gives you everything you need in practice.
For a deeper look at X-Ray features beyond the basics, see the AWS X-Ray Overview, which covers groups, insights, and pricing.
Enabling tracing in AWS services
How you enable X-Ray depends on where your code runs. This also varies depending on which compute option you are using.
Lambda
Set TracingConfig.Mode to Active on the function. No code changes
are needed for basic tracing: Lambda automatically creates segments and passes the trace header
to any downstream calls made through the AWS SDK. For a complete picture of Lambda observability,
see monitoring Lambda with CloudWatch.
# Enable active tracing on an existing Lambda function
aws lambda update-function-configuration \
--function-name my-api-handler \
--tracing-config Mode=ActiveAPI Gateway
Enable X-Ray tracing on the stage. API Gateway records its own segment and injects the
X-Amzn-Trace-Id header into the request before forwarding to your integration.
This is the entry point that starts the trace chain.
# Enable X-Ray on an API Gateway stage
aws apigateway update-stage \
--rest-api-id abc123 \
--stage-name prod \
--patch-operations op=replace,path=/tracingEnabled,value=trueECS and EC2
Run the X-Ray daemon as a sidecar container (ECS) or a background process (EC2). Your application SDK sends trace segments to the daemon over UDP port 2000. The daemon batches them and forwards them to X-Ray.
If the X-Ray daemon is not running, the SDK drops segments over UDP with no error, no log line, and no exception thrown. Your application runs fine but the X-Ray console stays empty. Always verify the daemon sidecar is healthy before concluding that tracing is broken elsewhere.
EKS
Deploy the X-Ray daemon as a DaemonSet so it runs on every node in the cluster. Your application pods send UDP packets to the daemon on port 2000. For a full guide to Kubernetes observability on AWS, see monitoring EKS.
When to use distributed tracing
Tracing is most valuable when a request touches more than one service and you need to understand the full latency breakdown across the chain.
Good fit for tracing
- Your system has multiple Lambda functions, ECS services, or microservices that call each other
- You are debugging intermittent slow requests and cannot tell which service is responsible
- You need to understand cold start impact on end-to-end latency, not just per-function latency
- You want to identify unnecessary sequential service calls that could be parallelized
- You are building SLAs and need p99/p95 latency data per service, not just per endpoint
- You want a visual dependency map as your system grows and changes over time
When logs or metrics are enough
Tracing has overhead and cost. If your system is simple, you may not need it yet:
- A single Lambda function with no downstream calls: structured logs are sufficient
- Batch jobs where success or failure counts matter but per-request latency does not
- Very early-stage apps with one or two services: add tracing before your first unexplained slowdown, not necessarily on day one
The first time you spend more than 20 minutes trying to figure out which service caused a slow request by reading logs manually. That is the moment you add tracing and never look back.
Distributed tracing vs logs vs metrics
These three tools answer different questions. Understanding the difference helps you reach for the right one quickly during an incident.
| Tool | What it answers | Granularity | Best for |
|---|---|---|---|
| Metrics | Is the system healthy? How many errors per minute? | Aggregated numbers over time | Alerting, dashboards, trend analysis |
| Logs | What happened inside this one service? | Per-event text records from one service | Debugging errors in isolation, auditing |
| Traces | Which service in the chain caused this slow or failed request? | Per-request timing across all services | Root-cause analysis in distributed systems |
In practice you use all three together. An alert on a CloudWatch metric fires. You open the X-Ray service map to find the failing service (traces). You then read that service’s structured logs to see the exact error message and context (logs).
AWS X-Ray vs OpenTelemetry
When you instrument your application for tracing, you have two main choices: the X-Ray SDK or the AWS Distro for OpenTelemetry (ADOT). Both can send data to X-Ray.
| X-Ray SDK | AWS Distro for OpenTelemetry (ADOT) | |
|---|---|---|
| Vendor lock-in | AWS-specific; sends only to X-Ray | Vendor-neutral; can send to X-Ray, Prometheus, Jaeger, Datadog, and others |
| Setup complexity | Simpler for AWS-only environments | Slightly more setup; configure exporters and the Collector |
| AWS integration | Deep integration with the AWS SDK; Lambda layers available | Full AWS integration via ADOT Lambda layers and the OpenTelemetry Collector |
| Community and ecosystem | AWS-maintained, smaller ecosystem | CNCF standard, large and growing ecosystem |
| AWS recommendation | Supported but not the primary future direction | AWS’s recommended path for new instrumentation |
Current AWS direction
AWS is actively investing in OpenTelemetry as the standard instrumentation path. The AWS Distro for OpenTelemetry (ADOT) is AWS’s own distribution of the OpenTelemetry Collector and SDKs, with native support for sending traces to X-Ray. For new projects, starting with ADOT means you are not locked to a single backend and your instrumentation will work if you add a second observability tool later.
If you are already using the X-Ray SDK, there is no urgency to migrate. Both are actively supported and both send data to the same X-Ray backend. The service map, trace viewer, and sampling rules all work the same way regardless of which SDK produced the segments.
Starting a new project on AWS only? Either option works fine. Starting a project that might run on multiple clouds, or that you might want to ship traces to Datadog or Grafana Tempo later? Start with ADOT now so you never have to rewrite your instrumentation.
Finding bottlenecks from a real trace
Once tracing is active, here is a repeatable step-by-step workflow for diagnosing a slow or failing request:
Step 1: Detect the issue
An alert fires: latency is up, error rate increased, or a user reported a slow page. Note the time window. Go to CloudWatch > X-Ray traces > Service map and set the time range to the incident window.
Step 2: Open the service map
Look for nodes with yellow or red rings. These indicate elevated error rates or latency on that service. Click the node to see its request volume, p50/p99 latency, and error breakdown (2xx, 4xx, 5xx). This narrows the problem to one or two services within seconds.
Step 3: Inspect individual traces
Switch to the Traces view and sort by duration descending. Click a slow trace to open the timeline. Look for:
- Long subsegments: a subsegment consuming most of the trace duration is the bottleneck. If it is a database call, investigate the query. If it is an outbound HTTP call, check the downstream service.
- Sequential calls that could be parallel: if your service calls three downstream APIs one after another, consider calling them concurrently to cut total latency.
- Initialization time on Lambda: the
Initializationblock appears on cold starts. A large block means runtime startup is slow. Consider provisioned concurrency for latency-sensitive functions. - Red segments: these indicate unhandled exceptions. The segment detail shows the error message and stack trace captured by the X-Ray SDK.
A trace showing 4,500ms total does not mean every service took a long time. Often one subsegment accounts for 90% of the time while everything else is near zero. Sort subsegments by duration before drawing any conclusions.
Step 4: Correlate with logs
Once you have identified the problematic service and approximate time, open its log group in CloudWatch. Using structured logging with a trace ID field lets you filter log lines directly to that specific request. This gives you the error details that the trace timeline cannot show. For a complete incident workflow, see incident response with monitoring.
Step 5: Decide next action
With the specific service, subsegment, and error message identified, you can make a targeted fix: optimize the slow query, add a cache, parallelize calls, or fix the error in the identified function. No more guessing.
# Retrieve recent traces with errors from the last hour
aws xray get-trace-summaries \
--start-time $(date -u -d '1 hour ago' +%s) \
--end-time $(date -u +%s) \
--filter-expression 'fault = true' \
--query 'TraceSummaries[*].{Id:Id,Duration:Duration,HasError:HasError}'For a complete guide to production debugging that goes beyond tracing alone, see debugging production systems in AWS.
Common beginner mistakes
Enabling tracing on Lambda but not on API Gateway. If API Gateway does not propagate the trace header, Lambda creates a new trace instead of extending the chain. Enable tracing on both services for end-to-end visibility.
Assuming the X-Ray daemon is optional on ECS and EKS. The SDK does not send segments directly to X-Ray. It sends them to the local daemon on UDP port 2000. If the daemon is not running, traces are silently dropped with no error message.
Setting sampling to 100% in production. Tracing every request generates high data volumes, adds latency overhead, and raises cost significantly. Use the default rule or a targeted custom rule for specific high-value paths.
Confusing faults, errors, and throttles in X-Ray. A fault is a 5xx server error. An error is a 4xx client error. A throttle is a 429. They display as different colors on the service map. Combining them will make your error analysis inaccurate.
Not adding annotations before incidents happen. Without annotations like customer ID or order ID, you cannot filter traces to a specific user or transaction. Add annotations for the identifiers you will need during an incident. Add them before the incident happens, not while you are already in one.
Frequently asked questions
What is distributed tracing in AWS?
Distributed tracing records the full path of a request as it moves through multiple services: Lambda, API Gateway, DynamoDB, external APIs. AWS X-Ray is the native service that does this. It assigns a unique trace ID to each request, collects timing data from every service that handles it, and lets you view the whole journey as a single timeline. This is how you find which service is slow or erroring in a microservices system.
How is tracing different from logs and metrics?
Metrics tell you a number changed (error rate is up, latency is high). Logs tell you what happened inside one service at a specific moment. Traces show how a single request traveled across all your services and how long each hop took. You need all three: metrics to detect a problem, traces to find which service caused it, and logs to understand the exact error.
When should I use X-Ray vs OpenTelemetry in AWS?
Use the AWS Distro for OpenTelemetry (ADOT) if you want vendor-neutral instrumentation that can send data to X-Ray, Prometheus, Jaeger, or any other backend. Use the X-Ray SDK if you want a simpler setup that is tightly integrated with AWS services. AWS is moving toward ADOT as its recommended instrumentation path, so new projects benefit from starting there.
Does tracing work differently for Lambda, ECS, and EKS?
Yes. Lambda enables tracing with a single config setting and no code changes. API Gateway traces the incoming request and passes the trace ID downstream. ECS and EC2 require you to run the X-Ray daemon as a sidecar container or process. EKS requires the daemon as a DaemonSet on every node. The instrumentation logic is the same across environments; only the setup varies.
How much should I sample in production?
The default X-Ray rule (first request per second, then 5% of additional requests) is a reasonable starting point for most workloads. For high-value, low-traffic paths like checkout or payment flows, trace 20 to 50 percent or more. For very high-throughput endpoints, 1 to 5 percent is usually enough. Avoid 100% tracing in production because it increases cost and latency significantly.