1
1
import { Console } from 'node:console' ;
2
- import { Utility } from '@aws-lambda-powertools/commons' ;
2
+ import { isDate } from 'node:util/types' ;
3
+ import { Utility , isIntegerNumber } from '@aws-lambda-powertools/commons' ;
3
4
import type {
4
5
GenericLogger ,
5
6
HandlerMethodDecorator ,
@@ -9,6 +10,8 @@ import { EnvironmentVariablesService } from './config/EnvironmentVariablesServic
9
10
import {
10
11
COLD_START_METRIC ,
11
12
DEFAULT_NAMESPACE ,
13
+ EMF_MAX_TIMESTAMP_FUTURE_AGE ,
14
+ EMF_MAX_TIMESTAMP_PAST_AGE ,
12
15
MAX_DIMENSION_COUNT ,
13
16
MAX_METRICS_SIZE ,
14
17
MAX_METRIC_VALUES_SIZE ,
@@ -198,6 +201,11 @@ class Metrics extends Utility implements MetricsInterface {
198
201
*/
199
202
private storedMetrics : StoredMetrics = { } ;
200
203
204
+ /**
205
+ * Custom timestamp for the metrics
206
+ */
207
+ #timestamp?: number ;
208
+
201
209
public constructor ( options : MetricsOptions = { } ) {
202
210
super ( ) ;
203
211
@@ -571,6 +579,46 @@ class Metrics extends Utility implements MetricsInterface {
571
579
this . clearMetadata ( ) ;
572
580
}
573
581
582
+ /**
583
+ * Sets the timestamp for the metric.
584
+ *
585
+ * If an integer is provided, it is assumed to be the epoch time in milliseconds.
586
+ * If a Date object is provided, it will be converted to epoch time in milliseconds.
587
+ *
588
+ * The timestamp must be a Date object or an integer representing an epoch time.
589
+ * This should not exceed 14 days in the past or be more than 2 hours in the future.
590
+ * Any metrics failing to meet this criteria will be skipped by Amazon CloudWatch.
591
+ *
592
+ * See: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html
593
+ * See: https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CloudWatch-Logs-Monitoring-CloudWatch-Metrics.html
594
+ *
595
+ * @example
596
+ * ```typescript
597
+ * import { MetricUnit, Metrics } from '@aws-lambda-powertools/metrics';
598
+ *
599
+ * const metrics = new Metrics({
600
+ * namespace: 'serverlessAirline',
601
+ * serviceName: 'orders',
602
+ * });
603
+ *
604
+ * export const handler = async () => {
605
+ * const metricTimestamp = new Date(Date.now() - 24 * 60 * 60 * 1000); // 24 hours ago
606
+ * metrics.setTimestamp(metricTimestamp);
607
+ * metrics.addMetric('successfulBooking', MetricUnit.Count, 1);
608
+ * };
609
+ * ```
610
+ * @param timestamp - The timestamp to set, which can be a number or a Date object.
611
+ */
612
+ public setTimestamp ( timestamp : number | Date ) : void {
613
+ if ( ! this . #validateEmfTimestamp( timestamp ) ) {
614
+ this . #logger. warn (
615
+ "This metric doesn't meet the requirements and will be skipped by Amazon CloudWatch. " +
616
+ 'Ensure the timestamp is within 14 days in the past or up to 2 hours in the future and is also a valid number or Date object.'
617
+ ) ;
618
+ }
619
+ this . #timestamp = this . #convertTimestampToEmfFormat( timestamp ) ;
620
+ }
621
+
574
622
/**
575
623
* Serialize the stored metrics into a JSON object compliant with the Amazon CloudWatch EMF (Embedded Metric Format) schema.
576
624
*
@@ -627,7 +675,7 @@ class Metrics extends Utility implements MetricsInterface {
627
675
628
676
return {
629
677
_aws : {
630
- Timestamp : new Date ( ) . getTime ( ) ,
678
+ Timestamp : this . #timestamp ?? new Date ( ) . getTime ( ) ,
631
679
CloudWatchMetrics : [
632
680
{
633
681
Namespace : this . namespace || DEFAULT_NAMESPACE ,
@@ -940,6 +988,51 @@ class Metrics extends Utility implements MetricsInterface {
940
988
}
941
989
}
942
990
}
991
+
992
+ /**
993
+ * Validates a given timestamp based on CloudWatch Timestamp guidelines.
994
+ *
995
+ * Timestamp must meet CloudWatch requirements.
996
+ * The time stamp can be up to two weeks in the past and up to two hours into the future.
997
+ * See [Timestamps](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#about_timestamp)
998
+ * for valid values.
999
+ *
1000
+ * @param timestamp - Date object or epoch time in milliseconds representing the timestamp to validate.
1001
+ */
1002
+ #validateEmfTimestamp( timestamp : number | Date ) : boolean {
1003
+ if ( ! isDate ( timestamp ) && ! isIntegerNumber ( timestamp ) ) {
1004
+ return false ;
1005
+ }
1006
+
1007
+ const timestampMs = isDate ( timestamp ) ? timestamp . getTime ( ) : timestamp ;
1008
+ const currentTime = new Date ( ) . getTime ( ) ;
1009
+
1010
+ const minValidTimestamp = currentTime - EMF_MAX_TIMESTAMP_PAST_AGE ;
1011
+ const maxValidTimestamp = currentTime + EMF_MAX_TIMESTAMP_FUTURE_AGE ;
1012
+
1013
+ return timestampMs >= minValidTimestamp && timestampMs <= maxValidTimestamp ;
1014
+ }
1015
+
1016
+ /**
1017
+ * Converts a given timestamp to EMF compatible format.
1018
+ *
1019
+ * @param timestamp - The timestamp to convert, which can be either a number (in milliseconds) or a Date object.
1020
+ * @returns The timestamp in milliseconds. If the input is invalid, returns 0.
1021
+ */
1022
+ #convertTimestampToEmfFormat( timestamp : number | Date ) : number {
1023
+ if ( isIntegerNumber ( timestamp ) ) {
1024
+ return timestamp ;
1025
+ }
1026
+ if ( isDate ( timestamp ) ) {
1027
+ return timestamp . getTime ( ) ;
1028
+ }
1029
+ /**
1030
+ * If this point is reached, it indicates timestamp was neither a valid number nor Date
1031
+ * Returning zero represents the initial date of epoch time,
1032
+ * which will be skipped by Amazon CloudWatch.
1033
+ **/
1034
+ return 0 ;
1035
+ }
943
1036
}
944
1037
945
1038
export { Metrics } ;
0 commit comments