From 79433b28d72808a1639d8ec611ee880fad19609b Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Thu, 27 Apr 2023 15:21:27 +0200 Subject: [PATCH 1/6] feat(metrics): add flush_metrics --- aws_lambda_powertools/metrics/base.py | 33 +++++++++++++------ docs/core/metrics.md | 14 ++++++-- .../src/{manual_flush.py => flush_metrics.py} | 6 +--- tests/functional/test_metrics.py | 20 +++++++++++ 4 files changed, 55 insertions(+), 18 deletions(-) rename examples/metrics/src/{manual_flush.py => flush_metrics.py} (69%) diff --git a/aws_lambda_powertools/metrics/base.py b/aws_lambda_powertools/metrics/base.py index b96356192ab..59daafa0bb1 100644 --- a/aws_lambda_powertools/metrics/base.py +++ b/aws_lambda_powertools/metrics/base.py @@ -328,6 +328,28 @@ def clear_metrics(self) -> None: self.dimension_set.clear() self.metadata_set.clear() + def flush_metrics(self, raise_on_empty_metrics: bool = False) -> None: + """Manually flushes the metrics. This is normally not necessary, + unless you're running on other runtimes besides Lambda, where the @log_metrics + decorator already handles things for you. + + Parameters + ---------- + raise_on_empty_metrics : bool, optional + raise exception if no metrics are emitted, by default False + """ + if not raise_on_empty_metrics and not self.metric_set: + warnings.warn( + "No application metrics to publish. The cold-start metric may be published if enabled. " + "If application metrics should never be empty, consider using 'raise_on_empty_metrics'", + stacklevel=2, + ) + else: + logger.debug("Flushing existing metrics") + metrics = self.serialize_metric_set() + print(json.dumps(metrics, separators=(",", ":"))) + self.clear_metrics() + def log_metrics( self, lambda_handler: Union[Callable[[Dict, Any], Any], Optional[Callable[[Dict, Any, Optional[Dict]], Any]]] = None, @@ -390,16 +412,7 @@ def decorate(event, context): if capture_cold_start_metric: self._add_cold_start_metric(context=context) finally: - if not raise_on_empty_metrics and not self.metric_set: - warnings.warn( - "No application metrics to publish. The cold-start metric may be published if enabled. " - "If application metrics should never be empty, consider using 'raise_on_empty_metrics'", - stacklevel=2, - ) - else: - metrics = self.serialize_metric_set() - self.clear_metrics() - print(json.dumps(metrics, separators=(",", ":"))) + self.flush_metrics(raise_on_empty_metrics=raise_on_empty_metrics) return response diff --git a/docs/core/metrics.md b/docs/core/metrics.md index 81acd8999d8..0a323e8360b 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -256,9 +256,17 @@ 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="11-14" title="Manually flushing and clearing metrics from memory" ---8<-- "examples/metrics/src/single_metric.py" -``` +=== "Regular Metrics" + + ```python hl_lines="10" + --8<-- "examples/metrics/src/flush_metrics.py" + ``` + +=== "Single Metrics" + + ```python hl_lines="11-14" + --8<-- "examples/metrics/src/single_metric.py" + ``` ### Metrics isolation diff --git a/examples/metrics/src/manual_flush.py b/examples/metrics/src/flush_metrics.py similarity index 69% rename from examples/metrics/src/manual_flush.py rename to examples/metrics/src/flush_metrics.py index def0f845d08..a10e5933a1d 100644 --- a/examples/metrics/src/manual_flush.py +++ b/examples/metrics/src/flush_metrics.py @@ -1,5 +1,3 @@ -import json - from aws_lambda_powertools import Metrics from aws_lambda_powertools.metrics import MetricUnit from aws_lambda_powertools.utilities.typing import LambdaContext @@ -9,6 +7,4 @@ def lambda_handler(event: dict, context: LambdaContext): metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1) - your_metrics_object = metrics.serialize_metric_set() - metrics.clear_metrics() - print(json.dumps(your_metrics_object)) + metrics.flush_metrics() diff --git a/tests/functional/test_metrics.py b/tests/functional/test_metrics.py index c0c41f3bf88..964af99ce6e 100644 --- a/tests/functional/test_metrics.py +++ b/tests/functional/test_metrics.py @@ -249,6 +249,26 @@ def lambda_handler(evt, ctx): assert expected == output +def test_log_metrics_manual_flush(capsys, metrics, dimensions, namespace): + # GIVEN Metrics is initialized + my_metrics = Metrics(namespace=namespace) + for metric in metrics: + my_metrics.add_metric(**metric) + for dimension in dimensions: + my_metrics.add_dimension(**dimension) + + # WHEN we manually the metrics + my_metrics.flush_metrics() + + output = capture_metrics_output(capsys) + expected = serialize_metrics(metrics=metrics, dimensions=dimensions, namespace=namespace) + + # THEN we should have no exceptions + # and a valid EMF object should be flushed correctly + remove_timestamp(metrics=[output, expected]) + assert expected == output + + def test_namespace_env_var(monkeypatch, capsys, metric, dimension, namespace): # GIVEN POWERTOOLS_METRICS_NAMESPACE is set monkeypatch.setenv("POWERTOOLS_METRICS_NAMESPACE", namespace) From 9c7e562ba006bc4abb85bfc541bca91074aeb5e0 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Fri, 28 Apr 2023 14:40:46 +0200 Subject: [PATCH 2/6] chore: address feedback --- docs/core/metrics.md | 14 +++----------- examples/metrics/src/flush_metrics.py | 6 ++++-- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/docs/core/metrics.md b/docs/core/metrics.md index 0a323e8360b..7e128219030 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -256,17 +256,9 @@ If you prefer not to use `log_metrics` because you might want to encapsulate add ???+ warning Metrics, dimensions and namespace validation still applies -=== "Regular Metrics" - - ```python hl_lines="10" - --8<-- "examples/metrics/src/flush_metrics.py" - ``` - -=== "Single Metrics" - - ```python hl_lines="11-14" - --8<-- "examples/metrics/src/single_metric.py" - ``` +```python hl_lines="12" title="Manually flushing and clearing metrics from memory" +--8<-- "examples/metrics/src/flush_metrics.py" +``` ### Metrics isolation diff --git a/examples/metrics/src/flush_metrics.py b/examples/metrics/src/flush_metrics.py index a10e5933a1d..dd9b646893c 100644 --- a/examples/metrics/src/flush_metrics.py +++ b/examples/metrics/src/flush_metrics.py @@ -6,5 +6,7 @@ def lambda_handler(event: dict, context: LambdaContext): - metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1) - metrics.flush_metrics() + try: + metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1) + finally: + metrics.flush_metrics() From 0238c6a2c82076b54fbb45336a39b8d8d0707fa5 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Fri, 28 Apr 2023 15:20:31 +0200 Subject: [PATCH 3/6] Update examples/metrics/src/flush_metrics.py Co-authored-by: Heitor Lessa Signed-off-by: Ruben Fonseca --- examples/metrics/src/flush_metrics.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/metrics/src/flush_metrics.py b/examples/metrics/src/flush_metrics.py index dd9b646893c..27fad00aa68 100644 --- a/examples/metrics/src/flush_metrics.py +++ b/examples/metrics/src/flush_metrics.py @@ -5,6 +5,12 @@ metrics = Metrics() +def book_flight(flight_id: str, **kwargs): + # logic to book flight + ... + metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1) + + def lambda_handler(event: dict, context: LambdaContext): try: metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1) From 18cc5e41104ab0660e4734c35ebed83c5a9740bd Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Fri, 28 Apr 2023 15:20:41 +0200 Subject: [PATCH 4/6] Update examples/metrics/src/flush_metrics.py Co-authored-by: Heitor Lessa Signed-off-by: Ruben Fonseca --- examples/metrics/src/flush_metrics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/metrics/src/flush_metrics.py b/examples/metrics/src/flush_metrics.py index 27fad00aa68..a66ce07cbf7 100644 --- a/examples/metrics/src/flush_metrics.py +++ b/examples/metrics/src/flush_metrics.py @@ -13,6 +13,6 @@ def book_flight(flight_id: str, **kwargs): def lambda_handler(event: dict, context: LambdaContext): try: - metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1) + book_flight(flight_id=event.get("flight_id", "")) finally: metrics.flush_metrics() From d10d371750590aafb6c5ccb45f123a9ef873a726 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Fri, 28 Apr 2023 15:22:00 +0200 Subject: [PATCH 5/6] fix: highlight --- docs/core/metrics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core/metrics.md b/docs/core/metrics.md index 7e128219030..c75b9ac9da5 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -256,7 +256,7 @@ 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="12" title="Manually flushing and clearing metrics from memory" +```python hl_lines="18" title="Manually flushing and clearing metrics from memory" --8<-- "examples/metrics/src/flush_metrics.py" ``` From 6b8e4a18e555b53a4252a6bb9f8fb6e589bd3800 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Fri, 28 Apr 2023 14:37:04 +0100 Subject: [PATCH 6/6] chore(docs): line editing --- docs/core/metrics.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/core/metrics.md b/docs/core/metrics.md index c75b9ac9da5..ba9f746e867 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -251,10 +251,12 @@ By default it will skip all previously defined dimensions including default dime ### Flushing metrics manually -If you prefer not to use `log_metrics` because you might want to encapsulate additional logic when doing so, you can manually flush and clear metrics as follows: +If you are using the AWS Lambda Web Adapter project, or a middleware with custom metric logic, you can use `flush_metrics()`. This method will serialize, print metrics available to standard output, and clear in-memory metrics data. ???+ warning - Metrics, dimensions and namespace validation still applies + This does not capture Cold Start metrics, and metric data validation still applies. + +Contrary to the `log_metrics` decorator, you are now also responsible to flush metrics in the event of an exception. ```python hl_lines="18" title="Manually flushing and clearing metrics from memory" --8<-- "examples/metrics/src/flush_metrics.py"