Back to Blog
Lesson 31 of the AWS: AWS Core Services for Developers course
Cloud NativeAugust 8, 20264 min read

Debugging Serverless Applications: A Practical Developer's Guide

Master systematic debugging for serverless. Learn to correlate CloudWatch logs with X-Ray traces and debug Lambda functions locally to resolve production issues.

AWSLambdaDebuggingServerlessCloudWatchX-RayTroubleshooting
A laptop screen showing a code editor with visible programming code in a dimly lit environment.

Previously in this course, we explored Distributed Tracing with AWS X-Ray: A Serverless Debugging Guide to visualize request flows. Now that you can see where latency or failures occur, this lesson adds the "how-to" of resolution: correlating those traces with logs and establishing a local feedback loop to fix bugs faster.

The Anatomy of a Serverless Debugging Workflow

In a distributed system, bugs aren't always where the error message appears. A 500 error in your API Gateway might be triggered by a timeout in your Lambda, which was caused by a slow DynamoDB query.

Effective debugging requires moving through three layers of visibility:

  1. The Symptom: What did the user see? (API Gateway 5xx errors).
  2. The Trace: Where did it fail? (X-Ray identifies the specific Lambda invocation that timed out).
  3. The Log: Why did it fail? (CloudWatch Logs provide the stack trace or execution context).

Correlating Logs and Traces

Every X-Ray trace segment contains a Trace ID. When you log from your Lambda function, you should include this ID. If you use the AWS SDK for JavaScript, the aws-xray-sdk automatically injects the Trace ID into the environment.

When you find a failed request in the X-Ray console, click the "View Logs" button. This automatically filters CloudWatch Logs to the exact time window and request ID associated with that trace. If you aren't seeing your logs, verify that your Lambda execution role has the logs:PutLogEvents permission—a classic troubleshooting oversight.

Debugging Lambda Code Locally

While cloud-based debugging is essential, the "deploy-wait-test" loop is slow. The most efficient best practices involve testing logic locally before pushing to AWS.

We use the AWS SAM CLI to simulate the Lambda environment.

Worked Example: Local Invocation

Assume your function processOrder expects an event object. Instead of deploying, create a test-event.json file:

JSON
{
  "orderId": "12345",
  "item": "keyboard"
}

Now, run the function locally using the SAM CLI:

Bash
sam local invoke -e test-event.json

This command pulls the Lambda container image (mimicking AWS's runtime) and executes your function code locally. You can use the --debug flag to get verbose output or even attach a debugger like VS Code to step through your code line-by-line.

Hands-On Exercise: The "Failed Request" Hunt

In our ongoing web app project, we have a Lambda that saves user data. Let's simulate a failure:

  1. Inject a Bug: Temporarily modify your Lambda code to throw an error if the user input is empty (e.g., if (!event.body) throw new Error("Invalid Input");).
  2. Trigger the Error: Use the API Gateway console to send a request with an empty body.
  3. Find the Trace: Go to the X-Ray console. Locate the red-colored (failed) trace.
  4. Correlate: Click the trace, find the Lambda segment, and click the link to CloudWatch Logs.
  5. Analyze: Observe the stack trace in the logs. Confirm it matches the error you injected.

Common Pitfalls

  • Assuming Code is the Culprit: Often, the issue is infrastructure (e.g., the Lambda doesn't have permission to write to DynamoDB). Check your IAM policies before rewriting your code.
  • Log Overload: Logging every single object can make it impossible to find the signal. Use structured logging (JSON) so you can query logs using CloudWatch Logs Insights (e.g., fields @timestamp, @message | filter status = 'error').
  • Environment Drift: Your local machine might have different node versions or environment variables than the production Lambda. Always use the same runtime version locally as defined in your CDK stack.

FAQ

Q: Can I use breakpoints in production? A: No. Serverless functions are ephemeral. Use console.log() statements for production troubleshooting, but ensure you include enough context (request IDs, input parameters) to make them useful.

Q: Why don't I see logs for my failed requests? A: This usually happens if the Lambda didn't even start—check API Gateway logs or check if the Lambda execution role lacks CloudWatch log group creation permissions.

Recap

Debugging in serverless is about reducing the distance between the failure and the fix. By correlating X-Ray traces with CloudWatch logs, you identify the "where." By using sam local invoke, you gain the ability to step through your code and verify the "why" without constant redeployments.

Up next: Optimizing Lambda Performance — where we'll look at how to interpret the execution time data we just learned to debug.

Similar Posts