Observability for AWS SDK Applications
Your application logs tell you what your code thinks it did. They rarely tell you what AWS actually did in response.
Search across all documentation pages
Your application logs tell you what your code thinks it did. They rarely tell you what AWS actually did in response.
A call to s3.get_object or DynamoDBClient.send can retry twice, wait on a throttle, and still return in 40ms - or take 4 seconds doing exactly the same logical thing. Without instrumentation around the call, both look identical in a plain console.log or print statement.
This page frames what "observability" means specifically for AWS SDK calls, and previews the tools the rest of this section covers in depth.
Observability for SDK calls rests on three pillars, and each answers a different question.
Structured logs answer "what happened, in what order, with what outcome." A log line with a request id, a duration, and a status code is queryable and correlatable across services.
Distributed traces answer "where did the time go, and across which services." A trace stitches together the spans (or subsegments) contributed by every hop a request takes, including the AWS SDK call itself.
Metrics answer "how is this behaving in aggregate, over time." Call counts, error rates, and p99 latency per operation let you set alarms and spot trends a single log line cannot show.
AWS gives you two native ways to get traces: AWS X-Ray, AWS's own tracing service with SDK-level auto-instrumentation, and OpenTelemetry (OTel), the vendor-neutral standard with its own AWS SDK auto-instrumentation packages. CloudWatch Application Signals sits on top of either, turning the trace data into service maps and SLO-style dashboards.
Here is the minimal shape: log a request id and latency around an SDK call, in both languages.
# --- Python (boto3) ---
import time
import logging
import boto3
logger = logging.getLogger("orders-service")
s3 = boto3.client("s3")
start = time.monotonic()
response = s3.head_object(Bucket="my-bucket", Key="report.pdf")
duration_ms = (time.monotonic() - start) * 1000
request_id = response["ResponseMetadata"]["RequestId"]
logger.info(
"s3.head_object ok",
extra={"request_id": request_id, "duration_ms": duration_ms},
)// --- TypeScript (AWS SDK v3) ---
import { S3Client, HeadObjectCommand } from "@aws-sdk/client-s3";
const logger = console; // stand-in for a structured logger
const s3 = new S3Client({});
const start = Date.now();
const response = await s3.send(new HeadObjectCommand({ Bucket: "my-bucket", Key: "report.pdf" }));
const durationMs = Date.now() - start;
logger.info(JSON.stringify({
msg: "s3.HeadObject ok",
requestId: response.$metadata.requestId,
durationMs,
}));Neither snippet logs the object body or bucket policy - only metadata about the call itself. That distinction matters more than it looks.
The reason plain application logs fall short is that an SDK call is not one thing - it can be several HTTPS requests wearing one method-call disguise.
A single list_objects_v2 behind a paginator issues one request per page. A throttled PutItem retries transparently, each attempt with its own latency and possibly its own request id. If you only log "wrote item, ok" after the call returns, you cannot see any of that internal structure.
Structured logging fixes the "what happened" half of the problem: consistent fields (request_id, operation, duration_ms, outcome) that a log aggregator can filter and graph. It does not, by itself, show you where in the whole request chain the time went if your order service calls a payments service that calls DynamoDB.
That is what tracing adds. X-Ray and OpenTelemetry both work the same way conceptually: wrap the SDK call in a span (OTel) or subsegment (X-Ray), which records its own start time, end time, and metadata, and nests under a parent span for the whole request. Auto-instrumentation packages - aws-xray-sdk-core's captureAWSv3Client, opentelemetry-instrumentation-botocore, @opentelemetry/instrumentation-aws-sdk - do this automatically for every SDK call, without you wrapping each one by hand.
Application Signals then consumes that trace data (from X-Ray or OTel) and renders it as a service map: which services call which, with latency and error rate on each edge. It turns "somewhere in this call graph something is slow" into a specific, highlighted edge.
The trade-off across all of this is volume versus signal.
Logging every field of every request would flood your log store and could leak sensitive data - AWS's own guidance, and the security-best-practices section of this cookbook, are explicit that request/response bodies can carry secrets, tokens, or PII and should never be logged wholesale. Log identifiers and outcomes, not payloads.
Tracing every request at 100% sampling in a high-throughput service can add real cost and overhead. Most teams sample: trace every request in low-traffic services, and a percentage (with 100% of errors) in high-traffic ones.
A common mistake is picking one tool and assuming it replaces the others. It does not. Structured logs are still how you correlate a specific failed request to its inputs after the fact. Traces are how you find where time went across services. Metrics are how you know something changed before a single request even shows up in your logs.
The rest of this section walks each layer in order: structured logging first, since it costs the least to add and pays off immediately, then X-Ray, then OpenTelemetry as the vendor-neutral alternative, then Application Signals as the view built from either.
Logging records discrete events (a call happened, with this outcome and duration). Tracing connects those events into a tree spanning multiple services, showing where time was spent across the whole request.
You need one tracing approach, not both at once for the same service. X-Ray is AWS-native and simplest inside AWS; OpenTelemetry is vendor-neutral and better if you also have non-AWS dependencies or want portability.
Full request or response bodies, credentials, tokens, and any field that could carry customer PII. Log the operation name, request id, duration, and outcome instead.
No, it is built on top of X-Ray (and OTel) trace data. It adds service maps and SLO-style dashboards derived from traces you are already emitting.
A request id and a duration, logged with the operation name and outcome. That alone turns "it felt slow" into a specific, comparable number.
A small amount, from the logging and trace-context overhead. Sampling in tracing and keeping log fields minimal keeps that overhead well under the cost of debugging blind.
Stack versions: This page was written for boto3 1.43.x (Python 3.10+) and the AWS SDK for JavaScript v3 (Node.js 18+).
Reviewed by Chris St. John·Last updated Jul 23, 2026