globaldatanetmenu

.Develop Lambda functions with AWS Lambda Powertools

Aug 9th 2022-5 min read

Following best practices, using structured logging, and enabling distributed tracing across all your Lambda functions can be quite a hassle. When you first start developing serverless applications with Lambda one starts to tend to copy-paste existing code from one Lambda function to another to have structured logs or the like. After some time, the pain of maintaining this one will start trimming the hedge and creating a dedicated library with a reusable logger, event validation, and parsing. Some won't leave it at that and create a dedicated Lambda layer for all the functionality needed across the majority of their Lambda function. From there on it's a walk in the park but getting there is comparable to having to fight your way through the jungle. There is no clear path. Every step is a good piece of work and the thicket makes it impossible to find the direct path.

With AWS Lambda Powertools we don't have to find our way through the jungle any longer. We can now start our journey right at the park gate. AWS Lambda Powertools is optimized for the Lambda function environments. It aims to facilitate best practice adoption as defined in the AWS Well-Architected Serverless Lens, keeps lean to keep our functions performant, and provides answers to users' urgent needs by listening to the community when considering what to do next.

AWS Lambda Powertools is available for Python (first Powertools library; available on PyPi and as Lambda Layer), Java, TypeScript, and in preview for .NET.

To get a bit more insight into what AWS Lambda Powertools has to offer, we will look at the following example architecture. It's probably one of the most used architectures, and while it doesn't matter much, it makes the example more tangible to imagine that we built an application for finding a walking buddy for the park.

Blog Content

The frontend will be implemented with Amplify. For the backend, we use API Gateway to provide a REST API to our frontend. API requests get processed by a Lambda function. User profiles and matches for walking buddies are stored in a single DynamoDB table. We use CloudWatch to store the Lambda function logs and X-Ray for distributed tracing.

As providing examples for every variant of AWS Lambda Powertools would go beyond the scope of the blog post, we will use Python in any code snippets. In my eyes, it's the most established Powertools variant with the most features, of which some will likely get adopted by the other variants in the future. Within the documentation for all AWS Lambda Powertools, you can find runnable examples, tutorials, and the code to those in public repositories.

Logging

Unified structured logging is simple with AWS Lambda Powertools. To add keys to all log message in a Lambda invocation use append_keys.

from aws_lambda_powertools import Logger
from aws_lambda_powertools.utilities.typing import LambdaContext

logger = Logger()


def handler(event: dict, context: LambdaContext) -> str:
    user_id = event.get("user_id")

    # this will ensure user_id key always has the latest value before logging
    logger.append_keys(user_id=user_id)

To add keys to just one log message, use extra.

from aws_lambda_powertools import Logger
from aws_lambda_powertools.utilities.typing import LambdaContext

logger = Logger()


def handler(event: dict, context: LambdaContext) -> str:
    user_id = event.get("user_id")
    
    # adds user_id only to this log message
    logger.info("Log additional key", extra={ "user_id": user_id })

Logging the incoming event can help in debugging issues. It's simple with the inject_lambda_context logger decorator. For supported events it also simplifies tracking correlation IDs around your system.

from aws_lambda_powertools import Logger
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools.logging import correlation_paths
from aws_lambda_powertools.utilities.typing import LambdaContext

logger = Logger()
app = APIGatewayRestResolver()


# adds correlation ID for REST API Gateway events to log messages and logs the incoming event
@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST, log_event=True)
def lambda_handler(event: dict, context: LambdaContext) -> str:
    pass

Tracing

After enabling tracing and setting the right IAM permissions for the Lambda function, we just need the capture_lambda_handler decorator to instrument tracing. The decorator will automatically add an annotation for cold starts.

from aws_lambda_powertools import Tracer
from aws_lambda_powertools.utilities.typing import LambdaContext


tracer = Tracer()

@tracer.capture_lambda_handler
def lambda_handler(event: dict, context: LambdaContext) -> str:
    # searchable metadata
    tracer.put_annotation(key="UserId", value=event.get("user_id"))
    tracer.put_metadata(key="Path", value=event.get("path"))

To add subsegments for certain functions use the @tracer.capture_method decorator.

Metrics

Let's say we want a metric to count how many people matched with walking buddies through our service. All it takes to log metrics is the log_metrics decorator.

from aws_lambda_powertools import Metrics
from aws_lambda_powertools.metrics import MetricUnit
from aws_lambda_powertools.utilities.typing import LambdaContext

metrics = Metrics()


@metrics.log_metrics  # ensures metrics are flushed upon request completion/failure
def lambda_handler(event: dict, context: LambdaContext):
    # ...
    
    metrics.add_metric(name="SuccessfulMatch", unit=MetricUnit.Count, value=1)

Event Validation

Our Lambda function gets invoked by API Gateway. We know the event format but it would be so nice if we wouldn't have to validate and parse the incoming events ourselves. Luckily, AWS Lambda Powertools provides a solution for this.

from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools.logging import correlation_paths
from aws_lambda_powertools.utilities.typing import LambdaContext
from aws_lambda_powertools.utilities.data_classes import event_source
from aws_lambda_powertools.utilities.data_classes.api_gateway_authorizer_event import APIGatewayAuthorizerRequestEvent

tracer = Tracer()
logger = Logger()
app = APIGatewayRestResolver()

# API route resolver
@app.get("/buddies")
@tracer.capture_method
def get_buddies():
    return {"buddies": []}


@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST)
@tracer.capture_lambda_handler
# transform incoming event to data class
@event_source(data_class=APIGatewayAuthorizerRequestEvent)
def lambda_handler(event: APIGatewayAuthorizerRequestEvent, context: LambdaContext) -> dict:
    # access event class attributes
    return app.resolve(event.raw_event, context)

If we don't want to use the event source data classes, we can opt to custom validation with the validator decorator, validating the event against JSON schemas. Another handy construct you can see in the example above is the use of the app resolver. You can use HTTP method decorators to match a function to an API request's method and path. No need to write custom routing logic. Errors can be handled with the @app.exception_handler(ExceptionType) decorator that will catch exceptions of the provided type and handle them in a dedicated function.

If we wouldn't use a REST API but a GraphQL API it would look similar. Instead of the HTTP method decorator the @app.resolver decorator matches a function to GraphQL types and fields.

Conclusion

Using AWS Lambda Powertools greatly improves Developer Experience when developing Lambda functions. It helps in ensuring to stick to established best practices. AWS Lambda Powertools Python offered as a Lambda layer makes it easy to try out, use, and later adopt it to all your functions. Through the community-driven approach every voice counts and influences the Powertools roadmap.

globaldatanetCloud Development, Optimization & Automation

.Navigation

.Social

  • follow globaldatanet on instagram
  • follow globaldatanet on facebook
  • follow globaldatanet on twitter
  • follow globaldatanet on linkendin
  • follow globaldatanet on twitch
  •  listen to our serverless world podcast
  • follow globaldatanet's tech rss feed
  • follow globaldatanet at github
© 2022 by globaldatanet. All Right Reserved
Your privacy is important to us!

We use cookies on our website. Some of them are essential,while others help us to improve our online offer.
You can find more information in our Privacy policy