Skip to content

Commit 76a7c35

Browse files
committed
Merge branch 'develop' into feat/tracer-disallow-response-metadata
* develop: chore: clarify changelog bugfix vs breaking change chore: remove/correct unnecessary debug logs chore: fix debug log adding unused obj improv: explicitly assert not having ColdStart metric chore: grammar chore: add metrics fix description fix: update cold_start doc to reflect #125 fix: split ColdStart metric to its own EMF blob #125 chore: correct typos
2 parents 0a61687 + ad00a92 commit 76a7c35

File tree

6 files changed

+63
-13
lines changed

6 files changed

+63
-13
lines changed

Diff for: CHANGELOG.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
### Fixed
10+
- **Metrics**: Cold start metric is now completely separate from application metrics dimensions, making it easier and cheaper to visualize.
11+
- This is a breaking change if you were graphing/alerting on both application metrics with the same name to compensate this previous malfunctioning
12+
- Marked as bugfix as this is the intended behaviour since the beginning, as you shouldn't have the same application metric with different dimensions
13+
914
## [1.3.1] - 2020-08-22
1015
### Fixed
1116
- **Tracer**: capture_method decorator did not properly handle nested context managers
@@ -44,7 +49,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4449

4550
## [1.0.1] - 2020-07-06
4651
### Fixed
47-
- **Logger**: Fix a bug with `inject_lambda_context` causing existing an Logger keys to be overriden if `structure_logs` was called before
52+
- **Logger**: Fix a bug with `inject_lambda_context` causing existing Logger keys to be overridden if `structure_logs` was called before
4853

4954
## [1.0.0] - 2020-06-18
5055
### Added
@@ -114,7 +119,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
114119

115120
## [0.8.0] - 2020-04-24
116121
### Added
117-
- **Logger**: Introduced `Logger` class for stuctured logging as a replacement for `logger_setup`
122+
- **Logger**: Introduced `Logger` class for structured logging as a replacement for `logger_setup`
118123
- **Logger**: Introduced `Logger.inject_lambda_context` decorator as a replacement for `logger_inject_lambda_context`
119124

120125
### Removed

