Debugging AWS Lambda + Serverless Framework Locally
Tags: aws, lambda, cloud, serverless
Published: 2022-03-10 22:31:08

### Working with Lambdas
I've been working with AWS Lambdas + [**Serverless Framework**](https://www.serverless.com) on my projects lately. When I started to work with AWS Lambda I was a bit lost - I was not sure about the best way to develop🛠, debug🐛 and test🧪 AWS Lambdas locally. **One thing I knew for sure - AWS web IDE is not the way to go.**

This approach uses Serverless Framework specific CLI commands. However, the idea can be generalized and used with other frameworks. I must also mention, that this post will not walk you through the project setup. But you can check the sample repository [here](https://github.com/tkeyo/aws_lambda_debug_sample).

### The Project & Workflow
While working with Lambdas I converged to my current workflow - the topic of this blog.

#### Project Setup
Consider the following setup for a Lambda project:
- `serverless.yaml` contains the definition of a function(s) - handler path, deployment settings, etc.  it's specific to Serverless Framework. More [here](https://www.serverless.com/framework/docs/providers/aws/guide/serverless.yml/).  
- `lambda_1` directory contains an AWS Lambda function. It's possible to have multiple Lambdas per project. You could have something like `lambda_2` in your project. You just need to add additional definitions in your `serverless.yaml` file.
- I prefer to use `src` directories that hold the source code for a function. 
- `handler.py` contains the entry point ("lambda handler") for invocation
- `event.json` is a sample event for local invocation. More on that soon.

```
# directory structure

aws_lambda_project
|-lambda_1
|  |-src
|    |-__init__.py
|    |-util.py
|    |-db.py
|  |-event.json
|  |-handler.py
|-serverless.yml
```

#### Local Debugging with Serverless Framework
Now, if you want to run/debug your Lambda you can use the Serverless Framework CLI command. Something like this:
```bash
sls invoke local --function my_funnction 
```
Or if you have environment variables and events you'd do something like this:
```bash
sls invoke local --function my_function --path event.json --env KEY=VALUE
```
Using these commands is a legit way to run your AWS Lambdas locally. However, in my experience, this invocation is **slow(ish)** and **no debug breakpoints** for you. 

> ❗️ Please, let me know if there's a way to execute with Serverless Framework CLI in debug mode with breakpoints. 
> ❗️ Apparently, if you use AWS SAM it [works OOB](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-using-debugging.html).

#### Making local debugging more effective & efficient
The workaround and workflow I converged to. I use an additional script, `local_handler.py`which wraps `handler.py` This allows you to set up your variables, "events", environment variables, and everything you might need. And best of all - you can use **breakpoints in your code.**

```
aws_lambda_project
|-lambda_1
|  |-src
|    |-__init__.py
|    |-util.py
|    |-db.py
|  |-event.json
|  |-handler.py
|  |-local_handler.py <- THIS
|-serverless.yml
```

Let's consider the following `handler.py` and `local_handler.py`. 
```Python
# handler.py
from lambda_1.src.util import get_db  
  
db = get_db()

def handler(event, context) -> dict:  
	"Sample lambda function handler."
    records = event.get("Records", "")

	if records:  
		db.save(records)  
	return {"statusCode": 200, "body": "Hello from Lambda!"}

```

> Sidenote:  This is the handler you can also invoke with the `sls invoke local`. This simulates an AWS trigger locally. If it works locally, it will likely work on AWS when deployed. 

Your local handler should look something like this:
```Python
# local_handler.py
from handler import handler  
  
sample_event = {
	"Records": [
		{"name": "John", "age": "30"},
		{"name": "Jane", "age": "25"}
	]  
}  

def main():  
	print(handler(sample_event, None))  
  
if __name__ == "__main__":  
    main()
```

Now in `local_handler.py`, I wrap the `handler` function with a `main` function which is called when you run the `local_handler.py`. 

This has a couple of **advantages** IMO:
- you can run your code with Python 🐍  - i.e. breakpoints, IDE capabilities
- you can keep your lambda handler intact and deploy it directly to AWS
- faster startup - no need to initialize Serverless Framework 

### Un-cluttering deployments
In `serverless.yml` it's possible to define files that you do not want to deploy to AWS. The exclamation mark ignores the files. They won't be packaged and pushed - keeping it tidy. See the example below:

```yaml
...
functions:
  sample_lambda:  
    package:  
      patterns:  
        # include  
		- 'src/**'  
		# exclude 
		- '!local_handler.py'  
		...
		- '!venv/**'  
    handler: lambda_1.handler.handler  
    environment: ${file(env/.env.sls.json):stage}
...
```

> 🏁  Fin. This approach has proven to be the most flexible in my experience with AWS Lambdas.

---

Feel free to leave a comment or reach out.  📥 💫.

**Repository:** [AWS Sample Repo](https://github.com/tkeyo/aws_lambda_debug_sample)
**Catch me on:** [github](https://github.com/tkeyo)
**Catch me on:** [Twitter](https://twitter.com/tkeyo_)

---