Skip to content

Commit 9e9b2cd

Browse files
committed
fix: use decorators, split cold start to ease reading
1 parent b096f5f commit 9e9b2cd

File tree

1 file changed

+93
-30
lines changed

1 file changed

+93
-30
lines changed

docs/quickstart.md

Lines changed: 93 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -523,18 +523,22 @@ By having structured logs like this, we can easily search and analyse them in [C
523523
!!! note
524524
You won't see any traces in AWS X-Ray when executing your function locally.
525525

526-
The next improvement is to add an appropriate tracking mechanism to your stack. Developers want to analyze traces of queries that pass via the API gateway to your Lambda.
527-
With structured logs, it is an important step to provide the observability of your application!
528-
The AWS service that has these capabilities is [AWS X-RAY](https://aws.amazon.com/xray/). How do we send application trace to the AWS X-RAY service then?
526+
The next improvement is to add distributed tracing to your stack. Traces help you visualize end-to-end transactions or parts of it to easily debug upstream/downstream anomalies.
529527

530-
Let's first explore how we can achieve this with [x-ray SDK](https://docs.aws.amazon.com/xray-sdk-for-python/latest/reference/index.html), and then try to simplify it with the Powertools library.
528+
Combined with structured logs, it is an important step to be able to observe how your application runs in production.
529+
530+
### Generating traces
531+
532+
[AWS X-Ray](https://aws.amazon.com/xray/){target="_blank"} is the distributed tracing service we're going to use. But how do we generate application traces in the first place?
533+
534+
It's a [two-step process](https://docs.aws.amazon.com/lambda/latest/dg/services-xray.html){target="_blank"}: **1/** enable tracing in your Lambda function, and **2/** instrument your application code. Let's explore how we can instrument our code with [AWS X-Ray SDK](https://docs.aws.amazon.com/xray-sdk-for-python/latest/reference/index.html){target="_blank"}, and then simplify it with [Lambda Powertools Tracer](core/tracer.md){target="_blank"} feature.
531535

532536
=== "app.py"
533537

534-
```python hl_lines="3 14 19-20 27-28 35-41"
538+
```python hl_lines="3 12 16 23 30"
535539
import json
536540

537-
from aws_xray_sdk.core import xray_recorder
541+
from aws_xray_sdk.core import patch_all, xray_recorder
538542

539543
from aws_lambda_powertools import Logger
540544
from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver
@@ -543,42 +547,32 @@ Let's first explore how we can achieve this with [x-ray SDK](https://docs.aws.am
543547
logger = Logger(service="APP")
544548

545549
app = ApiGatewayResolver()
546-
547-
548-
cold_start = True
550+
patch_all()
549551

550552

551553
@app.get("/hello/<name>")
554+
@xray_recorder.capture('hello_name')
552555
def hello_name(name):
553-
with xray_recorder.in_subsegment("hello_name") as subsegment:
554-
subsegment.put_annotation("User", name)
555-
logger.info(f"Request from {name} received")
556-
return {"statusCode": 200, "body": json.dumps({"message": f"hello {name}!"})}
556+
logger.info(f"Request from {name} received")
557+
return {"statusCode": 200, "body": json.dumps({"message": f"hello {name}!"})}
557558

558559

559560
@app.get("/hello")
561+
@xray_recorder.capture('hello')
560562
def hello():
561-
with xray_recorder.in_subsegment("hello") as subsegment:
562-
subsegment.put_annotation("User", "unknown")
563-
logger.info("Request from unknown received")
564-
return {"statusCode": 200, "body": json.dumps({"message": "hello unknown!"})}
563+
logger.info("Request from unknown received")
564+
return {"statusCode": 200, "body": json.dumps({"message": "hello unknown!"})}
565565

566566

567567
@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST, log_event=True)
568+
@xray_recorder.capture('handler')
568569
def lambda_handler(event, context):
569-
global cold_start
570-
with xray_recorder.in_subsegment("handler") as subsegment:
571-
if cold_start:
572-
subsegment.put_annotation("ColdStart", "True")
573-
cold_start = False
574-
else:
575-
subsegment.put_annotation("ColdStart", "False")
576-
return app.resolve(event, context)
570+
return app.resolve(event, context)
577571
```
578572

579573
=== "template.yaml"
580574

581-
```yaml hl_lines="15"
575+
```yaml hl_lines="14"
582576
AWSTemplateFormatVersion: "2010-09-09"
583577
Transform: AWS::Serverless-2016-10-31
584578
Description: Sample SAM Template for powertools-quickstart
@@ -610,16 +604,85 @@ Let's first explore how we can achieve this with [x-ray SDK](https://docs.aws.am
610604
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
611605
```
612606

613-
* First, we import required X-ray SDK classes. `xray_recorder` is a global AWS X-ray recorder class instance that starts/ends segments/sub-segments and sends them to the X-ray daemon.
614-
* To build new sub-segments, we use `xray_recorder.in_subsegment` method as a context manager.
615-
* We track Lambda cold start by setting global variable outside of a handler. The variable is defined only upon Lambda initialization. This information provides an overview of how often the runtime is reused by Lambda invoked, which directly impacts Lambda performance and latency.
607+
Let's break it down:
608+
609+
* **L1**: First, we import AWS X-Ray SDK. `xray_recorder` records blocks of code being traced ([subsegment](https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-subsegments){target="_blank"}). It also sends generated traces to the AWS X-Ray daemon running in the Lambda service who subsequently forwards them to AWS X-Ray service
610+
* **L12**: We patch [supported libraries](https://docs.aws.amazon.com/xray-sdk-for-python/latest/reference/thirdparty.html#patching-supported-libraries){target="_blank"} that might have been imported, e.g., AWS SDK, requests, httplib, etc.
611+
* **L16**: We decorate our function so the SDK traces the end-to-end execution, and the argument names the generated block being traced
612+
613+
???+ question
614+
But how do I enable tracing for the Lambda function and what permissions do I need?
615+
616+
Within `template.yaml` on line 14, we added a new Serverless Function property: `Tracing: Active`. This will enable tracing for the [Lambda function resource](https://docs.aws.amazon.com/lambda/latest/dg/services-xray.html#services-xray-cloudformation){target="_blank"}, and add a managed IAM Policy named [AWSXRayDaemonWriteAccess](https://console.aws.amazon.com/iam/home#/policies/arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess){target="_blank"} to allow Lambda to send traces to AWS X-Ray.
617+
618+
!!! danger "TODO: Revisit to see if it's still necessary"
616619

617-
To allow the tracking of our Lambda, we need to set it up in our SAM template and add `Tracing: Active` under Lambda `Properties` section.
618620
!!! Info
619621
Want to know more about context managers and understand the benefits of using them? Follow [article](https://realpython.com/python-with-statement/) from Real Python.
622+
623+
624+
### Enriching our generates traces
625+
626+
627+
cold start invocations
628+
629+
> Annotations are simple key-value pairs that are indexed for use with filter expressionsMetadata are key-value pairs with values of any type, including objects and lists, but that are not indexed
630+
631+
> Metadata are key-value pairs with values of any type, including objects and lists, but that are not indexed
632+
633+
634+
```python title="Capturing cold start as a tracing annotation" hl_lines="3 12-13 17-18 25-26 35-40 42"
635+
import json
636+
637+
from aws_xray_sdk.core import patch_all, xray_recorder
638+
639+
from aws_lambda_powertools import Logger
640+
from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver
641+
from aws_lambda_powertools.logging import correlation_paths
642+
643+
logger = Logger(service="APP")
644+
645+
app = ApiGatewayResolver()
646+
cold_start = True
647+
patch_all()
648+
649+
650+
@app.get("/hello/<name>")
651+
def hello_name(name):
652+
with xray_recorder.in_subsegment("hello_name") as subsegment:
653+
subsegment.put_annotation("User", name)
654+
logger.info(f"Request from {name} received")
655+
return {"message": f"hello {name}!"}
656+
657+
658+
@app.get("/hello")
659+
def hello():
660+
with xray_recorder.in_subsegment("hello") as subsegment:
661+
subsegment.put_annotation("User", "unknown")
662+
logger.info("Request from unknown received")
663+
return {"message": "hello unknown!"}
664+
665+
666+
@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST, log_event=True)
667+
def lambda_handler(event, context):
668+
global cold_start
669+
if cold_start:
670+
subsegment.put_annotation("ColdStart", cold_start)
671+
cold_start = False
672+
else:
673+
subsegment.put_annotation("ColdStart", cold_start)
674+
675+
with xray_recorder.in_subsegment("handler") as subsegment:
676+
return app.resolve(event, context)
677+
```
678+
679+
* We track Lambda cold start by setting global variable outside of a handler. The variable is defined only upon Lambda initialization. This information provides an overview of how often the runtime is reused by Lambda invoked, which directly impacts Lambda performance and latency.
680+
620681
!!! Info
621682
If you want to understand how the Lambda execution environment works and why cold starts can occur, follow [blog series](https://aws.amazon.com/blogs/compute/operating-lambda-performance-optimization-part-1/).
622683

684+
### Simplifying with Tracer
685+
623686
Now, let's try to simplify it with Lambda Powertools:
624687

625688
=== "app.py"

0 commit comments

Comments
 (0)