From e7f1dc14e5a5983a28f2e70517e79563af7745d4 Mon Sep 17 00:00:00 2001 From: Michael Brewer Date: Tue, 12 Apr 2022 19:08:01 -0700 Subject: [PATCH 1/2] fix(docs): Extract tracer code examples Changes: - Extract code examples - Run isort and black - Update line highlights - Open external links in new tab - Add make task Related to: - #1064 --- Makefile | 8 + docs/core/tracer.md | 237 ++++-------------- .../core/tracer/aiohttp_trace_config.py | 15 ++ .../core/tracer/capture_error_disable.py | 8 + .../core/tracer/capture_lambda_handler.py | 11 + .../core/tracer/capture_method_async.py | 11 + .../tracer/capture_method_context_manager.py | 13 + .../core/tracer/capture_method_generators.py | 12 + .../core/tracer/capture_method_sync.py | 10 + .../concurrent_asynchronous_functions.py | 22 ++ docs/examples/core/tracer/ignore_endpoint.py | 17 ++ docs/examples/core/tracer/patch_modules.py | 7 + docs/examples/core/tracer/put_annotation.py | 9 + docs/examples/core/tracer/put_metadata.py | 10 + docs/examples/core/tracer/reuse_handler.py | 11 + docs/examples/core/tracer/reuse_payment.py | 8 + .../core/tracer/sensitive_data_scenario.py | 13 + .../core/tracer/streaming_object_scenario.py | 12 + docs/examples/core/tracer/template.yml | 13 + .../tracer/tracer_provider_escape_hatches.py | 10 + 20 files changed, 262 insertions(+), 195 deletions(-) create mode 100644 docs/examples/core/tracer/aiohttp_trace_config.py create mode 100644 docs/examples/core/tracer/capture_error_disable.py create mode 100644 docs/examples/core/tracer/capture_lambda_handler.py create mode 100644 docs/examples/core/tracer/capture_method_async.py create mode 100644 docs/examples/core/tracer/capture_method_context_manager.py create mode 100644 docs/examples/core/tracer/capture_method_generators.py create mode 100644 docs/examples/core/tracer/capture_method_sync.py create mode 100644 docs/examples/core/tracer/concurrent_asynchronous_functions.py create mode 100644 docs/examples/core/tracer/ignore_endpoint.py create mode 100644 docs/examples/core/tracer/patch_modules.py create mode 100644 docs/examples/core/tracer/put_annotation.py create mode 100644 docs/examples/core/tracer/put_metadata.py create mode 100644 docs/examples/core/tracer/reuse_handler.py create mode 100644 docs/examples/core/tracer/reuse_payment.py create mode 100644 docs/examples/core/tracer/sensitive_data_scenario.py create mode 100644 docs/examples/core/tracer/streaming_object_scenario.py create mode 100644 docs/examples/core/tracer/template.yml create mode 100644 docs/examples/core/tracer/tracer_provider_escape_hatches.py diff --git a/Makefile b/Makefile index 73667eb5f58..a124d758d95 100644 --- a/Makefile +++ b/Makefile @@ -90,3 +90,11 @@ changelog: mypy: poetry run mypy --pretty aws_lambda_powertools + +format-examples: + poetry run isort docs/examples + poetry run black docs/examples/*/*/*.py + +lint-examples: + poetry run python3 -m py_compile docs/examples/*/*/*.py + cfn-lint docs/examples/*/*/*.yml diff --git a/docs/core/tracer.md b/docs/core/tracer.md index 363611bbbc0..e9bb3ed400c 100644 --- a/docs/core/tracer.md +++ b/docs/core/tracer.md @@ -3,7 +3,7 @@ title: Tracer description: Core utility --- -Tracer is an opinionated thin wrapper for [AWS X-Ray Python SDK](https://github.com/aws/aws-xray-sdk-python/). +Tracer is an opinionated thin wrapper for [AWS X-Ray Python SDK](https://github.com/aws/aws-xray-sdk-python/){target="_blank"}. ![Tracer showcase](../media/tracer_utility_showcase.png) @@ -18,35 +18,18 @@ Tracer is an opinionated thin wrapper for [AWS X-Ray Python SDK](https://github. ### Permissions -Before your use this utility, your AWS Lambda function [must have permissions](https://docs.aws.amazon.com/lambda/latest/dg/services-xray.html#services-xray-permissions) to send traces to AWS X-Ray. - -```yaml hl_lines="6 9" title="AWS Serverless Application Model (SAM) example" -Resources: - HelloWorldFunction: - Type: AWS::Serverless::Function - Properties: - Runtime: python3.8 - Tracing: Active - Environment: - Variables: - POWERTOOLS_SERVICE_NAME: example +Before your use this utility, your AWS Lambda function [must have permissions](https://docs.aws.amazon.com/lambda/latest/dg/services-xray.html#services-xray-permissions){target="_blank"} to send traces to AWS X-Ray. + +```yaml hl_lines="10 13" title="AWS Serverless Application Model (SAM) example" +--8<-- "docs/examples/core/tracer/template.yml" ``` ### Lambda handler You can quickly start by initializing `Tracer` and use `capture_lambda_handler` decorator for your Lambda handler. -```python hl_lines="1 3 6" title="Tracing Lambda handler with capture_lambda_handler" -from aws_lambda_powertools import Tracer - -tracer = Tracer() # Sets service via env var -# OR tracer = Tracer(service="example") - -@tracer.capture_lambda_handler -def handler(event, context): - charge_id = event.get('charge_id') - payment = collect_payment(charge_id) - ... +```python hl_lines="1 3 7" title="Tracing Lambda handler with capture_lambda_handler" +--8<-- "docs/examples/core/tracer/capture_lambda_handler.py" ``` `capture_lambda_handler` performs these additional tasks to ease operations: @@ -57,41 +40,24 @@ def handler(event, context): ### Annotations & Metadata -**Annotations** are key-values associated with traces and indexed by AWS X-Ray. You can use them to filter traces and to create [Trace Groups](https://aws.amazon.com/about-aws/whats-new/2018/11/aws-xray-adds-the-ability-to-group-traces/) to slice and dice your transactions. +**Annotations** are key-values associated with traces and indexed by AWS X-Ray. You can use them to filter traces and to create [Trace Groups](https://aws.amazon.com/about-aws/whats-new/2018/11/aws-xray-adds-the-ability-to-group-traces/){target="_blank"} to slice and dice your transactions. -```python hl_lines="7" title="Adding annotations with put_annotation method" -from aws_lambda_powertools import Tracer -tracer = Tracer() - -@tracer.capture_lambda_handler -def handler(event, context): - ... - tracer.put_annotation(key="PaymentStatus", value="SUCCESS") +```python hl_lines="9" title="Adding annotations with put_annotation method" +--8<-- "docs/examples/core/tracer/put_annotation.py" ``` **Metadata** are key-values also associated with traces but not indexed by AWS X-Ray. You can use them to add additional context for an operation using any native object. -```python hl_lines="8" title="Adding arbitrary metadata with put_metadata method" -from aws_lambda_powertools import Tracer -tracer = Tracer() - -@tracer.capture_lambda_handler -def handler(event, context): - ... - ret = some_logic() - tracer.put_metadata(key="payment_response", value=ret) +```python hl_lines="10" title="Adding arbitrary metadata with put_metadata method" +--8<-- "docs/examples/core/tracer/put_metadata.py" ``` ### Synchronous functions You can trace synchronous functions using the `capture_method` decorator. -```python hl_lines="7 13" title="Tracing an arbitrary function with capture_method" -@tracer.capture_method -def collect_payment(charge_id): - ret = requests.post(PAYMENT_ENDPOINT) # logic - tracer.put_annotation("PAYMENT_STATUS", "SUCCESS") # custom annotation - return ret +```python hl_lines="6 9" title="Tracing an arbitrary function with capture_method" +--8<-- "docs/examples/core/tracer/capture_method_sync.py" ``` ???+ note "Note: Function responses are auto-captured and stored as JSON, by default." @@ -101,7 +67,6 @@ def collect_payment(charge_id): The serialization is performed by aws-xray-sdk via `jsonpickle` module. This can cause side effects for file-like objects like boto S3 `StreamingBody`, where its response will be read only once during serialization. - ### Asynchronous and generator functions ???+ warning @@ -111,47 +76,20 @@ You can trace asynchronous functions and generator functions (including context === "Async" - ```python hl_lines="7" - import asyncio - import contextlib - from aws_lambda_powertools import Tracer - - tracer = Tracer() - - @tracer.capture_method - async def collect_payment(): - ... + ```python hl_lines="9" + --8<-- "docs/examples/core/tracer/capture_method_async.py" ``` === "Context manager" - ```python hl_lines="7-8" - import asyncio - import contextlib - from aws_lambda_powertools import Tracer - - tracer = Tracer() - - @contextlib.contextmanager - @tracer.capture_method - def collect_payment_ctxman(): - yield result - ... + ```python hl_lines="9-10" + --8<-- "docs/examples/core/tracer/capture_method_context_manager.py" ``` === "Generators" ```python hl_lines="9" - import asyncio - import contextlib - from aws_lambda_powertools import Tracer - - tracer = Tracer() - - @tracer.capture_method - def collect_payment_gen(): - yield result - ... + --8<-- "docs/examples/core/tracer/capture_method_generators.py" ``` ## Advanced @@ -163,13 +101,7 @@ Tracer automatically patches all [supported libraries by X-Ray](https://docs.aws If you're looking to shave a few microseconds, or milliseconds depending on your function memory configuration, you can patch specific modules using `patch_modules` param: ```python hl_lines="7" title="Example of explicitly patching boto3 and requests only" -import boto3 -import requests - -from aws_lambda_powertools import Tracer - -modules_to_be_patched = ["boto3", "requests"] -tracer = Tracer(patch_modules=modules_to_be_patched) +--8<-- "docs/examples/core/tracer/patch_modules.py" ``` ### Disabling response auto-capture @@ -183,27 +115,13 @@ Use **`capture_response=False`** parameter in both `capture_lambda_handler` and === "sensitive_data_scenario.py" - ```python hl_lines="3 7" - from aws_lambda_powertools import Tracer - - @tracer.capture_method(capture_response=False) - def fetch_sensitive_information(): - return "sensitive_information" - - @tracer.capture_lambda_handler(capture_response=False) - def handler(event, context): - sensitive_information = fetch_sensitive_information() + ```python hl_lines="6 11" + --8<-- "docs/examples/core/tracer/sensitive_data_scenario.py" ``` === "streaming_object_scenario.py" - ```python hl_lines="3" - from aws_lambda_powertools import Tracer - - @tracer.capture_method(capture_response=False) - def get_s3_object(bucket_name, object_key): - s3 = boto3.client("s3") - s3_object = get_object(Bucket=bucket_name, Key=object_key) - return s3_object + ```python hl_lines="8" + --8<-- "docs/examples/core/tracer/streaming_object_scenario.py" ``` ### Disabling exception auto-capture @@ -213,12 +131,8 @@ Use **`capture_error=False`** parameter in both `capture_lambda_handler` and `ca ???+ info Useful when returning sensitive information in exceptions/stack traces you don't control -```python hl_lines="3 5" title="Disabling exception auto-capture for tracing metadata" -from aws_lambda_powertools import Tracer - -@tracer.capture_lambda_handler(capture_error=False) -def handler(event, context): - raise ValueError("some sensitive info in the stack trace...") +```python hl_lines="6 8" title="Disabling exception auto-capture for tracing metadata" +--8<-- "docs/examples/core/tracer/capture_error_disable.py" ``` ### Ignoring certain HTTP endpoints @@ -227,93 +141,40 @@ You might have endpoints you don't want requests to be traced, perhaps due to th You can use `ignore_endpoint` method with the hostname and/or URLs you'd like it to be ignored - globs (`*`) are allowed. -```python title="Ignoring certain HTTP endpoints from being traced" -from aws_lambda_powertools import Tracer - -tracer = Tracer() -# ignore all calls to `ec2.amazon.com` -tracer.ignore_endpoint(hostname="ec2.amazon.com") -# ignore calls to `*.sensitive.com/password` and `*.sensitive.com/credit-card` -tracer.ignore_endpoint(hostname="*.sensitive.com", urls=["/password", "/credit-card"]) - - -def ec2_api_calls(): - return "suppress_api_responses" - -@tracer.capture_lambda_handler -def handler(event, context): - for x in long_list: - ec2_api_calls() +```python hl_lines="5 7" title="Ignoring certain HTTP endpoints from being traced" +--8<-- "docs/examples/core/tracer/ignore_endpoint.py" ``` - ### Tracing aiohttp requests ???+ info This snippet assumes you have aiohttp as a dependency -You can use `aiohttp_trace_config` function to create a valid [aiohttp trace_config object](https://docs.aiohttp.org/en/stable/tracing_reference.html). This is necessary since X-Ray utilizes aiohttp trace hooks to capture requests end-to-end. +You can use `aiohttp_trace_config` function to create a valid [aiohttp trace_config object](https://docs.aiohttp.org/en/stable/tracing_reference.html){target="_blank"}. This is necessary since X-Ray utilizes aiohttp trace hooks to capture requests end-to-end. -```python hl_lines="5 10" title="Tracing aiohttp requests" -import asyncio -import aiohttp - -from aws_lambda_powertools import Tracer -from aws_lambda_powertools.tracing import aiohttp_trace_config - -tracer = Tracer() - -async def aiohttp_task(): - async with aiohttp.ClientSession(trace_configs=[aiohttp_trace_config()]) as session: - async with session.get("https://httpbin.org/json") as resp: - resp = await resp.json() - return resp +```python hl_lines="6 12" title="Tracing aiohttp requests" +--8<-- "docs/examples/core/tracer/aiohttp_trace_config.py" ``` ### Escape hatch mechanism You can use `tracer.provider` attribute to access all methods provided by AWS X-Ray `xray_recorder` object. -This is useful when you need a feature available in X-Ray that is not available in the Tracer utility, for example [thread-safe](https://github.com/aws/aws-xray-sdk-python/#user-content-trace-threadpoolexecutor), or [context managers](https://github.com/aws/aws-xray-sdk-python/#user-content-start-a-custom-segmentsubsegment). - -```python hl_lines="7" title="Tracing a code block with in_subsegment escape hatch" -from aws_lambda_powertools import Tracer +This is useful when you need a feature available in X-Ray that is not available in the Tracer utility, for example [thread-safe](https://github.com/aws/aws-xray-sdk-python/#user-content-trace-threadpoolexecutor){target="_blank"}, or [context managers](https://github.com/aws/aws-xray-sdk-python/#user-content-start-a-custom-segmentsubsegment){target="_blank"}. -tracer = Tracer() - -@tracer.capture_lambda_handler -def handler(event, context): - with tracer.provider.in_subsegment('## custom subsegment') as subsegment: - ret = some_work() - subsegment.put_metadata('response', ret) +```python hl_lines="8" title="Tracing a code block with in_subsegment escape hatch" +--8<-- "docs/examples/core/tracer/tracer_provider_escape_hatches.py" ``` ### Concurrent asynchronous functions ???+ warning - [X-Ray SDK will raise an exception](https://github.com/aws/aws-xray-sdk-python/issues/164) when async functions are run and traced concurrently + [X-Ray SDK will raise an exception](https://github.com/aws/aws-xray-sdk-python/issues/164){target="_blank"} when async functions are run and traced concurrently A safe workaround mechanism is to use `in_subsegment_async` available via Tracer escape hatch (`tracer.provider`). -```python hl_lines="6 7 12 15 17" title="Workaround to safely trace async concurrent functions" -import asyncio - -from aws_lambda_powertools import Tracer -tracer = Tracer() - -async def another_async_task(): - async with tracer.provider.in_subsegment_async("## another_async_task") as subsegment: - subsegment.put_annotation(key="key", value="value") - subsegment.put_metadata(key="key", value="value", namespace="namespace") - ... - -async def another_async_task_2(): - ... - -@tracer.capture_method -async def collect_payment(charge_id): - asyncio.gather(another_async_task(), another_async_task_2()) - ... +```python hl_lines="8-9 15 19 21" title="Workaround to safely trace async concurrent functions" +--8<-- "docs/examples/core/tracer/concurrent_asynchronous_functions.py" ``` ### Reusing Tracer across your code @@ -329,28 +190,14 @@ Tracer keeps a copy of its configuration after the first initialization. This is === "handler.py" - ```python hl_lines="2 4 9" - from aws_lambda_powertools import Tracer - from payment import collect_payment - - tracer = Tracer(service="payment") - - @tracer.capture_lambda_handler - def handler(event, context): - charge_id = event.get('charge_id') - payment = collect_payment(charge_id) + ```python hl_lines="1 5 11" + --8<-- "docs/examples/core/tracer/reuse_handler.py" ``` === "payment.py" A new instance of Tracer will be created but will reuse the previous Tracer instance configuration, similar to a Singleton. - ```python hl_lines="3 5" - from aws_lambda_powertools import Tracer - - tracer = Tracer(service="payment") - - @tracer.capture_method - def collect_payment(charge_id: str): - ... + ```python hl_lines="3 6" + --8<-- "docs/examples/core/tracer/reuse_payment.py" ``` ## Testing your code @@ -361,4 +208,4 @@ Tracer is disabled by default when not running in the AWS Lambda environment - T * Use annotations on key operations to slice and dice traces, create unique views, and create metrics from it via Trace Groups * Use a namespace when adding metadata to group data more easily -* Annotations and metadata are added to the current subsegment opened. If you want them in a specific subsegment, use a [context manager](https://github.com/aws/aws-xray-sdk-python/#start-a-custom-segmentsubsegment) via the escape hatch mechanism +* Annotations and metadata are added to the current subsegment opened. If you want them in a specific subsegment, use a [context manager](https://github.com/aws/aws-xray-sdk-python/#start-a-custom-segmentsubsegment){target="_blank"} via the escape hatch mechanism diff --git a/docs/examples/core/tracer/aiohttp_trace_config.py b/docs/examples/core/tracer/aiohttp_trace_config.py new file mode 100644 index 00000000000..3d45dd72d52 --- /dev/null +++ b/docs/examples/core/tracer/aiohttp_trace_config.py @@ -0,0 +1,15 @@ +import asyncio + +import aiohttp + +from aws_lambda_powertools import Tracer +from aws_lambda_powertools.tracing import aiohttp_trace_config + +tracer = Tracer() + + +async def aiohttp_task(): + async with aiohttp.ClientSession(trace_configs=[aiohttp_trace_config()]) as session: + async with session.get("https://httpbin.org/json") as resp: + resp = await resp.json() + return resp diff --git a/docs/examples/core/tracer/capture_error_disable.py b/docs/examples/core/tracer/capture_error_disable.py new file mode 100644 index 00000000000..669c2e360ac --- /dev/null +++ b/docs/examples/core/tracer/capture_error_disable.py @@ -0,0 +1,8 @@ +from aws_lambda_powertools import Tracer + +tracer = Tracer() + + +@tracer.capture_lambda_handler(capture_error=False) +def handler(event, context): + raise ValueError("some sensitive info in the stack trace...") diff --git a/docs/examples/core/tracer/capture_lambda_handler.py b/docs/examples/core/tracer/capture_lambda_handler.py new file mode 100644 index 00000000000..6ccb49fefe5 --- /dev/null +++ b/docs/examples/core/tracer/capture_lambda_handler.py @@ -0,0 +1,11 @@ +from aws_lambda_powertools import Tracer + +tracer = Tracer() # Sets service via env var +# OR tracer = Tracer(service="example") + + +@tracer.capture_lambda_handler +def handler(event, context): + charge_id = event.get("charge_id") + payment = collect_payment(charge_id) + ... diff --git a/docs/examples/core/tracer/capture_method_async.py b/docs/examples/core/tracer/capture_method_async.py new file mode 100644 index 00000000000..5e5c5dfe79a --- /dev/null +++ b/docs/examples/core/tracer/capture_method_async.py @@ -0,0 +1,11 @@ +import asyncio +import contextlib + +from aws_lambda_powertools import Tracer + +tracer = Tracer() + + +@tracer.capture_method +async def collect_payment(): + ... diff --git a/docs/examples/core/tracer/capture_method_context_manager.py b/docs/examples/core/tracer/capture_method_context_manager.py new file mode 100644 index 00000000000..7aa21c46f2b --- /dev/null +++ b/docs/examples/core/tracer/capture_method_context_manager.py @@ -0,0 +1,13 @@ +import asyncio +import contextlib + +from aws_lambda_powertools import Tracer + +tracer = Tracer() + + +@contextlib.contextmanager +@tracer.capture_method +def collect_payment_ctxman(): + yield result + ... diff --git a/docs/examples/core/tracer/capture_method_generators.py b/docs/examples/core/tracer/capture_method_generators.py new file mode 100644 index 00000000000..f5ff3a6fa1c --- /dev/null +++ b/docs/examples/core/tracer/capture_method_generators.py @@ -0,0 +1,12 @@ +import asyncio +import contextlib + +from aws_lambda_powertools import Tracer + +tracer = Tracer() + + +@tracer.capture_method +def collect_payment_gen(): + yield result + ... diff --git a/docs/examples/core/tracer/capture_method_sync.py b/docs/examples/core/tracer/capture_method_sync.py new file mode 100644 index 00000000000..79cc9db68a7 --- /dev/null +++ b/docs/examples/core/tracer/capture_method_sync.py @@ -0,0 +1,10 @@ +from aws_lambda_powertools import Tracer + +tracer = Tracer() + + +@tracer.capture_method +def collect_payment(charge_id): + ret = requests.post(PAYMENT_ENDPOINT) # logic + tracer.put_annotation("PAYMENT_STATUS", "SUCCESS") # custom annotation + return ret diff --git a/docs/examples/core/tracer/concurrent_asynchronous_functions.py b/docs/examples/core/tracer/concurrent_asynchronous_functions.py new file mode 100644 index 00000000000..7bc39c66b1c --- /dev/null +++ b/docs/examples/core/tracer/concurrent_asynchronous_functions.py @@ -0,0 +1,22 @@ +import asyncio + +from aws_lambda_powertools import Tracer + +tracer = Tracer() + + +async def another_async_task(): + async with tracer.provider.in_subsegment_async("## another_async_task") as subsegment: + subsegment.put_annotation(key="key", value="value") + subsegment.put_metadata(key="key", value="value", namespace="namespace") + ... + + +async def another_async_task_2(): + ... + + +@tracer.capture_method +async def collect_payment(charge_id): + asyncio.gather(another_async_task(), another_async_task_2()) + ... diff --git a/docs/examples/core/tracer/ignore_endpoint.py b/docs/examples/core/tracer/ignore_endpoint.py new file mode 100644 index 00000000000..271cf233d3b --- /dev/null +++ b/docs/examples/core/tracer/ignore_endpoint.py @@ -0,0 +1,17 @@ +from aws_lambda_powertools import Tracer + +tracer = Tracer() +# ignore all calls to `ec2.amazon.com` +tracer.ignore_endpoint(hostname="ec2.amazon.com") +# ignore calls to `*.sensitive.com/password` and `*.sensitive.com/credit-card` +tracer.ignore_endpoint(hostname="*.sensitive.com", urls=["/password", "/credit-card"]) + + +def ec2_api_calls(): + return "suppress_api_responses" + + +@tracer.capture_lambda_handler +def handler(event, context): + for x in long_list: + ec2_api_calls() diff --git a/docs/examples/core/tracer/patch_modules.py b/docs/examples/core/tracer/patch_modules.py new file mode 100644 index 00000000000..a907682211d --- /dev/null +++ b/docs/examples/core/tracer/patch_modules.py @@ -0,0 +1,7 @@ +import boto3 +import requests + +from aws_lambda_powertools import Tracer + +modules_to_be_patched = ["boto3", "requests"] +tracer = Tracer(patch_modules=modules_to_be_patched) diff --git a/docs/examples/core/tracer/put_annotation.py b/docs/examples/core/tracer/put_annotation.py new file mode 100644 index 00000000000..15d3040e31f --- /dev/null +++ b/docs/examples/core/tracer/put_annotation.py @@ -0,0 +1,9 @@ +from aws_lambda_powertools import Tracer + +tracer = Tracer() + + +@tracer.capture_lambda_handler +def handler(event, context): + ... + tracer.put_annotation(key="PaymentStatus", value="SUCCESS") diff --git a/docs/examples/core/tracer/put_metadata.py b/docs/examples/core/tracer/put_metadata.py new file mode 100644 index 00000000000..9f1dd7a58bb --- /dev/null +++ b/docs/examples/core/tracer/put_metadata.py @@ -0,0 +1,10 @@ +from aws_lambda_powertools import Tracer + +tracer = Tracer() + + +@tracer.capture_lambda_handler +def handler(event, context): + ... + ret = some_logic() + tracer.put_metadata(key="payment_response", value=ret) diff --git a/docs/examples/core/tracer/reuse_handler.py b/docs/examples/core/tracer/reuse_handler.py new file mode 100644 index 00000000000..4789aee56f3 --- /dev/null +++ b/docs/examples/core/tracer/reuse_handler.py @@ -0,0 +1,11 @@ +from payment import collect_payment + +from aws_lambda_powertools import Tracer + +tracer = Tracer(service="payment") + + +@tracer.capture_lambda_handler +def handler(event, context): + charge_id = event.get("charge_id") + payment = collect_payment(charge_id) diff --git a/docs/examples/core/tracer/reuse_payment.py b/docs/examples/core/tracer/reuse_payment.py new file mode 100644 index 00000000000..41a81844264 --- /dev/null +++ b/docs/examples/core/tracer/reuse_payment.py @@ -0,0 +1,8 @@ +from aws_lambda_powertools import Tracer + +tracer = Tracer(service="payment") + + +@tracer.capture_method +def collect_payment(charge_id: str): + ... diff --git a/docs/examples/core/tracer/sensitive_data_scenario.py b/docs/examples/core/tracer/sensitive_data_scenario.py new file mode 100644 index 00000000000..3860ddc1163 --- /dev/null +++ b/docs/examples/core/tracer/sensitive_data_scenario.py @@ -0,0 +1,13 @@ +from aws_lambda_powertools import Tracer + +tracer = Tracer() + + +@tracer.capture_method(capture_response=False) +def fetch_sensitive_information(): + return "sensitive_information" + + +@tracer.capture_lambda_handler(capture_response=False) +def handler(event, context): + sensitive_information = fetch_sensitive_information() diff --git a/docs/examples/core/tracer/streaming_object_scenario.py b/docs/examples/core/tracer/streaming_object_scenario.py new file mode 100644 index 00000000000..1c516c80171 --- /dev/null +++ b/docs/examples/core/tracer/streaming_object_scenario.py @@ -0,0 +1,12 @@ +import boto3 + +from aws_lambda_powertools import Tracer + +tracer = Tracer() + + +@tracer.capture_method(capture_response=False) +def get_s3_object(bucket_name, object_key): + s3 = boto3.client("s3") + s3_object = s3.get_object(Bucket=bucket_name, Key=object_key) + return s3_object diff --git a/docs/examples/core/tracer/template.yml b/docs/examples/core/tracer/template.yml new file mode 100644 index 00000000000..45d296fdfe7 --- /dev/null +++ b/docs/examples/core/tracer/template.yml @@ -0,0 +1,13 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: src/ + Handler: app.lambda_handler + Runtime: python3.9 + Tracing: Active + Environment: + Variables: + POWERTOOLS_SERVICE_NAME: example diff --git a/docs/examples/core/tracer/tracer_provider_escape_hatches.py b/docs/examples/core/tracer/tracer_provider_escape_hatches.py new file mode 100644 index 00000000000..bc0f30a11c1 --- /dev/null +++ b/docs/examples/core/tracer/tracer_provider_escape_hatches.py @@ -0,0 +1,10 @@ +from aws_lambda_powertools import Tracer + +tracer = Tracer() + + +@tracer.capture_lambda_handler +def handler(event, context): + with tracer.provider.in_subsegment("## custom subsegment") as subsegment: + ret = some_work() + subsegment.put_metadata("response", ret) From 31fe45ec28d7ac9579d509351be214cabecd9c3e Mon Sep 17 00:00:00 2001 From: Simon Thulbourn Date: Thu, 28 Apr 2022 17:34:36 +0100 Subject: [PATCH 2/2] Revert "fix(parser): Add missing fields for SESEvent (#1027)" (#1190) This reverts commit 797a10afac80544e2d69bcb7d624909436f2b12a. --- .../utilities/parser/models/__init__.py | 8 -- .../utilities/parser/models/ses.py | 34 +----- tests/events/sesEventS3.json | 114 ------------------ tests/functional/parser/test_ses.py | 58 +-------- 4 files changed, 8 insertions(+), 206 deletions(-) delete mode 100644 tests/events/sesEventS3.json diff --git a/aws_lambda_powertools/utilities/parser/models/__init__.py b/aws_lambda_powertools/utilities/parser/models/__init__.py index 34c8e6ce6a1..e3fb50a2d5d 100644 --- a/aws_lambda_powertools/utilities/parser/models/__init__.py +++ b/aws_lambda_powertools/utilities/parser/models/__init__.py @@ -37,11 +37,7 @@ SesModel, SesReceipt, SesReceiptAction, - SesReceiptActionBase, - SesReceiptBounceAction, - SesReceiptS3Action, SesReceiptVerdict, - SesReceiptWorkmailAction, SesRecordModel, ) from .sns import SnsModel, SnsNotificationModel, SnsRecordModel @@ -88,10 +84,6 @@ "SesMailHeaders", "SesReceipt", "SesReceiptAction", - "SesReceiptActionBase", - "SesReceiptBounceAction", - "SesReceiptWorkmailAction", - "SesReceiptS3Action", "SesReceiptVerdict", "SnsModel", "SnsNotificationModel", diff --git a/aws_lambda_powertools/utilities/parser/models/ses.py b/aws_lambda_powertools/utilities/parser/models/ses.py index 7cd655ea28c..70fd2e83978 100644 --- a/aws_lambda_powertools/utilities/parser/models/ses.py +++ b/aws_lambda_powertools/utilities/parser/models/ses.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import List, Optional, Union +from typing import List, Optional from pydantic import BaseModel, Field from pydantic.networks import EmailStr @@ -12,38 +12,12 @@ class SesReceiptVerdict(BaseModel): status: Literal["PASS", "FAIL", "GRAY", "PROCESSING_FAILED"] -class SesReceiptActionBase(BaseModel): - topicArn: Optional[str] - - -class SesReceiptAction(SesReceiptActionBase): +class SesReceiptAction(BaseModel): type: Literal["Lambda"] # noqa A003,VNE003 invocationType: Literal["Event"] functionArn: str -class SesReceiptS3Action(SesReceiptActionBase): - type: Literal["S3"] # noqa A003,VNE003 - topicArn: str - bucketName: str - objectKey: str - - -class SesReceiptBounceAction(SesReceiptActionBase): - type: Literal["Bounce"] # noqa A003,VNE003 - topicArn: str - smtpReplyCode: str - message: str - sender: str - statusCode: str - - -class SesReceiptWorkmailAction(SesReceiptActionBase): - type: Literal["WorkMail"] # noqa A003,VNE003 - topicArn: str - organizationArn: str - - class SesReceipt(BaseModel): timestamp: datetime processingTimeMillis: PositiveInt @@ -51,10 +25,8 @@ class SesReceipt(BaseModel): spamVerdict: SesReceiptVerdict virusVerdict: SesReceiptVerdict spfVerdict: SesReceiptVerdict - dkimVerdict: SesReceiptVerdict dmarcVerdict: SesReceiptVerdict - dmarcPolicy: Optional[Literal["quarantine", "reject", "none"]] - action: Union[SesReceiptAction, SesReceiptS3Action, SesReceiptBounceAction, SesReceiptWorkmailAction] + action: SesReceiptAction class SesMailHeaders(BaseModel): diff --git a/tests/events/sesEventS3.json b/tests/events/sesEventS3.json deleted file mode 100644 index dbea2d42ce1..00000000000 --- a/tests/events/sesEventS3.json +++ /dev/null @@ -1,114 +0,0 @@ -{ - "Records": [ - { - "eventVersion": "1.0", - "ses": { - "receipt": { - "timestamp": "2015-09-11T20:32:33.936Z", - "processingTimeMillis": 406, - "recipients": [ - "recipient@example.com" - ], - "spamVerdict": { - "status": "PASS" - }, - "virusVerdict": { - "status": "PASS" - }, - "spfVerdict": { - "status": "PASS" - }, - "dkimVerdict": { - "status": "PASS" - }, - "dmarcVerdict": { - "status": "PASS" - }, - "dmarcPolicy": "reject", - "action": { - "type": "S3", - "topicArn": "arn:aws:sns:us-east-1:012345678912:example-topic", - "bucketName": "my-S3-bucket", - "objectKey": "email" - } - }, - "mail": { - "timestamp": "2015-09-11T20:32:33.936Z", - "source": "0000014fbe1c09cf-7cb9f704-7531-4e53-89a1-5fa9744f5eb6-000000@amazonses.com", - "messageId": "d6iitobk75ur44p8kdnnp7g2n800", - "destination": [ - "recipient@example.com" - ], - "headersTruncated": false, - "headers": [ - { - "name": "Return-Path", - "value": "<0000014fbe1c09cf-7cb9f704-7531-4e53-89a1-5fa9744f5eb6-000000@amazonses.com>" - }, - { - "name": "Received", - "value": "from a9-183.smtp-out.amazonses.com (a9-183.smtp-out.amazonses.com [54.240.9.183]) by inbound-smtp.us-east-1.amazonaws.com with SMTP id d6iitobk75ur44p8kdnnp7g2n800 for recipient@example.com; Fri, 11 Sep 2015 20:32:33 +0000 (UTC)" - }, - { - "name": "DKIM-Signature", - "value": "v=1; a=rsa-sha256; q=dns/txt; c=relaxed/simple; s=ug7nbtf4gccmlpwj322ax3p6ow6yfsug; d=amazonses.com; t=1442003552; h=From:To:Subject:MIME-Version:Content-Type:Content-Transfer-Encoding:Date:Message-ID:Feedback-ID; bh=DWr3IOmYWoXCA9ARqGC/UaODfghffiwFNRIb2Mckyt4=; b=p4ukUDSFqhqiub+zPR0DW1kp7oJZakrzupr6LBe6sUuvqpBkig56UzUwc29rFbJF hlX3Ov7DeYVNoN38stqwsF8ivcajXpQsXRC1cW9z8x875J041rClAjV7EGbLmudVpPX 4hHst1XPyX5wmgdHIhmUuh8oZKpVqGi6bHGzzf7g=" - }, - { - "name": "From", - "value": "sender@example.com" - }, - { - "name": "To", - "value": "recipient@example.com" - }, - { - "name": "Subject", - "value": "Example subject" - }, - { - "name": "MIME-Version", - "value": "1.0" - }, - { - "name": "Content-Type", - "value": "text/plain; charset=UTF-8" - }, - { - "name": "Content-Transfer-Encoding", - "value": "7bit" - }, - { - "name": "Date", - "value": "Fri, 11 Sep 2015 20:32:32 +0000" - }, - { - "name": "Message-ID", - "value": "<61967230-7A45-4A9D-BEC9-87CBCF2211C9@example.com>" - }, - { - "name": "X-SES-Outgoing", - "value": "2015.09.11-54.240.9.183" - }, - { - "name": "Feedback-ID", - "value": "1.us-east-1.Krv2FKpFdWV+KUYw3Qd6wcpPJ4Sv/pOPpEPSHn2u2o4=:AmazonSES" - } - ], - "commonHeaders": { - "returnPath": "0000014fbe1c09cf-7cb9f704-7531-4e53-89a1-5fa9744f5eb6-000000@amazonses.com", - "from": [ - "sender@example.com" - ], - "date": "Fri, 11 Sep 2015 20:32:32 +0000", - "to": [ - "recipient@example.com" - ], - "messageId": "<61967230-7A45-4A9D-BEC9-87CBCF2211C9@example.com>", - "subject": "Example subject" - } - } - }, - "eventSource": "aws:ses" - } - ] -} diff --git a/tests/functional/parser/test_ses.py b/tests/functional/parser/test_ses.py index 34a44253514..d434e2350f8 100644 --- a/tests/functional/parser/test_ses.py +++ b/tests/functional/parser/test_ses.py @@ -1,22 +1,11 @@ from aws_lambda_powertools.utilities.parser import event_parser -from aws_lambda_powertools.utilities.parser.models import ( - SesModel, - SesReceiptBounceAction, - SesReceiptWorkmailAction, - SesRecordModel, -) +from aws_lambda_powertools.utilities.parser.models import SesModel, SesRecordModel from aws_lambda_powertools.utilities.typing import LambdaContext from tests.functional.utils import load_event @event_parser(model=SesModel) -def handle_ses(event: SesModel, _: LambdaContext) -> SesModel: - return event - - -def test_ses_trigger_lambda_event(): - event_dict = load_event("sesEvent.json") - event = handle_ses(event_dict, LambdaContext()) +def handle_ses(event: SesModel, _: LambdaContext): expected_address = "johndoe@example.com" records = event.Records record: SesRecordModel = records[0] @@ -40,10 +29,6 @@ def test_ses_trigger_lambda_event(): assert common_headers.to == [expected_address] assert common_headers.messageId == "<0123456789example.com>" assert common_headers.subject == "Test Subject" - assert common_headers.cc is None - assert common_headers.bcc is None - assert common_headers.sender is None - assert common_headers.reply_to is None receipt = record.ses.receipt convert_time = int(round(receipt.timestamp.timestamp() * 1000)) assert convert_time == 0 @@ -53,45 +38,12 @@ def test_ses_trigger_lambda_event(): assert receipt.virusVerdict.status == "PASS" assert receipt.spfVerdict.status == "PASS" assert receipt.dmarcVerdict.status == "PASS" - assert receipt.dmarcVerdict.status == "PASS" - assert receipt.dmarcPolicy is None action = receipt.action assert action.type == "Lambda" assert action.functionArn == "arn:aws:lambda:us-west-2:012345678912:function:Example" assert action.invocationType == "Event" - assert action.topicArn is None - -def test_ses_trigger_event_s3(): - event_dict = load_event("sesEventS3.json") - event = handle_ses(event_dict, LambdaContext()) - records = list(event.Records) - record = records[0] - receipt = record.ses.receipt - assert receipt.dmarcPolicy == "reject" - action = record.ses.receipt.action - assert action.type == "S3" - assert action.topicArn == "arn:aws:sns:us-east-1:012345678912:example-topic" - assert action.bucketName == "my-S3-bucket" - assert action.objectKey == "email" - - -def test_ses_trigger_event_bounce(): - event_dict = { - "type": "Bounce", - "topicArn": "arn:aws:sns:us-east-1:123456789012:topic:my-topic", - "smtpReplyCode": "5.1.1", - "message": "message", - "sender": "sender", - "statusCode": "550", - } - SesReceiptBounceAction(**event_dict) - -def test_ses_trigger_event_work_mail(): - event_dict = { - "type": "WorkMail", - "topicArn": "arn:aws:sns:us-east-1:123456789012:topic:my-topic", - "organizationArn": "arn", - } - SesReceiptWorkmailAction(**event_dict) +def test_ses_trigger_event(): + event_dict = load_event("sesEvent.json") + handle_ses(event_dict, LambdaContext())