AWS Lambda Monitoring with CloudWatch: Metrics, Logs, Alarms, X-Ray, and Lambda Insights
Lambda functions are short-lived and stateless, which makes them harder to monitor than a long-running server. Every invocation disappears after it runs. The only evidence it ran is what it wrote to logs, emitted as metrics, and sent to traces. This guide covers the full Lambda monitoring stack: CloudWatch metrics, structured logs, alarms, Lambda Insights, and X-Ray.
If you are new to Lambda monitoring, set up these three things first:
- An error rate alarm: fires when errors exceed a percentage of invocations
- A Throttles alarm: fires on any throttled invocations
- Structured JSON logging: makes your logs queryable from day one
Everything else on this page builds on those foundations.
What AWS Lambda monitoring means
Think of it like an airline’s black box. Metrics are the instrument panel readings that get transmitted every second. Logs are the cockpit voice recorder. Traces are the flight path replay. You rarely need all three at once, but when something goes wrong, each one answers a different question.
- Metrics tell you what changed. Error count went up; duration spiked; throttles appeared. Metrics are fast to query and easy to alarm on.
- Logs tell you what happened. The exact request, the exact error, the exact value that caused the crash. CloudWatch Logs captures everything your function writes to stdout or stderr.
- Traces show where time went. When a function calls DynamoDB, SQS, and S3, a trace shows which call was slow. See distributed tracing with X-Ray for the full picture.
- Enhanced monitoring (Lambda Insights) gives you runtime-level detail per invocation: actual memory consumed, CPU time, disk and network I/O. Standard metrics do not include any of this.
For most functions, you will spend the majority of your time in metrics and logs. Traces and Insights are worth adding when you need to go deeper on a specific problem.
What to monitor first
Six signals cover the most common Lambda failure modes. Start here before adding anything more complex.
Errors
What it is: invocations that ended with an unhandled exception.
Why it matters: a rising error count usually means broken code, a bad dependency, or a downstream service that started failing.
What bad looks like: any non-zero value during normal traffic; a spike above 1–2% error rate during peak.
First action: create an error rate alarm (errors / invocations > 5%). Then check CloudWatch Logs for the stack trace.
Duration (p95 and p99)
What it is: how long each invocation ran, measured in milliseconds.
Why it matters: slow functions cost more and degrade user experience. The p99 percentile catches outliers that the average hides.
What bad looks like: p99 consistently approaching your timeout, or a sudden jump in p95 without a corresponding traffic increase.
First action: set your function timeout to 2–3x the normal p99. Alarm when p99 crosses that threshold.
Throttles
What it is: invocations rejected because the function hit its concurrency limit.
Why it matters: throttled requests are silently dropped by API Gateway (returns 429) and retried by SQS; neither shows up in your Errors metric.
What bad looks like: any throttle count above zero in a production function.
First action: alarm on Throttles > 0. Investigate whether you need reserved concurrency or a higher account limit. See Lambda scaling and concurrency for the full model.
ConcurrentExecutions
What it is: the number of function instances running at the same moment.
Why it matters: every AWS account has a regional concurrency limit (default 1,000). Approach that limit and other functions start throttling.
What bad looks like: sustained concurrency above 80% of your reserved limit, or spikes that hit the account limit.
First action: chart concurrency alongside invocations. Set reserved concurrency on critical functions to protect them from noisy neighbours.
Max Memory Used
What it is: the peak memory consumed during an invocation, shown in the REPORT log line.
Why it matters: if Max Memory Used approaches the configured memory limit, the next invocation may hit an out-of-memory error and crash without a useful stack trace.
What bad looks like: Max Memory Used within 10–20% of the memory limit.
First action: create a log-based metric to track Max Memory Used over time, then alarm when it crosses 85% of your limit.
Cold starts
What it is: the extra initialisation time when Lambda starts a fresh execution environment.
Why it matters: cold starts add 100ms to several seconds of latency depending on runtime and package size. For user-facing APIs, this is noticeable.
What bad looks like: Init Duration regularly above 1 second; cold starts on more than 5–10% of invocations for latency-sensitive functions.
First action: filter REPORT lines for Init Duration to count cold starts. Consider provisioned concurrency for functions where cold start latency is unacceptable.
The key Lambda metrics in CloudWatch
Lambda publishes these metrics to the AWS/Lambda namespace automatically.
No configuration is required; they appear as soon as your function is invoked.
For a broader introduction to how CloudWatch collects and stores metrics, see the
CloudWatch overview.
| Metric | Unit | What it tells you | When to alarm |
|---|---|---|---|
| Invocations | Count | How many times the function was called. A sudden drop can mean traffic stopped reaching the function. | Count drops to zero unexpectedly |
| Errors | Count | Invocations that ended with an unhandled exception. Does not include throttles. | Error rate > 5% (use metric math) |
| Duration | Milliseconds | How long each invocation ran. Monitor p95 and p99 to catch slow outliers the average hides. | p99 > 80% of function timeout |
| Throttles | Count | Invocations rejected because the function hit its concurrency limit. Throttled requests are retried by SQS and Kinesis but dropped by API Gateway. | Throttles > 0 |
| ConcurrentExecutions | Count | Function instances running at the same moment. Compare against your reserved or account-level concurrency limit. | Sustained > 80% of reserved limit |
| UnreservedConcurrentExecutions | Count | Concurrent executions drawing from the shared account pool. High values can starve other functions with no reserved concurrency. | High relative to account limit |
| IteratorAge | Milliseconds | For stream-based triggers (Kinesis, DynamoDB Streams): how far behind the function is on processing. Not available for SQS-triggered functions. | Growing trend over time |
Pull these metrics via the CLI for a quick health check:
# Get error count for a function over the last hour (sum)
aws cloudwatch get-metric-statistics \
--namespace AWS/Lambda \
--metric-name Errors \
--dimensions Name=FunctionName,Value=my-function \
--start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
--period 3600 \
--statistics Sum
# Get p99 duration for the last hour
aws cloudwatch get-metric-statistics \
--namespace AWS/Lambda \
--metric-name Duration \
--dimensions Name=FunctionName,Value=my-function \
--start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
--period 3600 \
--extended-statistics p99How Lambda logging works
Lambda automatically sends all stdout and stderr output to a CloudWatch Logs log
group named /aws/lambda/<function-name>. Each invocation creates its
own log stream within that group.
Every invocation produces at minimum three system log lines:
- START: the request ID and Lambda version that handled this invocation
- END: marks the invocation as finished
- REPORT: a summary containing Duration, Billed Duration, Memory Size, Max Memory Used, and (on cold starts only) Init Duration
Init Duration only appears on cold starts, so filtering for it identifies cold starts
without any extra instrumentation. Max Memory Used compared to Memory Size
tells you how close the function is to an out-of-memory crash. Get comfortable reading REPORT lines
before adding any other monitoring tool.
# Read the most recent log stream for a function
LOG_GROUP="/aws/lambda/my-function"
STREAM=$(aws logs describe-log-streams \
--log-group-name "$LOG_GROUP" \
--order-by LastEventTime \
--descending \
--limit 1 \
--query 'logStreams[0].logStreamName' \
--output text)
aws logs get-log-events \
--log-group-name "$LOG_GROUP" \
--log-stream-name "$STREAM" \
--limit 50 \
--query 'events[*].message' \
--output textStructured logging
Plain text logs work until you need to query them. When a function runs thousands of times per day, searching free text for a specific order ID or error type becomes slow and unreliable. Structured logs output each event as JSON, which CloudWatch Logs Insights can filter, aggregate, and compute across millions of lines.
Useful fields to include in every structured log event:
request_id: links the log event to the Lambda REPORT line and X-Ray tracelevel: INFO, WARN, or ERROR. Lets you filter noise out of queries.message: a short, consistent event name you can group on- Any business identifiers relevant to the invocation (order_id, user_id, batch_id)
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def handler(event, context):
order_id = event.get('order_id')
logger.info(json.dumps({
"level": "INFO",
"message": "order.processing",
"order_id": order_id,
"request_id": context.aws_request_id,
"function_name": context.function_name
}))
# ... business logic ...
logger.info(json.dumps({
"level": "INFO",
"message": "order.complete",
"order_id": order_id,
"duration_ms": 45
}))Once logs are structured, you can run queries like this in Logs Insights:
fields @timestamp, order_id, duration_ms
| filter message = "order.complete"
| stats avg(duration_ms), max(duration_ms) by bin(5m)For deeper patterns including correlation IDs across services, see the structured logging guide.
How alerting works for Lambda
An error count alarm fires when a specific number of errors occurs in a period. An error rate alarm fires when errors exceed a percentage of total invocations. Rate alarms are more reliable because one error on a function that runs twice per day is very different from one error on a function that runs 10,000 times per day.
The cleanest way to build a Lambda error rate alarm is with a CloudWatch metric math expression:
# Create an alarm that fires when error rate exceeds 5% over 5 minutes
aws cloudwatch put-metric-alarm \
--alarm-name "lambda-my-function-error-rate-high" \
--alarm-description "Fires when Lambda error rate exceeds 5%" \
--metrics '[
{
"Id": "errors",
"MetricStat": {
"Metric": {
"Namespace": "AWS/Lambda",
"MetricName": "Errors",
"Dimensions": [{"Name": "FunctionName", "Value": "my-function"}]
},
"Period": 300,
"Stat": "Sum"
},
"ReturnData": false
},
{
"Id": "invocations",
"MetricStat": {
"Metric": {
"Namespace": "AWS/Lambda",
"MetricName": "Invocations",
"Dimensions": [{"Name": "FunctionName", "Value": "my-function"}]
},
"Period": 300,
"Stat": "Sum"
},
"ReturnData": false
},
{
"Id": "error_rate",
"Expression": "errors / invocations * 100",
"ReturnData": true
}
]' \
--comparison-operator GreaterThanThreshold \
--threshold 5 \
--evaluation-periods 1 \
--alarm-actions arn:aws:sns:us-east-1:123456789012:on-call-alertsRecommended first alarms
These four alarms catch the most common Lambda failure modes. Set them up before going deeper with dashboards or custom metrics.
High error rate. Errors / invocations > 5% over 5 minutes. Worth creating first because it catches broken code, broken dependencies, and downstream service failures all at once.
Throttles > 0. Any throttled invocation means traffic is being dropped or delayed. Throttle issues tend to appear suddenly at traffic spikes and are invisible in the Errors metric.
Sustained p99 duration increase. Alarm when p99 exceeds 80% of your timeout for two consecutive evaluation periods. Catches slow degradation before it becomes a timeout.
Concurrency near limit. Alert when ConcurrentExecutions exceeds 80% of your reserved limit for 5 minutes. Gives you warning before throttling starts. See Lambda scaling and concurrency for how concurrency limits work across functions and accounts.
For more alarm patterns including composite alarms and anomaly detection, see creating CloudWatch alarms.
CloudWatch vs Lambda Insights vs X-Ray
Each tool answers a different question. You do not need all three for every function.
| Tool | What it answers | When it is enough | Cost |
|---|---|---|---|
| CloudWatch metrics | Is the function failing, slow, or throttling? | For most production functions. Covers the top failure modes at no extra cost. | Free (included with Lambda) |
| Lambda Insights | Is the function using too much memory or CPU? What is the actual memory used per invocation? | When you need per-invocation system metrics to diagnose performance or right-size memory allocation. | Adds ~10–20ms billed duration per invocation; CloudWatch Logs costs for the extra events |
| X-Ray | Where is time being spent across the full call chain? Which downstream service is slow? | When your function calls multiple downstream services and you need to trace latency across all of them. | Per-trace pricing; sampled at 5% by default in passive mode |
Use CloudWatch alone for simple functions with one or two downstream calls. The built-in metrics cover errors, duration, and concurrency without any setup.
Add Lambda Insights when you are optimising function memory or investigating unexplained duration increases. Insights tells you whether duration is coming from your code (CPU time) or from waiting on I/O, which standard metrics cannot distinguish.
Add X-Ray when your function is part of a distributed system and you need to pinpoint which service call added latency. See the distributed tracing guide for how to propagate trace context across service boundaries.
Use all three for business-critical functions that call multiple services, require low-latency guarantees, and need deep runtime visibility.
Lambda Insights
Lambda Insights is an optional enhanced monitoring layer that collects system-level metrics per invocation: CPU time consumed, memory used at peak, disk bytes read/written, and network bytes in/out. Standard Lambda metrics give you duration and memory limit but not actual memory consumed.
Lambda Insights works by adding a Lambda layer to your function. The layer runs after your
handler and writes a structured log event to a separate log group
(/aws/lambda-insights). CloudWatch parses these events into metrics you can
chart and alarm on.
# Add Lambda Insights layer to a function (replace region and account as needed)
aws lambda update-function-configuration \
--function-name my-function \
--layers arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:38
# Lambda Insights also requires a managed policy on the function's execution role
aws iam attach-role-policy \
--role-name my-function-execution-role \
--policy-arn arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicyLambda Insights adds roughly 10–20ms to each invocation because the layer writes its metrics after your handler returns. This counts toward your billed duration. For functions running under 50ms, the overhead is proportionally large. Weigh this against the value of per-invocation memory and CPU data before enabling it on every function.
X-Ray tracing for Lambda
Enabling X-Ray on a Lambda function adds a trace segment for each invocation, showing total duration, downstream AWS service calls, and cold start initialisation time.
# Enable active X-Ray tracing on a Lambda function
aws lambda update-function-configuration \
--function-name my-function \
--tracing-config Mode=ActiveWith active tracing enabled, the X-Ray console shows:
- Initialization: only present on cold starts; shows how long the runtime took to initialise
- Invocation: the time your handler ran
- Overhead: Lambda’s post-invocation cleanup
- Subsegments for any AWS SDK calls your handler made (DynamoDB, SQS, S3, and others)
For distributed systems where Lambda calls other services, X-Ray traces the complete request path. See the distributed tracing guide for how trace propagation works across service boundaries.
Monitoring cold starts
A cold start happens when Lambda must start a fresh execution environment because no warm environment is available. Cold starts add latency ranging from tens of milliseconds (simple Python or Node.js functions) to several seconds (large Java or .NET runtimes with heavy dependencies).
A cold start does not increment Errors or Throttles. It just makes the invocation slower. Without a metric filter on REPORT lines or Lambda Insights enabled, you will not see cold starts in any default CloudWatch chart. You have to go looking for them.
Cold starts matter most in these situations:
- API Gateway-backed Lambda. Users experience the extra latency directly. A 2-second cold start on the first request of the day is a visible UX problem.
- Bursty workloads. Traffic that scales from zero quickly forces many simultaneous cold starts. Scale-from-zero patterns in event-driven architectures are a common culprit.
- Java and .NET functions. JVM startup and .NET runtime initialisation are significantly slower than Python or Node.js. Cold start duration scales with dependency size.
- Provisioned concurrency decisions. If cold starts are consistently above an acceptable threshold, provisioned concurrency keeps environments warm at a predictable cost.
To track cold starts, filter CloudWatch Logs for the Init Duration field in
REPORT lines using a metric filter. See log-based metrics
for how to extract Init Duration as a queryable metric:
# Create a metric filter that counts cold starts
aws logs put-metric-filter \
--log-group-name "/aws/lambda/my-function" \
--filter-name "cold-starts" \
--filter-pattern "[report_label=\"REPORT\", ..., init_label=\"Init\", init_duration_label=\"Duration:\", init_duration_ms, ...]" \
--metric-transformations \
metricName=ColdStarts,metricNamespace=CustomLambda/my-function,metricValue=1,defaultValue=0Once you have a cold start metric, you can alarm on it or track it over time to measure the effect of optimisation changes like provisioned concurrency or reducing package size.
When to use this monitoring setup
Different Lambda workloads have different failure modes. Here is what to prioritise for the most common patterns.
API Gateway-backed Lambda
Watch error rate, p99 duration, and cold starts. API Gateway drops throttled requests with a 429 rather than retrying, so a Throttles alarm is critical here. Cold start monitoring matters more than almost anywhere else in this pattern. See Lambda fundamentals for how the execution model affects API response time.
SQS consumers
Watch Errors and queue depth. SQS retries on failure, so errors cause messages to retry until they hit the dead-letter queue. Monitor DLQ depth alongside Lambda errors to catch runaway retry loops before they drain your concurrency.
Scheduled jobs
Watch for zero invocations (the schedule may have broken), non-zero errors, and duration creeping toward the timeout. Scheduled functions often have no users waiting, so failures go unnoticed for hours. An invocation count alarm that fires on zero is simple and valuable.
Event-driven processing
Watch concurrency, throttles, and error rate together. Event-driven architectures can generate sudden bursts that exhaust concurrency. For the full picture of how Lambda handles events from S3, SNS, and EventBridge, see event-driven compute with Lambda.
Functions with unpredictable concurrency
Watch ConcurrentExecutions relative to your reserved or account limit. Functions without reserved concurrency draw from the shared account pool and can be starved by other functions. For right-sizing concurrency configuration and understanding cost implications, see Lambda cost optimisation.
Common mistakes
Alarming on error count instead of error rate. A single error on a function that runs twice per day is very different from a single error on a function that runs 10,000 times per day. Alarm on error rate (errors / invocations) to get context-aware alerts.
Not monitoring Throttles. Throttles are silent failures for many invocation sources. API Gateway returns a 429. SQS retries. Either way, your users or processing is affected and the Errors metric stays at zero.
Ignoring Max Memory Used in REPORT lines. If Max Memory Used is close to the configured memory limit, the function is at risk of an out-of-memory crash. Increase the memory allocation or investigate memory leaks before it happens in production.
Using print() instead of a logger in Python.
print()goes to stdout and is captured by Lambda, but using theloggingmodule adds log levels and makes it far easier to filter noise in CloudWatch Logs Insights queries.Not separating timeout errors from application errors. A function that times out appears in the Errors metric, but the root cause is duration, not a code exception. Filter logs for
Task timed out afterto separate timeout errors from application errors. For a systematic approach, see debugging Lambda failures.Setting the same alarm thresholds for all functions. A latency-sensitive API function needs tighter p99 alarms than a background batch job. Tune thresholds per function based on observed baselines, not generic defaults.
Summary
- Lambda automatically publishes Invocations, Errors, Duration, Throttles, and ConcurrentExecutions to CloudWatch with no configuration required
- Start with an error rate alarm and a Throttles alarm before adding anything more complex
- Lambda logs go to
/aws/lambda/function-nameautomatically; REPORT lines show duration, memory, and cold start timing - Structured JSON logs make CloudWatch Logs Insights queries far more powerful
- Lambda Insights adds system-level per-invocation metrics (actual memory, CPU, disk, network) via a Lambda layer
- Enable X-Ray active tracing to see the full invocation timeline including downstream service calls and cold start initialisation time
- Different workloads (API Gateway, SQS, scheduled, event-driven) have different priority signals; match your alarms to your architecture
Frequently asked questions
What should I alert on first for Lambda?
Start with an error rate alarm (Errors / Invocations) and a Throttles alarm. These two cover the most common failure modes: your function crashing and your function being rejected before it even runs. Add a p99 duration alarm once you know what a normal baseline looks like.
What is the difference between CloudWatch and Lambda Insights?
CloudWatch metrics (Invocations, Errors, Duration, Throttles) are built-in and free. Lambda Insights is an optional add-on that gives you memory used per invocation, CPU time, disk I/O, and network I/O. Use Insights when the standard metrics cannot explain why a function is slow or expensive.
When should I enable X-Ray for Lambda?
Enable X-Ray when your Lambda function calls other AWS services (DynamoDB, SQS, API Gateway) and you need to trace where time is spent across the full call chain. For functions that do one simple thing, CloudWatch metrics and structured logs are usually enough.
How do I monitor Lambda cold starts?
Cold starts appear as Init Duration in Lambda REPORT log lines. Create a CloudWatch metric filter on your log group to extract Init Duration as a custom metric, then chart or alarm on it. Lambda Insights also captures cold start data automatically.
Do I need structured logging for small Lambda functions?
Not strictly, but it is worth adopting early. Unstructured logs are fine until you need to query across invocations or aggregate values. Structured JSON logs take one extra line of code and make CloudWatch Logs Insights queries far more powerful as your function scales.