Skip to content

Commit 0fed904

Browse files
committed
feat: add metrics metadata #80
1 parent 2e3e0d1 commit 0fed904

File tree

3 files changed

+81
-4
lines changed

3 files changed

+81
-4
lines changed

aws_lambda_powertools/metrics/base.py

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import os
66
import pathlib
77
from enum import Enum
8-
from typing import Dict, List, Union
8+
from typing import Any, Dict, List, Union
99

1010
import fastjsonschema
1111

@@ -78,14 +78,20 @@ class MetricManager:
7878
"""
7979

8080
def __init__(
81-
self, metric_set: Dict[str, str] = None, dimension_set: Dict = None, namespace: str = None, service: str = None
81+
self,
82+
metric_set: Dict[str, str] = None,
83+
dimension_set: Dict = None,
84+
namespace: str = None,
85+
metadata_set: Dict[str, Any] = None,
86+
service: str = None,
8287
):
8388
self.metric_set = metric_set if metric_set is not None else {}
8489
self.dimension_set = dimension_set if dimension_set is not None else {}
8590
self.namespace = namespace or os.getenv("POWERTOOLS_METRICS_NAMESPACE")
8691
self.service = service or os.environ.get("POWERTOOLS_SERVICE_NAME")
8792
self._metric_units = [unit.value for unit in MetricUnit]
8893
self._metric_unit_options = list(MetricUnit.__members__)
94+
self.metadata_set = self.metadata_set if metadata_set is not None else {}
8995

9096
def add_metric(self, name: str, unit: MetricUnit, value: Union[float, int]):
9197
"""Adds given metric
@@ -131,7 +137,7 @@ def add_metric(self, name: str, unit: MetricUnit, value: Union[float, int]):
131137
# since we could have more than 100 metrics
132138
self.metric_set.clear()
133139

134-
def serialize_metric_set(self, metrics: Dict = None, dimensions: Dict = None) -> Dict:
140+
def serialize_metric_set(self, metrics: Dict = None, dimensions: Dict = None, metadata: Dict = None) -> Dict:
135141
"""Serializes metric and dimensions set
136142
137143
Parameters
@@ -165,6 +171,9 @@ def serialize_metric_set(self, metrics: Dict = None, dimensions: Dict = None) ->
165171
if dimensions is None: # pragma: no cover
166172
dimensions = self.dimension_set
167173

174+
if metadata is None: # pragma: no cover
175+
metadata = self.metadata_set
176+
168177
if self.service and not self.dimension_set.get("service"):
169178
self.dimension_set["service"] = self.service
170179

@@ -190,6 +199,7 @@ def serialize_metric_set(self, metrics: Dict = None, dimensions: Dict = None) ->
190199
metrics_timestamp = {"Timestamp": int(datetime.datetime.now().timestamp() * 1000)}
191200
metric_set["_aws"] = {**metrics_timestamp, **metrics_definition}
192201
metric_set.update(**dimensions)
202+
metric_set.update(**metadata)
193203

194204
try:
195205
logger.debug("Validating serialized metrics against CloudWatch EMF schema", metric_set)
@@ -225,6 +235,38 @@ def add_dimension(self, name: str, value: str):
225235
else:
226236
self.dimension_set[name] = str(value)
227237

238+
def add_metadata(self, key: str, value: Any):
239+
"""Adds high cardinal metadata for metrics object
240+
241+
This will not be available during metrics visualization.
242+
Instead, this will be searchable through logs.
243+
244+
If you're looking to add metadata to filter metrics, then
245+
use add_dimensions method.
246+
247+
Example
248+
-------
249+
**Add metrics metadata**
250+
251+
metric.add_metadata(key="booking_id", value="booking_id")
252+
253+
Parameters
254+
----------
255+
name : str
256+
Metadata key
257+
value : any
258+
Metadata value
259+
"""
260+
logger.debug(f"Adding metadata: {key}:{value}")
261+
262+
# Cast key to str according to EMF spec
263+
# Majority of keys are expected to be string already, so
264+
# checking before casting improves performance in most cases
265+
if isinstance(key, str):
266+
self.metadata_set[key] = value
267+
else:
268+
self.metadata_set[str(key)] = value
269+
228270
def __extract_metric_unit_value(self, unit: Union[str, MetricUnit]) -> str:
229271
"""Return metric value from metric unit whether that's str or MetricUnit enum
230272

aws_lambda_powertools/metrics/metrics.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,20 +67,28 @@ def do_something():
6767

6868
_metrics = {}
6969
_dimensions = {}
70+
_metadata = {}
7071

7172
def __init__(self, service: str = None, namespace: str = None):
7273
self.metric_set = self._metrics
7374
self.dimension_set = self._dimensions
7475
self.service = service
7576
self.namespace = namespace
77+
self.metadata_set = self._metadata
78+
7679
super().__init__(
77-
metric_set=self.metric_set, dimension_set=self.dimension_set, namespace=self.namespace, service=self.service
80+
metric_set=self.metric_set,
81+
dimension_set=self.dimension_set,
82+
namespace=self.namespace,
83+
metadata_set=self.metadata_set,
84+
service=self.service,
7885
)
7986

8087
def clear_metrics(self):
8188
logger.debug("Clearing out existing metric set from memory")
8289
self.metric_set.clear()
8390
self.dimension_set.clear()
91+
self.metadata_set.clear()
8492

8593
def log_metrics(
8694
self,

tests/functional/test_metrics.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,3 +533,30 @@ def lambda_handler(evt, ctx):
533533

534534
for metric_record in second_output["_aws"]["CloudWatchMetrics"]:
535535
assert ["service"] in metric_record["Dimensions"]
536+
537+
538+
def test_add_metadata_non_string_dimension_keys(service, metric, namespace):
539+
# GIVEN Metrics is initialized
540+
my_metrics = Metrics(service=service, namespace=namespace)
541+
my_metrics.add_metric(**metric)
542+
543+
# WHEN we utilize add_metadata with non-string keys
544+
my_metrics.add_metadata(key=10, value="number_ten")
545+
546+
# THEN we should have no exceptions
547+
# and dimension values should be serialized as strings
548+
expected_metadata = {"10": "number_ten"}
549+
assert my_metrics.metadata_set == expected_metadata
550+
551+
552+
def test_add_metadata(service, metric, namespace):
553+
# GIVEN Metrics is initialized
554+
my_metrics = Metrics(service=service, namespace=namespace)
555+
my_metrics.add_metric(**metric)
556+
557+
# WHEN we utilize add_metadata with non-string keys
558+
my_metrics.add_metadata(key="username", value="test")
559+
560+
# THEN we should have no exceptions
561+
# and dimension values should be serialized as strings
562+
assert my_metrics.metadata_set == {"username": "test"}

0 commit comments

Comments
 (0)