Skip to content

feat(metrics): support to set default dimension in EphemeralMetrics #2748

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions aws_lambda_powertools/metrics/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
6 changes: 3 additions & 3 deletions aws_lambda_powertools/shared/user_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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**
Expand All @@ -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))
Expand Down
3 changes: 1 addition & 2 deletions docs/core/metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?"

Expand Down
40 changes: 40 additions & 0 deletions tests/functional/test_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down