Debug Serverless AWS Lambda Functions locally with SAM CLI and VSCode

Why

When we build software, it’s always good to get a quick development cycle running. This setup can be a bit of a struggle with serverless development because the platforms that run our functions are in the cloud and uploading to test isn’t exactly what I would call quick.

Luckily most of the cloud providers supply us with tools to ease this pain, for example, the SAM CLI by Amazon Web Services. It’s a command-line tool to help with the creation of SAM-based applications.

In this article, we will learn how to set up a local debugging environment for our serverless applications with the SAM CLI.

What

We need the following software.

The SAM CLI is a command line tool written in Python that helps with creating and maintaining serverless backends. It uses AWS SAM, a dialect of AWS CloudFormation specially designed to handle serverless resources line AWS Lambda, API-Gateway and DynamoDB.

The AWS CLI is used to access AWS via the CLI; it is used by the SAM CLI in the background to modify resources inside our AWS account.

Docker lets us perform operating-system-level virtualization. With this technology, we can set up an environment inside a Docker container and run this container where ever Docker itself can run. The SAM CLI uses this technology to emulate the cloud environment of an AWS Lambda process on our machine.

VSCode is an Electron-based code editor written in TypeScript. It comes with a built-in debug UI which we will leverage to debug our serverless JavaScript code.

How

We have to install the AWS CLI, the AWS SAM CLI, and docker Docker. Then we have to create a new SAM project and link the VSCode debug UI to the Docker container which the SAM CLI is starting for us.

Setup

First, we have to install the AWS CLI, because the SAM CLI builds upon it.

$ curl "https://s3.amazonaws.com/aws-cli/awscli-bundle.zip" -o "awscli-bundle.zip"
$ unzip awscli-bundle.zip
$ ./awscli-bundle/install -b ~/bin/aws

The AWS CLI also needs to be configured with our AWS credentials.

$ aws configure

This command will ask about our IAM user credentials. If you don’t have an IAM user, AWS provides a tutorial for this

We need to install Docker and the AWS CLI and the SAM CLI, I will show an example installation on macOS.

There is a dmg archive for macOS installation we can download here

To install the SAM CLI on macOS, we need Homebrew. We can install it with the following command:

$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Then we tap the AWS tap, to get access to the AWS brew packages and install the aws-sam-cli package. This download can take a few minutes.

$ brew tap aws/tap
$ brew install aws-sam-cli

The next step is to init a SAM project with nodejs8.10 runtime. If we don’t use the --location command line parameter SAM CLI will download a hello world template and create a sam-app directory for it.

$ sam init --runtime nodejs8.10
$ cd sam-app

The crucial files created are hello-world/app.js which holds the code for our AWS Lambda function handler and event.json which holds an example event.

To test the only function this example project has, we can use the sam local invoke command:

$ sam local invoke HelloWorldFunction -e event.json

This command will run our HelloWorldFunction function code inside hello-world/app.js, pass the content of the event.json into it and give the following output at the end:

{"statusCode":200,"body":"{\"message\":\"hello world\"}"}

Now that the SAM CLI is up and running and we got our project set up, we have to link a debugger to it.

Linking the Debug UI

I will use the built-in debug UI of VSCode here.

First, we have to set a breakpoint inside the file we want to debug. In our case, this is the hello-world/app.json.

VSCode Breakpoint Screenshot

We can also add watch expressions. This expression helps to get values out of deeply nested objects right away, without the need to navigate to them in the VARIABLES tab on the side.

VSCode Watch Expression Screenshot

Next, we have to invoke the function locally, but with different CLI parameters.

The sam local invoke command takes a -d parameter to configure a debug port. If we use it, SAM CLI will wait until a debugger is connected to that port before it starts running our code.

If we run the command like this:

$ sam local invoke -d 9999 -e event.json HelloWorldFunction

We will get an output that tells us a debugger is listening:

Debugger listening on ws://0.0.0.0:9999/a47891d0-d0d3-419e-8123-caf8baf4fbbc

Now that we have a waiting debug process, we can attach our VSCode debug-UI to it.

For this, we have to create a launch configuration.

Launch configurations are VSCode specific. We have to write them inside a new file at .vscode/launch.json.

For our application it can look like this:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Attach to SAM CLI",
      "type": "node",
      "request": "attach",
      "address": "localhost",
      "port": 9999,
      "localRoot": "${workspaceRoot}/hello-world",
      "remoteRoot": "/var/task",
      "protocol": "inspector",
      "stopOnEntry": false
    }
  ]
}

The most important parts here are the port, type, localRoot and the protocol.

We ran the SAM CLI with -d 9999, so our port needs to be set accordingly in the launch config. We also want to debug a JavaScript/Node.js file with the inspector protocol that is in the hello-world directory of our project.

If our sam local invoke command is still running and listening for a debug client, we can now run our launch config in the debug-UI of VSCode.

VSCode Debugger Screenshot

If we click the run button, VSCode should attach itself to the SAM CLI process, which is indicated by a red info-bar at the bottom of VSCode.

VSCode highlights the line with our breakpoint.

VSCode Breakpoint Highlight Screenshot

And the debugger sidebar in VSCode should show the currently scoped variables, like event and context.

VSCode Debugger Sidebar Screenshot

Bonus: NPM Script

To streamline the whole process a little bit, we can also use an NPM script to run our SAM CLI command.

To make this work, we need to initialize an NPM project inside of our project directory.

$ npm init -y

Then open the new package.json and add a line to the scripts section.

"scripts": {
  "hello-world": "sam local invoke -d 9999 HelloWorldFunction -e "
},

This script runs the SAM CLI in debug mode and expects a path to a JSON event file.

So we can go from this:

$ sam local invoke -d 9999 HelloWorldFunction -e event.json

To to the much more concise:

$ npm run hello-world -- event.json

The -- ensures that the following event.json argument is passed to the SAM CLI and not to NPM.

Conclusion

The SAM CLI tool makes debugging, and testing of Lambda functions easy. It integrates with all popular IDEs and debug-UIs via a standard debug protocol so we can use the tools we like.

The fact that it uses Docker in the background relieves us from the tremendous burden that comes with the setup of testing and debugging environments and ensures that we end up with something that resembles the cloud in which our function will run at the end.

Learn More About Moesif Get User-Centric API Logging for AWS Lambda 14 day free trial. No credit card required. Learn More
Get User-Centric API Logging for AWS Lambda Get User-Centric API Logging for AWS Lambda

Get User-Centric API Logging for AWS Lambda

Learn More