Debugging serverless functions locally and using CloudWatch
Learning to develop serverless in a workshop seems easy, but we all know that when we start building them it in production, things somehow get a lot harder. And one of the reasons lies in our own mistakes, the unknown errors that we won't see initially, but then might cost us much.
You might wonder how do we debug serverless apps if “there is no server” we can connect to. Where are the logs?
Our client "Sauf Pompiers" SARL, won't forgive us if we mess up that badly, so we need to be ready to jump in and fix, as they have an SLA (Service Layer Agreement) of 99.95%. Meaning that their downtime is less than 0.05% per month. Ouch!
Debugging a serverless app with CloudWatch console
Before we do anything let's first introduce a temporary error. Let's see how to do that with out Hello World Function code:
exports.handler = async (event) => {
throw new Error('an error from AWS Lambda');
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};
Now after we need to deploy this, so please run sam deploy
.
Then invoke the Lambda through its API.
You should receive an HTTP status 500.
Now to debug, we will first use CloudWatch, the AWS monitoring and observability service. CloudWatch captures logs, metrics, and events coming from your serverless applications. Specifically for AWS Lambda it creates a Log group for each and every Lambda function and separate Log entries for each deployment.
To access CloudWatch, go to the CloudWatch Console
Then click Logs and then click Log Groups. You should see a page containing a table with some if items (in case it's an empty account, you might see only one). If you're using a company account you might see a lot of these entries, so to find the one you deployed, type in /aws/lambda/
then the name of your CloudFormation stack, a dash -
and then your function name.
Then, click on the Log group and you should a new page consisting of a table of Log Streams. A single Log Stream entry contains all of your log entries from that Lambda Function's single deployment. It is important to remember, that if you didn't deploy again, the same Log entry may contain the logs from multiple Lambda executions.
Now to find the last entry, sort by Last Event Time and then click on the top one. You should see (yet another!) table with multiple lines representing all the events the AWS CloudWatch service stored for your single Lambda function, its single Log group, single Log entry from a single deployment (too many singles).
You should see the error you created in one of the Log event entries. Feel free to click on it to expand. You've done a first CloudWatch Console debug session on an AWS Lambda, isn't it annoying already?
To make it even more annoying, you have a task to do.
Task #1
- Create an error in one of your Expenses functions
- Deploy the stack
- Call its API endpoint
- Debug the error through your AWS CloudFormation console
Note
When you've finished, continue reading this chapter!
Local debug of serverless applications
As you could already notice, the way of debugging serverless applications by deploying, running and then checking the CloudWatch Logs is pretty annoying. Before we could debug apps locally and then deploy them, so why can't we do it now too?
Luckily for us, we can!
But, to be able to do that we need to do a little setup, because we need to install LambCI, a CI built on AWS Lambda (by another AWS Serverless Hero, Michael Hart).
Prerequisites
Docker
Before we continue, you will need to have Docker installed. So please,before continuing, have it installed.
Setup your tool's IDE
Important We will we only covering the Visual Studio Code's setup and debug, but it can be useful for comparing to your own IDE's setup.
For Visual Studio Code, open the Debug tab. Then click to create a launch.json
. Pick the one you are using and add something similar to the following example:
{
"version": "0.2.0",
"configurations": [
{
"name": "YOUR_PROJECT_NAME_YOU_PUT_AS_EVENT",
"type": "node",
"request": "attach",
"address": "localhost",
"port": 8888,
// Location to where the transpiled JS file is: follows CodeUri
"localRoot": "${workspaceRoot}/",
"remoteRoot": "/var/task",
"protocol": "inspector",
"stopOnEntry": false,
// Same as LocalRoot given we run on a docker container
// outFiles allows VSCode debugger to know where the source code is after finding its sourceMap
"outFiles": [
"${workspaceRoot}/index.js",
],
// instructs debugger to use sourceMap to identify correct breakpoint line
// and more importantly expand line/column numbers correctly as code is minified
"sourceMaps": true
}
]
}
Now before we try, we also need to setup your Lambda Function's test event. This test event is required because when we trigger our function, we need to send it an actual event. The main reason is that LambCI simulates an identical environment, as similar as possible to a real Lambda environment.
Instead of copy/pasting this event from CloudWatch, there is a handy SAM command:
sam local generate-event apigateway aws-proxy
The generate-event
command can create a whole bunch of other Lambda events. You can read more about the sam local generate-event
command here
Copy it and save it in a file. Then you should put the event inside your tests
folder, and a separate folder called test-events
or something similar. When you have created your test event, its time to try it out!
To try, run:
sam local invoke YOUR_PROJECT_NAME_YOU_PUT_AS_EVENT --event tests/test-events/event-deploy.json --profile default --region us-east-1 --debug-port 8888
Note: Environment varialbes
If you want to pass environment variables to this local invocation of your Lambda function, you need to add an env
parameter flag and pass it the location to the JSON file containing the enviroment variable values.
Something like the following line:
sam local invoke YOUR_PROJECT_NAME_YOU_PUT_AS_EVENT --event tests/test-events/event-deploy.json --env-vars env.json --profile default --region us-east-1
The file contents should looks something like this:
{
"YOUR_PROJECT_NAME_YOU_PUT_AS_EVENT": {
"PARAM_1": "param-1-value",
"PARAM_2": "param-2-value"
}
}
That's it! Now you know how to locally debug your Lambda Functions.
Task #2
- Create test events for all 3 Expense functions
- Add a debug breakpoint for each three
- Run and check if the breakpoints stop the function execution