diff --git a/aws_lambda_powertools/metrics/base.py b/aws_lambda_powertools/metrics/base.py index 65e47cd5061..22456f6925c 100644 --- a/aws_lambda_powertools/metrics/base.py +++ b/aws_lambda_powertools/metrics/base.py @@ -286,9 +286,8 @@ def add_dimension(self, name: str, value: str) -> None: f"Maximum number of dimensions exceeded ({MAX_DIMENSIONS}): Unable to add dimension {name}.", ) # Cast value to str according to EMF spec - # Majority of values are expected to be string already, so - # checking before casting improves performance in most cases - self.dimension_set[name] = value if isinstance(value, str) else str(value) + # Majority of values are expected to be string already + self.dimension_set[name] = str(value) def add_metadata(self, key: str, value: Any) -> None: """Adds high cardinal metadata for metrics object @@ -315,12 +314,8 @@ def add_metadata(self, key: str, value: Any) -> None: logger.debug(f"Adding metadata: {key}:{value}") # Cast key to str according to EMF spec - # Majority of keys are expected to be string already, so - # checking before casting improves performance in most cases - if isinstance(key, str): - self.metadata_set[key] = value - else: - self.metadata_set[str(key)] = value + # Majority of keys are expected to be string already + self.metadata_set[str(key)] = value def set_timestamp(self, timestamp: int | datetime.datetime): """ diff --git a/aws_lambda_powertools/metrics/metrics.py b/aws_lambda_powertools/metrics/metrics.py index 2ed7b43c35e..3f6d05b6a1c 100644 --- a/aws_lambda_powertools/metrics/metrics.py +++ b/aws_lambda_powertools/metrics/metrics.py @@ -91,21 +91,14 @@ def __init__( provider: AmazonCloudWatchEMFProvider | None = None, function_name: str | None = None, ): - self.metric_set = self._metrics - self.metadata_set = self._metadata - self.default_dimensions = self._default_dimensions - self.dimension_set = self._dimensions - - self.dimension_set.update(**self._default_dimensions) - if provider is None: self.provider = AmazonCloudWatchEMFProvider( namespace=namespace, service=service, - metric_set=self.metric_set, - dimension_set=self.dimension_set, - metadata_set=self.metadata_set, - default_dimensions=self._default_dimensions, + metric_set=Metrics._metrics, + dimension_set=Metrics._dimensions, + metadata_set=Metrics._metadata, + default_dimensions=Metrics._default_dimensions, function_name=function_name, ) else: @@ -174,7 +167,6 @@ def log_metrics( ) def set_default_dimensions(self, **dimensions) -> None: - self.provider.set_default_dimensions(**dimensions) """Persist dimensions across Lambda invocations Parameters @@ -195,14 +187,10 @@ def set_default_dimensions(self, **dimensions) -> None: def lambda_handler(): return True """ - for name, value in dimensions.items(): - self.add_dimension(name, value) - - self.default_dimensions.update(**dimensions) + self.provider.set_default_dimensions(**dimensions) def clear_default_dimensions(self) -> None: self.provider.default_dimensions.clear() - self.default_dimensions.clear() def clear_metrics(self) -> None: self.provider.clear_metrics() @@ -227,6 +215,22 @@ def service(self): def service(self, service): self.provider.service = service + @property + def metric_set(self): + return self.provider.metric_set + + @property + def dimension_set(self): + return self.provider.dimension_set + + @property + def metadata_set(self): + return self.provider.metadata_set + + @property + def default_dimensions(self): + return self.provider.default_dimensions + # Maintenance: until v3, we can't afford to break customers. # AmazonCloudWatchEMFProvider has the exact same functionality (non-singleton) diff --git a/aws_lambda_powertools/metrics/provider/cloudwatch_emf/cloudwatch.py b/aws_lambda_powertools/metrics/provider/cloudwatch_emf/cloudwatch.py index 243fc561593..97e6e8c0ffb 100644 --- a/aws_lambda_powertools/metrics/provider/cloudwatch_emf/cloudwatch.py +++ b/aws_lambda_powertools/metrics/provider/cloudwatch_emf/cloudwatch.py @@ -87,7 +87,7 @@ def __init__( ): self.metric_set = metric_set if metric_set is not None else {} self.dimension_set = dimension_set if dimension_set is not None else {} - self.default_dimensions = default_dimensions or {} + self.default_dimensions = default_dimensions if default_dimensions is not None else {} self.namespace = resolve_env_var_choice(choice=namespace, env=os.getenv(constants.METRICS_NAMESPACE_ENV)) self.service = resolve_env_var_choice(choice=service, env=os.getenv(constants.SERVICE_NAME_ENV)) self.function_name = function_name @@ -100,7 +100,8 @@ def __init__( self._metric_unit_valid_options = list(MetricUnit.__members__) self._metric_resolutions = [resolution.value for resolution in MetricResolution] - self.dimension_set.update(**self.default_dimensions) + for name, value in self.default_dimensions.items(): + self.add_dimension(name, value) def add_metric( self, @@ -306,7 +307,7 @@ def add_dimension(self, name: str, value: str) -> None: f"Maximum number of dimensions exceeded ({MAX_DIMENSIONS}): Unable to add dimension {name}.", ) - value = value if isinstance(value, str) else str(value) + value = str(value) if not name.strip() or not value.strip(): warnings.warn( @@ -317,7 +318,7 @@ def add_dimension(self, name: str, value: str) -> None: ) return - if name in self.dimension_set or name in self.default_dimensions: + if name in self.dimension_set: warnings.warn( f"Dimension '{name}' has already been added. The previous value will be overwritten.", category=PowertoolsUserWarning, @@ -415,12 +416,8 @@ def add_metadata(self, key: str, value: Any) -> None: logger.debug(f"Adding metadata: {key}:{value}") # Cast key to str according to EMF spec - # Majority of keys are expected to be string already, so - # checking before casting improves performance in most cases - if isinstance(key, str): - self.metadata_set[key] = value - else: - self.metadata_set[str(key)] = value + # Majority of keys are expected to be string already + self.metadata_set[str(key)] = value def set_timestamp(self, timestamp: int | datetime.datetime): """ @@ -453,7 +450,8 @@ def clear_metrics(self) -> None: self.dimension_set.clear() self.dimension_sets.clear() self.metadata_set.clear() - self.set_default_dimensions(**self.default_dimensions) + for name, value in self.default_dimensions.items(): + self.add_dimension(name, value) def flush_metrics(self, raise_on_empty_metrics: bool = False) -> None: """Manually flushes the metrics. This is normally not necessary, diff --git a/tests/functional/metrics/required_dependencies/test_metrics_cloudwatch_emf.py b/tests/functional/metrics/required_dependencies/test_metrics_cloudwatch_emf.py index 834575e4754..d965f5cf1f4 100644 --- a/tests/functional/metrics/required_dependencies/test_metrics_cloudwatch_emf.py +++ b/tests/functional/metrics/required_dependencies/test_metrics_cloudwatch_emf.py @@ -1221,6 +1221,28 @@ def lambda_handler(evt, ctx): assert "environment" in second_invocation +def test_flush_metrics_with_default_dimensions(capsys, metrics, dimensions, namespace): + # GIVEN a Metrics is initialized + my_metrics = Metrics(namespace=namespace) + my_metrics.set_default_dimensions(environment="test", log_group="/lambda/test") + + # WHEN we add_metric and flush_metrics + # THEN we should have no warnings + with warnings.catch_warnings(record=True) as w: + for metric in metrics: + my_metrics.add_metric(**metric) + assert not w + + with warnings.catch_warnings(record=True) as w: + my_metrics.flush_metrics() + assert not w + + # THEN we should have default dimensions in output + output = capture_metrics_output(capsys) + assert "environment" in output + assert "log_group" in output + + def test_metrics_reuse_dimension_set(metric, dimension, namespace): # GIVEN Metrics is initialized with a metric and dimension my_metrics = Metrics(namespace=namespace)