From 6a80ce077ff92401852a4fa4f42a53df43ba36c7 Mon Sep 17 00:00:00 2001 From: Michael Brewer Date: Tue, 12 Apr 2022 18:35:45 -0700 Subject: [PATCH 1/2] fix(docs): Extract metrics code examples Changes: - Extract code examples - Run isort and black - Fix python and yaml examples - Update line highlights - Add make task Related to: - #1064 --- Makefile | 8 + docs/core/metrics.md | 225 +++--------------- docs/examples/core/metrics/add_metadata.py | 10 + .../metrics/clear_metrics_between_tests.py | 14 ++ docs/examples/core/metrics/example_app.py | 8 + docs/examples/core/metrics/flush_metrics.py | 9 + .../core/metrics/flush_metrics_manually.py | 13 + .../core/metrics/functional_testing.py | 25 ++ .../functional_testing_multiple_blobs.py | 33 +++ .../log_metrics_capture_cold_start_metric.py | 8 + .../metrics/log_metrics_default_dimensions.py | 10 + .../log_metrics_multiple_decorators.py | 11 + .../log_metrics_raise_on_empty_metrics.py | 8 + docs/examples/core/metrics/metrics_app.py | 9 + .../metrics/metrics_custom_dimensions_app.py | 10 + .../core/metrics/set_default_dimensions.py | 10 + docs/examples/core/metrics/single_metric.py | 8 + docs/examples/core/metrics/template.yml | 13 + 18 files changed, 240 insertions(+), 192 deletions(-) create mode 100644 docs/examples/core/metrics/add_metadata.py create mode 100644 docs/examples/core/metrics/clear_metrics_between_tests.py create mode 100644 docs/examples/core/metrics/example_app.py create mode 100644 docs/examples/core/metrics/flush_metrics.py create mode 100644 docs/examples/core/metrics/flush_metrics_manually.py create mode 100644 docs/examples/core/metrics/functional_testing.py create mode 100644 docs/examples/core/metrics/functional_testing_multiple_blobs.py create mode 100644 docs/examples/core/metrics/log_metrics_capture_cold_start_metric.py create mode 100644 docs/examples/core/metrics/log_metrics_default_dimensions.py create mode 100644 docs/examples/core/metrics/log_metrics_multiple_decorators.py create mode 100644 docs/examples/core/metrics/log_metrics_raise_on_empty_metrics.py create mode 100644 docs/examples/core/metrics/metrics_app.py create mode 100644 docs/examples/core/metrics/metrics_custom_dimensions_app.py create mode 100644 docs/examples/core/metrics/set_default_dimensions.py create mode 100644 docs/examples/core/metrics/single_metric.py create mode 100644 docs/examples/core/metrics/template.yml 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/metrics.md b/docs/core/metrics.md index 99ee17106b3..ba7639e3fa3 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -41,31 +41,17 @@ Setting | Description | Environment variable | Constructor parameter ???+ example **AWS Serverless Application Model (SAM)** -=== "template.yml" - - ```yaml hl_lines="9 10" - Resources: - HelloWorldFunction: - Type: AWS::Serverless::Function - Properties: - Runtime: python3.8 - Environment: - Variables: - POWERTOOLS_SERVICE_NAME: payment - POWERTOOLS_METRICS_NAMESPACE: ServerlessAirline - ``` + === "template.yml" -=== "app.py" - - ```python hl_lines="4 6" - from aws_lambda_powertools import Metrics - from aws_lambda_powertools.metrics import MetricUnit + ```yaml hl_lines="12-13" + --8<-- "docs/examples/core/metrics/template.yml" + ``` - metrics = Metrics() # Sets metric namespace and service via env var - # OR - metrics = Metrics(namespace="ServerlessAirline", service="orders") # Sets metric namespace, and service as a metric dimension - ``` + === "app.py" + ```python hl_lines="4 6-8" + --8<-- "docs/examples/core/metrics/example_app.py" + ``` ### Creating metrics @@ -76,28 +62,13 @@ You can create metrics using `add_metric`, and you can create dimensions for all === "Metrics" - ```python hl_lines="8" - from aws_lambda_powertools import Metrics - from aws_lambda_powertools.metrics import MetricUnit - - metrics = Metrics(namespace="ExampleApplication", service="booking") - - @metrics.log_metrics - def lambda_handler(evt, ctx): - metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1) + ```python hl_lines="9" + --8<-- "docs/examples/core/metrics/metrics_app.py" ``` === "Metrics with custom dimensions" - ```python hl_lines="8-9" - from aws_lambda_powertools import Metrics - from aws_lambda_powertools.metrics import MetricUnit - - metrics = Metrics(namespace="ExampleApplication", service="booking") - - @metrics.log_metrics - def lambda_handler(evt, ctx): - metrics.add_dimension(name="environment", value="prod") - metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1) + ```python hl_lines="9-10" + --8<-- "docs/examples/core/metrics/metrics_custom_dimensions_app.py" ``` ???+ tip "Tip: Autocomplete Metric Units" @@ -118,28 +89,12 @@ If you'd like to remove them at some point, you can use `clear_default_dimension === "set_default_dimensions method" ```python hl_lines="5" - from aws_lambda_powertools import Metrics - from aws_lambda_powertools.metrics import MetricUnit - - metrics = Metrics(namespace="ExampleApplication", service="booking") - metrics.set_default_dimensions(environment="prod", another="one") - - @metrics.log_metrics - def lambda_handler(evt, ctx): - metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1) + --8<-- "docs/examples/core/metrics/set_default_dimensions.py" ``` === "with log_metrics decorator" - ```python hl_lines="5 7" - from aws_lambda_powertools import Metrics - from aws_lambda_powertools.metrics import MetricUnit - - metrics = Metrics(namespace="ExampleApplication", service="booking") - DEFAULT_DIMENSIONS = {"environment": "prod", "another": "one"} - - @metrics.log_metrics(default_dimensions=DEFAULT_DIMENSIONS) - def lambda_handler(evt, ctx): - metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1) + ```python hl_lines="5 8" + --8<-- "docs/examples/core/metrics/log_metrics_default_dimensions.py" ``` ### Flushing metrics @@ -150,15 +105,8 @@ This decorator also **validates**, **serializes**, and **flushes** all your metr === "app.py" - ```python hl_lines="6" - from aws_lambda_powertools import Metrics - from aws_lambda_powertools.metrics import MetricUnit - - metrics = Metrics(namespace="ExampleApplication", service="ExampleService") - - @metrics.log_metrics - def lambda_handler(evt, ctx): - metrics.add_metric(name="BookingConfirmation", unit=MetricUnit.Count, value=1) + ```python hl_lines="7" + --8<-- "docs/examples/core/metrics/flush_metrics.py" ``` === "Example CloudWatch Logs excerpt" @@ -199,14 +147,8 @@ This decorator also **validates**, **serializes**, and **flushes** all your metr If you want to ensure at least one metric is always emitted, you can pass `raise_on_empty_metrics` to the **log_metrics** decorator: -```python hl_lines="5" title="Raising SchemaValidationError exception if no metrics are added" -from aws_lambda_powertools.metrics import Metrics - -metrics = Metrics() - -@metrics.log_metrics(raise_on_empty_metrics=True) -def lambda_handler(evt, ctx): - ... +```python hl_lines="6" title="Raising SchemaValidationError exception if no metrics are added" +--8<-- "docs/examples/core/metrics/log_metrics_raise_on_empty_metrics.py" ``` ???+ tip "Suppressing warning messages on empty metrics" @@ -216,31 +158,16 @@ def lambda_handler(evt, ctx): When using multiple middlewares, use `log_metrics` as your **last decorator** wrapping all subsequent ones to prevent early Metric validations when code hasn't been run yet. -```python hl_lines="7-8" title="Example with multiple decorators" -from aws_lambda_powertools import Metrics, Tracer -from aws_lambda_powertools.metrics import MetricUnit - -tracer = Tracer(service="booking") -metrics = Metrics(namespace="ExampleApplication", service="booking") - -@metrics.log_metrics -@tracer.capture_lambda_handler -def lambda_handler(evt, ctx): - metrics.add_metric(name="BookingConfirmation", unit=MetricUnit.Count, value=1) +```python hl_lines="8-9" title="Example with multiple decorators" +--8<-- "docs/examples/core/metrics/log_metrics_multiple_decorators.py" ``` ### Capturing cold start metric You can optionally capture cold start metrics with `log_metrics` decorator via `capture_cold_start_metric` param. -```python hl_lines="5" title="Generating function cold start metric" -from aws_lambda_powertools import Metrics - -metrics = Metrics(service="ExampleService") - -@metrics.log_metrics(capture_cold_start_metric=True) -def lambda_handler(evt, ctx): - ... +```python hl_lines="6" title="Generating function cold start metric" +--8<-- "docs/examples/core/metrics/log_metrics_capture_cold_start_metric.py" ``` If it's a cold start invocation, this feature will: @@ -264,16 +191,8 @@ You can add high-cardinality data as part of your Metrics log with `add_metadata === "app.py" - ```python hl_lines="9" - from aws_lambda_powertools import Metrics - from aws_lambda_powertools.metrics import MetricUnit - - metrics = Metrics(namespace="ExampleApplication", service="booking") - - @metrics.log_metrics - def lambda_handler(evt, ctx): - metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1) - metrics.add_metadata(key="booking_id", value="booking_uuid") + ```python hl_lines="10" + --8<-- "docs/examples/core/metrics/add_metadata.py" ``` === "Example CloudWatch Logs excerpt" @@ -315,14 +234,7 @@ CloudWatch EMF uses the same dimensions across all your metrics. Use `single_met **unique metric = (metric_name + dimension_name + dimension_value)** ```python hl_lines="6-7" title="Generating an EMF blob with a single metric" -from aws_lambda_powertools import single_metric -from aws_lambda_powertools.metrics import MetricUnit - - -def lambda_handler(evt, ctx): - with single_metric(name="ColdStart", unit=MetricUnit.Count, value=1, namespace="ExampleApplication") as metric: - metric.add_dimension(name="function_context", value="$LATEST") - ... +--8<-- "docs/examples/core/metrics/single_metric.py" ``` ### Flushing metrics manually @@ -332,18 +244,8 @@ If you prefer not to use `log_metrics` because you might want to encapsulate add ???+ warning Metrics, dimensions and namespace validation still applies -```python hl_lines="9-11" title="Manually flushing and clearing metrics from memory" -import json -from aws_lambda_powertools import Metrics -from aws_lambda_powertools.metrics import MetricUnit - -metrics = Metrics(namespace="ExampleApplication", service="booking") - -def lambda_handler(evt, ctx): - metrics.add_metric(name="ColdStart", unit=MetricUnit.Count, value=1) - your_metrics_object = metrics.serialize_metric_set() - metrics.clear_metrics() - print(json.dumps(your_metrics_object)) +```python hl_lines="11-13" title="Manually flushing and clearing metrics from memory" +--8<-- "docs/examples/core/metrics/flush_metrics_manually.py" ``` ## Testing your code @@ -366,14 +268,7 @@ POWERTOOLS_SERVICE_NAME="Example" POWERTOOLS_METRICS_NAMESPACE="Application" pyt `Metrics` keep metrics in memory across multiple instances. If you need to test this behaviour, you can use the following Pytest fixture to ensure metrics are reset incl. cold start: ```python title="Clearing metrics between tests" -@pytest.fixture(scope="function", autouse=True) -def reset_metric_set(): - # Clear out every metric data prior to every test - metrics = Metrics() - metrics.clear_metrics() - metrics_global.is_cold_start = True # ensure each test has cold start - metrics.clear_default_dimensions() # remove persisted default dimensions, if any - yield +--8<-- "docs/examples/core/metrics/clear_metrics_between_tests.py" ``` ### Functional testing @@ -382,68 +277,14 @@ As metrics are logged to standard output, you can read standard output and asser === "Assert single EMF blob with pytest.py" - ```python hl_lines="6 9-10 23-34" - from aws_lambda_powertools import Metrics - from aws_lambda_powertools.metrics import MetricUnit - - import json - - def test_log_metrics(capsys): - # GIVEN Metrics is initialized - metrics = Metrics(namespace="ServerlessAirline") - - # WHEN we utilize log_metrics to serialize - # and flush all metrics at the end of a function execution - @metrics.log_metrics - def lambda_handler(evt, ctx): - metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1) - metrics.add_dimension(name="environment", value="prod") - - lambda_handler({}, {}) - log = capsys.readouterr().out.strip() # remove any extra line - metrics_output = json.loads(log) # deserialize JSON str - - # THEN we should have no exceptions - # and a valid EMF object should be flushed correctly - assert "SuccessfulBooking" in log # basic string assertion in JSON str - assert "SuccessfulBooking" in metrics_output["_aws"]["CloudWatchMetrics"][0]["Metrics"][0]["Name"] + ```python hl_lines="7 15-16 24-25" + --8<-- "docs/examples/core/metrics/functional_testing.py" ``` === "Assert multiple EMF blobs with pytest" - ```python hl_lines="8-9 11 21-23 25 29-30 32" - from aws_lambda_powertools import Metrics - from aws_lambda_powertools.metrics import MetricUnit - - from collections import namedtuple - - import json - - def capture_metrics_output_multiple_emf_objects(capsys): - return [json.loads(line.strip()) for line in capsys.readouterr().out.split("\n") if line] - - def test_log_metrics(capsys): - # GIVEN Metrics is initialized - metrics = Metrics(namespace="ServerlessAirline") - - # WHEN log_metrics is used with capture_cold_start_metric - @metrics.log_metrics(capture_cold_start_metric=True) - def lambda_handler(evt, ctx): - metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1) - metrics.add_dimension(name="environment", value="prod") - - # log_metrics uses function_name property from context to add as a dimension for cold start metric - LambdaContext = namedtuple("LambdaContext", "function_name") - lambda_handler({}, LambdaContext("example_fn") - - cold_start_blob, custom_metrics_blob = capture_metrics_output_multiple_emf_objects(capsys) - - # THEN ColdStart metric and function_name dimension should be logged - # in a separate EMF blob than the application metrics - assert cold_start_blob["ColdStart"] == [1.0] - assert cold_start_blob["function_name"] == "example_fn" - - assert "SuccessfulBooking" in custom_metrics_blob # as per previous example + ```python hl_lines="8-9 12 22-24 26 30-31 33" + --8<-- "docs/examples/core/metrics/functional_testing_multiple_blobs.py" ``` ???+ tip diff --git a/docs/examples/core/metrics/add_metadata.py b/docs/examples/core/metrics/add_metadata.py new file mode 100644 index 00000000000..1e0660f2816 --- /dev/null +++ b/docs/examples/core/metrics/add_metadata.py @@ -0,0 +1,10 @@ +from aws_lambda_powertools import Metrics +from aws_lambda_powertools.metrics import MetricUnit + +metrics = Metrics(namespace="ExampleApplication", service="booking") + + +@metrics.log_metrics +def lambda_handler(evt, ctx): + metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1) + metrics.add_metadata(key="booking_id", value="booking_uuid") diff --git a/docs/examples/core/metrics/clear_metrics_between_tests.py b/docs/examples/core/metrics/clear_metrics_between_tests.py new file mode 100644 index 00000000000..cea3879af83 --- /dev/null +++ b/docs/examples/core/metrics/clear_metrics_between_tests.py @@ -0,0 +1,14 @@ +import pytest + +from aws_lambda_powertools import Metrics +from aws_lambda_powertools.metrics import metrics as metrics_global + + +@pytest.fixture(scope="function", autouse=True) +def reset_metric_set(): + # Clear out every metric data prior to every test + metrics = Metrics() + metrics.clear_metrics() + metrics_global.is_cold_start = True # ensure each test has cold start + metrics.clear_default_dimensions() # remove persisted default dimensions, if any + yield diff --git a/docs/examples/core/metrics/example_app.py b/docs/examples/core/metrics/example_app.py new file mode 100644 index 00000000000..b644c96b72e --- /dev/null +++ b/docs/examples/core/metrics/example_app.py @@ -0,0 +1,8 @@ +from aws_lambda_powertools import Metrics +from aws_lambda_powertools.metrics import MetricUnit + +metrics = Metrics() # Sets metric namespace and service via env var +# OR +metrics = Metrics( + namespace="ServerlessAirline", service="orders" +) # Sets metric namespace, and service as a metric dimension diff --git a/docs/examples/core/metrics/flush_metrics.py b/docs/examples/core/metrics/flush_metrics.py new file mode 100644 index 00000000000..379c51f864e --- /dev/null +++ b/docs/examples/core/metrics/flush_metrics.py @@ -0,0 +1,9 @@ +from aws_lambda_powertools import Metrics +from aws_lambda_powertools.metrics import MetricUnit + +metrics = Metrics(namespace="ExampleApplication", service="ExampleService") + + +@metrics.log_metrics +def lambda_handler(evt, ctx): + metrics.add_metric(name="BookingConfirmation", unit=MetricUnit.Count, value=1) diff --git a/docs/examples/core/metrics/flush_metrics_manually.py b/docs/examples/core/metrics/flush_metrics_manually.py new file mode 100644 index 00000000000..cf26cde2500 --- /dev/null +++ b/docs/examples/core/metrics/flush_metrics_manually.py @@ -0,0 +1,13 @@ +import json + +from aws_lambda_powertools import Metrics +from aws_lambda_powertools.metrics import MetricUnit + +metrics = Metrics(namespace="ExampleApplication", service="booking") + + +def lambda_handler(evt, ctx): + metrics.add_metric(name="ColdStart", unit=MetricUnit.Count, value=1) + your_metrics_object = metrics.serialize_metric_set() + metrics.clear_metrics() + print(json.dumps(your_metrics_object)) diff --git a/docs/examples/core/metrics/functional_testing.py b/docs/examples/core/metrics/functional_testing.py new file mode 100644 index 00000000000..71f628f2cae --- /dev/null +++ b/docs/examples/core/metrics/functional_testing.py @@ -0,0 +1,25 @@ +import json + +from aws_lambda_powertools import Metrics +from aws_lambda_powertools.metrics import MetricUnit + + +def test_log_metrics(capsys): + # GIVEN Metrics is initialized + metrics = Metrics(namespace="ServerlessAirline") + + # WHEN we utilize log_metrics to serialize + # and flush all metrics at the end of a function execution + @metrics.log_metrics + def lambda_handler(evt, ctx): + metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1) + metrics.add_dimension(name="environment", value="prod") + + lambda_handler({}, {}) + log = capsys.readouterr().out.strip() # remove any extra line + metrics_output = json.loads(log) # deserialize JSON str + + # THEN we should have no exceptions + # and a valid EMF object should be flushed correctly + assert "SuccessfulBooking" in log # basic string assertion in JSON str + assert "SuccessfulBooking" in metrics_output["_aws"]["CloudWatchMetrics"][0]["Metrics"][0]["Name"] diff --git a/docs/examples/core/metrics/functional_testing_multiple_blobs.py b/docs/examples/core/metrics/functional_testing_multiple_blobs.py new file mode 100644 index 00000000000..ed1c23078a1 --- /dev/null +++ b/docs/examples/core/metrics/functional_testing_multiple_blobs.py @@ -0,0 +1,33 @@ +import json +from collections import namedtuple + +from aws_lambda_powertools import Metrics +from aws_lambda_powertools.metrics import MetricUnit + + +def capture_metrics_output_multiple_emf_objects(capsys): + return [json.loads(line.strip()) for line in capsys.readouterr().out.split("\n") if line] + + +def test_log_metrics(capsys): + # GIVEN Metrics is initialized + metrics = Metrics(namespace="ServerlessAirline") + + # WHEN log_metrics is used with capture_cold_start_metric + @metrics.log_metrics(capture_cold_start_metric=True) + def lambda_handler(evt, ctx): + metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1) + metrics.add_dimension(name="environment", value="prod") + + # log_metrics uses function_name property from context to add as a dimension for cold start metric + LambdaContext = namedtuple("LambdaContext", "function_name") + lambda_handler({}, LambdaContext("example_fn")) + + cold_start_blob, custom_metrics_blob = capture_metrics_output_multiple_emf_objects(capsys) + + # THEN ColdStart metric and function_name dimension should be logged + # in a separate EMF blob than the application metrics + assert cold_start_blob["ColdStart"] == [1.0] + assert cold_start_blob["function_name"] == "example_fn" + + assert "SuccessfulBooking" in custom_metrics_blob # as per previous example diff --git a/docs/examples/core/metrics/log_metrics_capture_cold_start_metric.py b/docs/examples/core/metrics/log_metrics_capture_cold_start_metric.py new file mode 100644 index 00000000000..37b10026afa --- /dev/null +++ b/docs/examples/core/metrics/log_metrics_capture_cold_start_metric.py @@ -0,0 +1,8 @@ +from aws_lambda_powertools import Metrics + +metrics = Metrics(service="ExampleService") + + +@metrics.log_metrics(capture_cold_start_metric=True) +def lambda_handler(evt, ctx): + ... diff --git a/docs/examples/core/metrics/log_metrics_default_dimensions.py b/docs/examples/core/metrics/log_metrics_default_dimensions.py new file mode 100644 index 00000000000..39ea7ce4613 --- /dev/null +++ b/docs/examples/core/metrics/log_metrics_default_dimensions.py @@ -0,0 +1,10 @@ +from aws_lambda_powertools import Metrics +from aws_lambda_powertools.metrics import MetricUnit + +metrics = Metrics(namespace="ExampleApplication", service="booking") +DEFAULT_DIMENSIONS = {"environment": "prod", "another": "one"} + + +@metrics.log_metrics(default_dimensions=DEFAULT_DIMENSIONS) +def lambda_handler(evt, ctx): + metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1) diff --git a/docs/examples/core/metrics/log_metrics_multiple_decorators.py b/docs/examples/core/metrics/log_metrics_multiple_decorators.py new file mode 100644 index 00000000000..7fd6d14d9d0 --- /dev/null +++ b/docs/examples/core/metrics/log_metrics_multiple_decorators.py @@ -0,0 +1,11 @@ +from aws_lambda_powertools import Metrics, Tracer +from aws_lambda_powertools.metrics import MetricUnit + +tracer = Tracer(service="booking") +metrics = Metrics(namespace="ExampleApplication", service="booking") + + +@metrics.log_metrics +@tracer.capture_lambda_handler +def lambda_handler(evt, ctx): + metrics.add_metric(name="BookingConfirmation", unit=MetricUnit.Count, value=1) diff --git a/docs/examples/core/metrics/log_metrics_raise_on_empty_metrics.py b/docs/examples/core/metrics/log_metrics_raise_on_empty_metrics.py new file mode 100644 index 00000000000..90b5b475709 --- /dev/null +++ b/docs/examples/core/metrics/log_metrics_raise_on_empty_metrics.py @@ -0,0 +1,8 @@ +from aws_lambda_powertools.metrics import Metrics + +metrics = Metrics() + + +@metrics.log_metrics(raise_on_empty_metrics=True) +def lambda_handler(evt, ctx): + ... diff --git a/docs/examples/core/metrics/metrics_app.py b/docs/examples/core/metrics/metrics_app.py new file mode 100644 index 00000000000..9d1be476025 --- /dev/null +++ b/docs/examples/core/metrics/metrics_app.py @@ -0,0 +1,9 @@ +from aws_lambda_powertools import Metrics +from aws_lambda_powertools.metrics import MetricUnit + +metrics = Metrics(namespace="ExampleApplication", service="booking") + + +@metrics.log_metrics +def lambda_handler(evt, ctx): + metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1) diff --git a/docs/examples/core/metrics/metrics_custom_dimensions_app.py b/docs/examples/core/metrics/metrics_custom_dimensions_app.py new file mode 100644 index 00000000000..ef7f9a85e2d --- /dev/null +++ b/docs/examples/core/metrics/metrics_custom_dimensions_app.py @@ -0,0 +1,10 @@ +from aws_lambda_powertools import Metrics +from aws_lambda_powertools.metrics import MetricUnit + +metrics = Metrics(namespace="ExampleApplication", service="booking") + + +@metrics.log_metrics +def lambda_handler(evt, ctx): + metrics.add_dimension(name="environment", value="prod") + metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1) diff --git a/docs/examples/core/metrics/set_default_dimensions.py b/docs/examples/core/metrics/set_default_dimensions.py new file mode 100644 index 00000000000..0006703b287 --- /dev/null +++ b/docs/examples/core/metrics/set_default_dimensions.py @@ -0,0 +1,10 @@ +from aws_lambda_powertools import Metrics +from aws_lambda_powertools.metrics import MetricUnit + +metrics = Metrics(namespace="ExampleApplication", service="booking") +metrics.set_default_dimensions(environment="prod", another="one") + + +@metrics.log_metrics +def lambda_handler(evt, ctx): + metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1) diff --git a/docs/examples/core/metrics/single_metric.py b/docs/examples/core/metrics/single_metric.py new file mode 100644 index 00000000000..13a1a6c3e0c --- /dev/null +++ b/docs/examples/core/metrics/single_metric.py @@ -0,0 +1,8 @@ +from aws_lambda_powertools import single_metric +from aws_lambda_powertools.metrics import MetricUnit + + +def lambda_handler(evt, ctx): + with single_metric(name="ColdStart", unit=MetricUnit.Count, value=1, namespace="ExampleApplication") as metric: + metric.add_dimension(name="function_context", value="$LATEST") + ... diff --git a/docs/examples/core/metrics/template.yml b/docs/examples/core/metrics/template.yml new file mode 100644 index 00000000000..b9bc7ba6e04 --- /dev/null +++ b/docs/examples/core/metrics/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 + Environment: + Variables: + POWERTOOLS_SERVICE_NAME: payment + POWERTOOLS_METRICS_NAMESPACE: ServerlessAirline 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())