DEV Community

Cover image for Debugging AWS Lambda + Serverless Framework Locally
Tomas
Tomas

Posted on • Updated on

Debugging AWS Lambda + Serverless Framework Locally

Working with Lambdas

I've been working with AWS Lambdas + Serverless Framework 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.

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.
  • 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
Enter fullscreen mode Exit fullscreen mode

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:

sls invoke local --function my_funnction 
Enter fullscreen mode Exit fullscreen mode

Or if you have environment variables and events you'd do something like this:

sls invoke local --function my_function --path event.json --env KEY=VALUE
Enter fullscreen mode Exit fullscreen mode

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.

Making local debugging more effective & efficient

The workaround and workflow I converged to. I use an additional script, local_handler.pywhich 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
Enter fullscreen mode Exit fullscreen mode

Let's consider the following handler.py and local_handler.py.

# 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!"}

Enter fullscreen mode Exit fullscreen mode

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:

# 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()
Enter fullscreen mode Exit fullscreen mode

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:

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

๐Ÿ 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
Catch me on: github
Catch me on: Twitter


Top comments (0)