Introducing samp-cli for local Lambda debugging of SAM and CDK stacks
Tags: lambda, serverless, cdk, aws
Published: 2023-07-24 20:05:39

_"Ok, how do I run this thing locally"_, is the first thing most developers from a non-serverless background ask when they're tasked to work on a serverless stack. It's a valid question which seasoned serverless developers have worked around in various ways by:

* Using local emulators like [sam local](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/using-sam-cli-local.html), [serverless-offline](https://github.com/dherault/serverless-offline), [localstack](https://localstack.cloud/), etc.
* Wrapping Lambda handlers in unit or integration tests.

While these approaches sort of work, they're not ideal:
* Local emulators are not always accurate and can be a pain to setup and maintain. Also, they don't work well with other AWS services like S3, SQS, etc.
* Unit tests are great for testing individual functions, but they don't test the entire stack and you'll need to keep test payloads up to date.

What you really want is to be able to run Lambda functions on your local machine under the same context and IAM permissions as in the cloud, but have them triggered by real event sources, such as API Gateway, SQS, EventBridge, etc. 

I became inspired to do something about this while listening to [Sebastian Bille](https://twitter.com/TastefulElk) talk about sst.dev at a recent AWS User Group meetup in Stockholm. SST includes [local debugging](https://docs.sst.dev/live-lambda-development) out of the box and Sebastian's demo looked great. 

With my FOMO nerve tightly pinched, I felt determined to deliver a similar experience to SAM/CDK users.

My goals for this were:

* No need to install any additional dev dependencies (given that you already have `samp-cli` installed).
* No need to change your existing SAM template or CDK code.
* No need to change your existing Lambda code.
* It should work for all runtimes supported by Lambda. (so far it supports JavaScript/TypeScript, .NET and Python as of v1.0.40. Java, Go and Ruby is coming)

## Get debugging
In this example I will debug a CDK stack since it's slightly more complicated than a SAM. The same approach works for SAM templates as well.

I will use [dynamodb-streams-to-eventbridge-outbox-pattern](https://github.com/aws-samples/serverless-patterns/tree/main/dynamodb-streams-to-eventbridge-outbox-pattern/cdk) by [David Boyne](https://twitter.com/boyney123) as an example.

### Install samp-cli
First, install the latest version of `samp-cli`

```bash
$ npm install -g samp-cli
```

### Deploy CDK stack to AWS if you haven't already
```bash
$ cdk deploy
```

### Configure launch.config
Note that automatic launch configuration is currently only supported for vscode.

From your project root, run:

```bash
$ samp local --debug
```

For SAM stacks, this step is fast and easy, as it finds all information it needs in the `samconfig.toml` file, but for CDK you have to give it some additional information.

This one-time step per project guides you through a short wizard to create (or append to) a launch.json file in your .vscode folder.

{% youtube ajth1hoHNEM %}

### Start debugging
Now you're ready to start debugging, but before you press F5 it's a good idea to understand what's happening under the hood. This will help you understand why *YOU SHOULD NEVER RUN THIS ON PRODUCTION*.

The first thing to understand is that the tool _replaces your function code in AWS_ with a relay proxy for the duration of the debug session. Once you stop debugging, it will restore to the last deployed code artifact. 

It also updates your function configuration to set `MemorySize` to 128mb and `Timeout` to 60 seconds (it can be increased to 15 minutes by adding environment variable `SAMP_TIMEOUT=900` in `launch.json`). The reason for this is to allow you to sit for longer on breakpoints without the function timing out. Also, the relay proxy doesn't require much memory, so setting it to 128mb gets a bit cheaper.

Both the local machine and the relay proxy lambda function establish an MQTT connection to AWS IoT Core. When the relay function gets triggered, it publishes the event payload, context object and environment variables to the MQTT topic. A local event router receives the message and routes it to the correct function on the developer's machine. Finally, the response it returned to the cloud over MQTT.

![Normal vs. debug flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lnjssopvt4p20a243kye.png)

Video demonstrating setting breakpoints in each of the function in the above architecture as well as hot-reloading code changes:
{% youtube 5M4zP60lwsQ %}

The following sequence diagram visualises the flow of interactions ([high resolution image here](https://github.com/ljacobsson/samp-cli/blob/main/images/samp-local-swimlanes.png)):
![Sequence diagram](https://raw.githubusercontent.com/ljacobsson/samp-cli/main/images/samp-local-swimlanes.png)

### Stop debugging

When debugging is stopped, a clean-up script is run. This restores each function according to the last deploy in CloudFormation. Once this is done, functions are invoked as normal in AWS Lambda.

If this should fail for whatever reason, like if your AWS credentials have expired, you can re-run it using `samp local --force-restore`. If that fails, just redeploy your stack.

### Limitations
* CDK can be set up in many different ways and there will be approaches out there that won't work with this tool. It's been tested with the TypeScript/CDK patterns in [Serverless Land](https://serverlessland.com/patterns). I'd like to hear about your setup though, so please take a minute to [raise an issue](https://github.com/ljacobsson/samp-cli/issues/new?assignees=&labels=bug%2C+triage&projects=&template=bug_report.md&title=[samp%20local%20issue]:%20%3Cyour%20description%3E)
* Functions using Lambda Layers could cause issues. If the layer is in the same stack, [this workaround](https://github.com/ljacobsson/samp-cli#samp-local:~:text=NOTE%20%232%3A%20this%20command%20does%20not%20fully%20support%20Lambda%20layers.%20If%20you%20use%20layers%20to%20bundle%20your%20dependencies%2C%20you%20will%20have%20to%20manually%20install%20them%20locally%20as%20well.%20If%20you%20use%20layers%20to%20share%20custom%20code%20between%20functions%2C%20create%20a%20symlink%20from%20/opt/nodejs%20to%20the%20layer%20folder%20in%20your%20function%20folder.) might work. If you include third party layers, like [Lambda Powertools](https://docs.powertools.aws.dev/lambda/typescript/latest/#lambda-layer), just make sure you have it included in your `devDependencies`.
* If your function accesses resources in a VPC, you'll need to establish a tunnel to the VPC resources. See [the AWS docs](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/how-it-works.html). You can also mock these calls by checking the presence of environment variable `LOCAL_DEBUG`.

### Cost
The cost of running this should be small, but please make sure you understand the pricing for [Lambda](https://aws.amazon.com/lambda/pricing/) and [IoT Core](https://aws.amazon.com/iot-core/pricing/).

In short:
* A 128mb Lambda invocation is charged at $0.000126 per minute
* $1 per 1 million AWS IoT messages

### About `samp-cli`
`Samp-cli` was previously knows as `sam-patterns-cli`. It was born in a [response](https://twitter.com/lajacobsson/status/1377368916800929796) to the announcement of [serverlessland.com/patterns](https://serverlessland.com/patterns) back in the spring of 2021. Since then, it's grown into a versatile productivity tool for SAM users, hence the rename. Here's a list of what it supports at the time of writing: 

![samp help](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y6u2coklbewkeetlj0wt.png)

### Conclusion
Every way to improve the developer experience for Lambda is a win for all the serverless developers out there. Getting fast feedback on the code we write has always been important and has not changed with the serverless movement.

I hope this will speed up development for you as much as it has for me. If not, please let me know any improvements you'd like to see by [raising an issue](https://github.com/ljacobsson/samp-cli/issues/new?assignees=&labels=bug%2C+triage&projects=&template=bug_report.md&title=[samp%20local%20issue]:%20%3Cyour%20description%3E).