Skip to content

Commit dbc9f2e

Browse files
Refactoring logic + mypy still failling
1 parent 6569743 commit dbc9f2e

File tree

3 files changed

+71
-6
lines changed

3 files changed

+71
-6
lines changed

aws_lambda_powertools/metrics/functions.py

+57
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
from __future__ import annotations
22

3+
from datetime import datetime, timezone
4+
35
from aws_lambda_powertools.metrics.provider.cloudwatch_emf.exceptions import (
46
MetricResolutionError,
57
MetricUnitError,
68
)
79
from aws_lambda_powertools.metrics.provider.cloudwatch_emf.metric_properties import MetricResolution, MetricUnit
10+
from aws_lambda_powertools.shared import constants
811
from aws_lambda_powertools.shared.types import List
912

1013

@@ -69,3 +72,57 @@ def extract_cloudwatch_metric_unit_value(metric_units: List, metric_valid_option
6972
unit = unit.value
7073

7174
return unit
75+
76+
77+
def validate_emf_timestamp(timestamp: int | datetime) -> bool:
78+
"""
79+
Validates a given timestamp based on CloudWatch Timestamp guidelines.
80+
81+
Timestamp must meet CloudWatch requirements, otherwise an InvalidTimestampError will be raised.
82+
See [Timestamps](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#about_timestamp)
83+
for valid values.
84+
85+
Parameters:
86+
----------
87+
timestamp: int | datetime
88+
Datetime object or epoch time representing the timestamp to validate.
89+
90+
Returns
91+
-------
92+
bool
93+
Valid or not timestamp values
94+
"""
95+
96+
if not isinstance(timestamp, (int, datetime)):
97+
return False
98+
99+
if isinstance(timestamp, datetime):
100+
# Assuming the integer timestamp represents seconds since the epoch
101+
timestamp = int(timestamp.timestamp() * 1000)
102+
103+
current_time = int(datetime.now(timezone.utc).timestamp() * 1000)
104+
min_valid_timestamp = current_time - constants.EMF_MAX_TIMESTAMP_PAST_AGE
105+
max_valid_timestamp = current_time + constants.EMF_MAX_TIMESTAMP_FUTURE_AGE
106+
107+
return min_valid_timestamp <= timestamp <= max_valid_timestamp
108+
109+
110+
def convert_timestamp_to_emf_format(timestamp: int | datetime) -> int:
111+
"""
112+
Converts a timestamp to EMF compatible format.
113+
114+
Parameters
115+
----------
116+
timestamp: int | datetime
117+
The timestamp to convert. If already in milliseconds format, returns it as is.
118+
If datetime object, converts it to milliseconds since Unix epoch.
119+
120+
Returns:
121+
--------
122+
int
123+
The timestamp converted to EMF compatible format (milliseconds since Unix epoch).
124+
"""
125+
if isinstance(timestamp, int):
126+
return timestamp
127+
128+
return int(round(timestamp.timestamp() * 1000))

aws_lambda_powertools/metrics/provider/cloudwatch_emf/cloudwatch.py

+9-6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from aws_lambda_powertools.metrics.functions import (
1515
extract_cloudwatch_metric_resolution_value,
1616
extract_cloudwatch_metric_unit_value,
17+
validate_emf_timestamp,
1718
)
1819
from aws_lambda_powertools.metrics.provider.base import BaseProvider
1920
from aws_lambda_powertools.metrics.provider.cloudwatch_emf.constants import MAX_DIMENSIONS, MAX_METRICS
@@ -305,15 +306,17 @@ def add_metadata(self, key: str, value: Any) -> None:
305306
else:
306307
self.metadata_set[str(key)] = value
307308

308-
def set_timestamp(self, timestamp: int):
309-
if not isinstance(timestamp, int):
309+
def set_timestamp(self, timestamp: int | datetime.datetime):
310+
if not validate_emf_timestamp(timestamp):
310311
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.",
312+
"The timestamp must be a Datetime object or an integer representing an epoch time. "
313+
"This should not exceed 14 days in the past or be more than 2 hours in the future. "
314+
"Any metrics failing to meet this criteria will be skipped by Amazon CloudWatch. "
315+
"Please check the EMFValidationErrors metric in AWS/Logs namespace for more details.",
313316
stacklevel=2,
314317
)
315-
else:
316-
self.timestamp = timestamp
318+
319+
self.timestamp = timestamp
317320

318321
def clear_metrics(self) -> None:
319322
logger.debug("Clearing out existing metric set from memory")

aws_lambda_powertools/shared/constants.py

+5
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@
3939
METRICS_NAMESPACE_ENV: str = "POWERTOOLS_METRICS_NAMESPACE"
4040
DATADOG_FLUSH_TO_LOG: str = "DD_FLUSH_TO_LOG"
4141
SERVICE_NAME_ENV: str = "POWERTOOLS_SERVICE_NAME"
42+
# If the timestamp of log event is more than 2 hours in future, the log event is skipped.
43+
# If the timestamp of log event is more than 14 days in past, the log event is skipped.
44+
# See https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AgentReference.html
45+
EMF_MAX_TIMESTAMP_PAST_AGE = 14 * 24 * 60 * 60 * 1000 # 14 days
46+
EMF_MAX_TIMESTAMP_FUTURE_AGE = 2 * 60 * 60 * 1000 # 2 hours
4247

4348
# Parameters constants
4449
PARAMETERS_SSM_DECRYPT_ENV: str = "POWERTOOLS_PARAMETERS_SSM_DECRYPT"

0 commit comments

Comments
 (0)