Diff for: aws_lambda_powertools/metrics/base.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def serialize_metric_set(self, metrics: Dict = None, dimensions: Dict = None, me
177177
if self.service and not self.dimension_set.get("service"):
178178
self.dimension_set["service"] = self.service
179179

180-
logger.debug("Serializing...", {"metrics": metrics, "dimensions": dimensions})
180+
logger.debug({"details": "Serializing metrics", "metrics": metrics, "dimensions": dimensions})
181181

182182
metric_names_and_units: List[Dict[str, str]] = [] # [ { "Name": "metric_name", "Unit": "Count" } ]
183183
metric_names_and_values: Dict[str, str] = {} # { "metric_name": 1.0 }
@@ -207,7 +207,7 @@ def serialize_metric_set(self, metrics: Dict = None, dimensions: Dict = None, me
207207
}
208208

209209
try:
210-
logger.debug("Validating serialized metrics against CloudWatch EMF schema", embedded_metrics_object)
210+
logger.debug("Validating serialized metrics against CloudWatch EMF schema")
211211
fastjsonschema.validate(definition=CLOUDWATCH_EMF_SCHEMA, data=embedded_metrics_object)
212212
except fastjsonschema.JsonSchemaException as e:
213213
message = f"Invalid format. Error: {e.message}, Invalid item: {e.name}" # noqa: B306, E501

Diff for: aws_lambda_powertools/metrics/metric.py

-2
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,6 @@ def single_metric(name: str, unit: MetricUnit, value: float, namespace: str = No
110110
metric: SingleMetric = SingleMetric(namespace=namespace)
111111
metric.add_metric(name=name, unit=unit, value=value)
112112
yield metric
113-
logger.debug("Serializing single metric")
114113
metric_set: Dict = metric.serialize_metric_set()
115114
finally:
116-
logger.debug("Publishing single metric", {"metric": metric})
117115
print(json.dumps(metric_set))

Diff for: aws_lambda_powertools/metrics/metrics.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
import warnings
55
from typing import Any, Callable
66

7-
from .base import MetricManager
7+
from .base import MetricManager, MetricUnit
8+
from .metric import single_metric
89

910
logger = logging.getLogger(__name__)
1011

@@ -149,7 +150,6 @@ def decorate(event, context):
149150
else:
150151
metrics = self.serialize_metric_set()
151152
self.clear_metrics()
152-
logger.debug("Publishing metrics", {"metrics": metrics})
153153
print(json.dumps(metrics))
154154

155155
return response
@@ -167,6 +167,7 @@ def __add_cold_start_metric(self, context: Any):
167167
global is_cold_start
168168
if is_cold_start:
169169
logger.debug("Adding cold start metric and function_name dimension")
170-
self.add_metric(name="ColdStart", value=1, unit="Count")
171-
self.add_dimension(name="function_name", value=context.function_name)
172-
is_cold_start = False
170+
with single_metric(name="ColdStart", unit=MetricUnit.Count, value=1, namespace=self.namespace) as metric:
171+
metric.add_dimension(name="function_name", value=context.function_name)
172+
metric.add_dimension(name="service", value=self.service)
173+
is_cold_start = False

Diff for: docs/content/core/metrics.mdx

+6-1
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,12 @@ def lambda_handler(evt, ctx):
238238
...
239239
```
240240

241-
If it's a cold start, this feature will add a metric named `ColdStart` and a dimension named `function_name`.
241+
If it's a cold start invocation, this feature will:
242+
243+
* Create a separate EMF blob solely containing a metric named `ColdStart`
244+
* Add `function_name` and `service` dimensions
245+
246+
This has the advantage of keeping cold start metric separate from your application metrics.
242247

243248
## Testing your code
244249

Diff for: tests/functional/test_metrics.py

+42-1
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77

88
from aws_lambda_powertools import Metrics, single_metric
99
from aws_lambda_powertools.metrics import MetricUnit, MetricUnitError, MetricValueError, SchemaValidationError
10+
from aws_lambda_powertools.metrics import metrics as metrics_global
1011
from aws_lambda_powertools.metrics.base import MetricManager
1112

1213

1314
@pytest.fixture(scope="function", autouse=True)
1415
def reset_metric_set():
1516
metrics = Metrics()
1617
metrics.clear_metrics()
18+
metrics_global.is_cold_start = True # ensure each test has cold start
1719
yield
1820

1921

@@ -112,6 +114,10 @@ def capture_metrics_output(capsys):
112114
return json.loads(capsys.readouterr().out.strip())
113115

114116

117+
def capture_metrics_output_multiple_emf_objects(capsys):
118+
return [json.loads(line.strip()) for line in capsys.readouterr().out.split("\n") if line]
119+
120+
115121
def test_single_metric_logs_one_metric_only(capsys, metric, dimension, namespace):
116122
# GIVEN we try adding more than one metric
117123
# WHEN using single_metric context manager
@@ -495,7 +501,7 @@ def lambda_handler(evt, context):
495501

496502
LambdaContext = namedtuple("LambdaContext", "function_name")
497503
lambda_handler({}, LambdaContext("example_fn"))
498-
_ = capture_metrics_output(capsys) # ignore first stdout captured
504+
_, _ = capture_metrics_output_multiple_emf_objects(capsys) # ignore first stdout captured
499505

500506
# THEN ColdStart metric and function_name dimension should be logged once
501507
lambda_handler({}, LambdaContext("example_fn"))
@@ -630,3 +636,38 @@ def test_serialize_metric_set_metric_definition(metric, dimension, namespace, se
630636
assert "Timestamp" in metric_definition_output["_aws"]
631637
remove_timestamp(metrics=[metric_definition_output, expected_metric_definition])
632638
assert metric_definition_output == expected_metric_definition
639+
640+
641+
def test_log_metrics_capture_cold_start_metric_separately(capsys, namespace, service, metric, dimension):
642+
# GIVEN Metrics is initialized
643+
my_metrics = Metrics(service=service, namespace=namespace)
644+
645+
# WHEN log_metrics is used with capture_cold_start_metric
646+
@my_metrics.log_metrics(capture_cold_start_metric=True)
647+
def lambda_handler(evt, context):
648+
my_metrics.add_metric(**metric)
649+
my_metrics.add_dimension(**dimension)
650+
651+
LambdaContext = namedtuple("LambdaContext", "function_name")
652+
lambda_handler({}, LambdaContext("example_fn"))
653+
654+
cold_start_blob, custom_metrics_blob = capture_metrics_output_multiple_emf_objects(capsys)
655+
656+
# THEN ColdStart metric and function_name dimension should be logged
657+
# in a separate EMF blob than the application metrics
658+
assert cold_start_blob["ColdStart"] == 1
659+
assert cold_start_blob["function_name"] == "example_fn"
660+
assert cold_start_blob["service"] == service
661+
662+
# and that application metrics dimensions are not part of ColdStart EMF blob
663+
assert "test_dimension" not in cold_start_blob
664+
665+
# THEN application metrics EMF blob should not have
666+
# ColdStart metric nor function_name dimension
667+
assert "function_name" not in custom_metrics_blob
668+
assert "ColdStart" not in custom_metrics_blob
669+
670+
# and that application metrics are recorded as normal
671+
assert custom_metrics_blob["service"] == service
672+
assert custom_metrics_blob["single_metric"] == metric["value"]
673+
assert custom_metrics_blob["test_dimension"] == dimension["value"]

0 commit comments

Comments
 (0)