Skip to content

Commit 6569743

Browse files
Initial commit
1 parent 43eac11 commit 6569743

File tree

4 files changed

+73
-2
lines changed

4 files changed

+73
-2
lines changed

aws_lambda_powertools/metrics/base.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ def __init__(
7676
self.namespace = resolve_env_var_choice(choice=namespace, env=os.getenv(constants.METRICS_NAMESPACE_ENV))
7777
self.service = resolve_env_var_choice(choice=service, env=os.getenv(constants.SERVICE_NAME_ENV))
7878
self.metadata_set = metadata_set if metadata_set is not None else {}
79+
self.timestamp: int | None = None
80+
7981
self._metric_units = [unit.value for unit in MetricUnit]
8082
self._metric_unit_valid_options = list(MetricUnit.__members__)
8183
self._metric_resolutions = [resolution.value for resolution in MetricResolution]
@@ -224,7 +226,7 @@ def serialize_metric_set(
224226

225227
return {
226228
"_aws": {
227-
"Timestamp": int(datetime.datetime.now().timestamp() * 1000), # epoch
229+
"Timestamp": self.timestamp or int(datetime.datetime.now().timestamp() * 1000), # epoch
228230
"CloudWatchMetrics": [
229231
{
230232
"Namespace": self.namespace, # "test_namespace"
@@ -296,6 +298,16 @@ def add_metadata(self, key: str, value: Any) -> None:
296298
else:
297299
self.metadata_set[str(key)] = value
298300

301+
def set_timestamp(self, timestamp: int):
302+
if not isinstance(timestamp, int):
303+
warnings.warn(
304+
"The timestamp key must be an integer value representing an epoch time. ",
305+
"The provided value is not valid. Using the current timestamp instead.",
306+
stacklevel=2,
307+
)
308+
else:
309+
self.timestamp = timestamp
310+
299311
def clear_metrics(self) -> None:
300312
logger.debug("Clearing out existing metric set from memory")
301313
self.metric_set.clear()

aws_lambda_powertools/metrics/metrics.py

+3
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ def serialize_metric_set(
125125
def add_metadata(self, key: str, value: Any) -> None:
126126
self.provider.add_metadata(key=key, value=value)
127127

128+
def set_timestamp(self, timestamp: int):
129+
self.provider.set_timestamp(timestamp=timestamp)
130+
128131
def flush_metrics(self, raise_on_empty_metrics: bool = False) -> None:
129132
self.provider.flush_metrics(raise_on_empty_metrics=raise_on_empty_metrics)
130133

aws_lambda_powertools/metrics/provider/cloudwatch_emf/cloudwatch.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ def __init__(
7373
self.namespace = resolve_env_var_choice(choice=namespace, env=os.getenv(constants.METRICS_NAMESPACE_ENV))
7474
self.service = resolve_env_var_choice(choice=service, env=os.getenv(constants.SERVICE_NAME_ENV))
7575
self.metadata_set = metadata_set if metadata_set is not None else {}
76+
self.timestamp: int | None = None
7677

7778
self._metric_units = [unit.value for unit in MetricUnit]
7879
self._metric_unit_valid_options = list(MetricUnit.__members__)
@@ -231,7 +232,7 @@ def serialize_metric_set(
231232

232233
return {
233234
"_aws": {
234-
"Timestamp": int(datetime.datetime.now().timestamp() * 1000), # epoch
235+
"Timestamp": self.timestamp or int(datetime.datetime.now().timestamp() * 1000), # epoch
235236
"CloudWatchMetrics": [
236237
{
237238
"Namespace": self.namespace, # "test_namespace"
@@ -304,6 +305,16 @@ def add_metadata(self, key: str, value: Any) -> None:
304305
else:
305306
self.metadata_set[str(key)] = value
306307

308+
def set_timestamp(self, timestamp: int):
309+
if not isinstance(timestamp, int):
310+
warnings.warn(
311+
"The timestamp key must be an integer value representing an epoch time. "
312+
"The provided value is not valid. Using the current timestamp instead.",
313+
stacklevel=2,
314+
)
315+
else:
316+
self.timestamp = timestamp
317+
307318
def clear_metrics(self) -> None:
308319
logger.debug("Clearing out existing metric set from memory")
309320
self.metric_set.clear()

tests/functional/metrics/test_metrics_cloudwatch_emf.py

+45
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import datetime
34
import json
45
import warnings
56
from collections import namedtuple
@@ -1213,3 +1214,47 @@ def lambda_handler(evt, ctx):
12131214

12141215
output = capture_metrics_output_multiple_emf_objects(capsys)
12151216
assert len(output) == 2
1217+
1218+
1219+
def test_metric_with_custom_timestamp(namespace, metric, capsys):
1220+
# GIVEN Metrics instance is initialized
1221+
my_metrics = Metrics(namespace=namespace)
1222+
1223+
# Calculate the metric timestamp as 2 days before the current time
1224+
metric_timestamp = int((datetime.datetime.now() - datetime.timedelta(days=2)).timestamp() * 1000)
1225+
1226+
# WHEN we set custom timestamp before to flush the metric
1227+
@my_metrics.log_metrics
1228+
def lambda_handler(evt, ctx):
1229+
my_metrics.add_metric(**metric)
1230+
my_metrics.set_timestamp(metric_timestamp)
1231+
1232+
lambda_handler({}, {})
1233+
invocation = capture_metrics_output(capsys)
1234+
1235+
# THEN Timestamp must be the custom value
1236+
assert invocation["_aws"]["Timestamp"] == metric_timestamp
1237+
1238+
1239+
def test_metric_with_wrong_custom_timestamp(namespace, metric):
1240+
# GIVEN Metrics instance is initialized
1241+
my_metrics = Metrics(namespace=namespace)
1242+
1243+
# Setting timestamp as datetime
1244+
metric_timestamp = datetime.datetime.now()
1245+
1246+
# WHEN we set a wrong timestamp before to flush the metric
1247+
@my_metrics.log_metrics
1248+
def lambda_handler(evt, ctx):
1249+
my_metrics.add_metric(**metric)
1250+
my_metrics.set_timestamp(metric_timestamp)
1251+
1252+
# THEN should raise a warning
1253+
with warnings.catch_warnings(record=True) as w:
1254+
warnings.simplefilter("default")
1255+
lambda_handler({}, {})
1256+
assert len(w) == 1
1257+
assert str(w[-1].message) == (
1258+
"The timestamp key must be an integer value representing an epoch time. "
1259+
"The provided value is not valid. Using the current timestamp instead."
1260+
)

0 commit comments

Comments
 (0)