diff --git a/aws_lambda_powertools/metrics/metrics.py b/aws_lambda_powertools/metrics/metrics.py index 4d6a7d3140d..487f2ab9b2f 100644 --- a/aws_lambda_powertools/metrics/metrics.py +++ b/aws_lambda_powertools/metrics/metrics.py @@ -128,5 +128,46 @@ class EphemeralMetrics(MetricManager): - Create the same metrics with different dimensions more than once """ + _dimensions: Dict[str, str] = {} + _default_dimensions: Dict[str, Any] = {} + def __init__(self, service: Optional[str] = None, namespace: Optional[str] = None): + self.default_dimensions = self._default_dimensions + self.dimension_set = self._dimensions + + self.dimension_set.update(**self._default_dimensions) super().__init__(namespace=namespace, service=service) + + def set_default_dimensions(self, **dimensions) -> None: + """Persist dimensions across Lambda invocations + + Parameters + ---------- + dimensions : Dict[str, Any], optional + metric dimensions as key=value + + Example + ------- + **Sets some default dimensions that will always be present across metrics and invocations** + + from aws_lambda_powertools import Metrics + + metrics = Metrics(namespace="ServerlessAirline", service="payment") + metrics.set_default_dimensions(environment="demo", another="one") + + @metrics.log_metrics() + def lambda_handler(): + return True + """ + for name, value in dimensions.items(): + self.add_dimension(name, value) + + self.default_dimensions.update(**dimensions) + + def clear_default_dimensions(self) -> None: + self.default_dimensions.clear() + + def clear_metrics(self) -> None: + super().clear_metrics() + # re-add default dimensions + self.set_default_dimensions(**self.default_dimensions) diff --git a/aws_lambda_powertools/shared/user_agent.py b/aws_lambda_powertools/shared/user_agent.py index 098be7a503a..c682c24b34f 100644 --- a/aws_lambda_powertools/shared/user_agent.py +++ b/aws_lambda_powertools/shared/user_agent.py @@ -112,7 +112,7 @@ def register_feature_to_session(session, feature): def register_feature_to_botocore_session(botocore_session, feature): """ Register the given feature string to the event system of the provided botocore session - + Please notice this function is for patching botocore session and is different from previous one which is for patching boto3 session @@ -127,7 +127,7 @@ def register_feature_to_botocore_session(botocore_session, feature): ------ AttributeError If the provided session does not have an event system. - + Examples -------- **register data-masking user-agent to botocore session** @@ -139,7 +139,7 @@ def register_feature_to_botocore_session(botocore_session, feature): >>> session = botocore.session.Session() >>> register_feature_to_botocore_session(botocore_session=session, feature="data-masking") >>> key_provider = StrictAwsKmsMasterKeyProvider(key_ids=self.keys, botocore_session=session) - + """ try: botocore_session.register(TARGET_SDK_EVENT, _create_feature_function(feature)) diff --git a/docs/core/metrics.md b/docs/core/metrics.md index bb93d99eb4f..052406300dc 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -274,12 +274,11 @@ You can use `EphemeralMetrics` class when looking to isolate multiple instances **Differences between `EphemeralMetrics` and `Metrics`** -`EphemeralMetrics` has only two differences while keeping nearly the exact same set of features: +`EphemeralMetrics` has only one difference while keeping nearly the exact same set of features: | Feature | Metrics | EphemeralMetrics | | ----------------------------------------------------------------------------------------------------------- | ------- | ---------------- | | **Share data across instances** (metrics, dimensions, metadata, etc.) | Yes | - | -| **[Default dimensions](#adding-default-dimensions) that persists across Lambda invocations** (metric flush) | Yes | - | !!! question "Why not changing the default `Metrics` behaviour to not share data across instances?" diff --git a/tests/functional/test_metrics.py b/tests/functional/test_metrics.py index d7063f88744..5a6222f248d 100644 --- a/tests/functional/test_metrics.py +++ b/tests/functional/test_metrics.py @@ -1115,6 +1115,46 @@ def test_metrics_reuse_metadata_set(metric, dimension, namespace): assert my_metrics_2.metadata_set == my_metrics.metadata_set +def test_log_ephemeral_metrics_with_default_dimensions(capsys, metrics, dimensions, namespace): + # GIVEN Metrics is initialized + my_metrics = EphemeralMetrics(namespace=namespace) + default_dimensions = {"environment": "test", "log_group": "/lambda/test"} + + # WHEN we utilize log_metrics with default dimensions to serialize + # and flush metrics and clear all metrics and dimensions from memory + # at the end of a function execution + @my_metrics.log_metrics(default_dimensions=default_dimensions) + def lambda_handler(evt, ctx): + for metric in metrics: + my_metrics.add_metric(**metric) + + lambda_handler({}, {}) + first_invocation = capture_metrics_output(capsys) + + lambda_handler({}, {}) + second_invocation = capture_metrics_output(capsys) + + # THEN we should have default dimensions in both outputs + assert "environment" in first_invocation + assert "environment" in second_invocation + + +def test_ephemeral_metrics_isolated_data_set_with_default_dimension(metric, dimension, namespace, capsys): + # GIVEN two EphemeralMetrics instances are initialized + # One with default dimension and another without + my_metrics = EphemeralMetrics(namespace=namespace) + my_metrics.set_default_dimensions(dev="powertools") + isolated_metrics = EphemeralMetrics(namespace=namespace) + + # WHEN metrics added to the both instances + my_metrics.add_metric(**metric) + isolated_metrics.add_metric(**metric) + + # THEN the second instance should not have dimensions + assert my_metrics.metric_set == isolated_metrics.metric_set + assert my_metrics.dimension_set != isolated_metrics.dimension_set + + def test_ephemeral_metrics_isolates_data_set(metric, dimension, namespace, metadata): # GIVEN two EphemeralMetrics instances are initialized my_metrics = EphemeralMetrics(namespace=namespace)