11import { 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' ;
34import type {
45 GenericLogger ,
56 HandlerMethodDecorator ,
@@ -9,6 +10,8 @@ import { EnvironmentVariablesService } from './config/EnvironmentVariablesServic
910import {
1011 COLD_START_METRIC ,
1112 DEFAULT_NAMESPACE ,
13+ EMF_MAX_TIMESTAMP_FUTURE_AGE ,
14+ EMF_MAX_TIMESTAMP_PAST_AGE ,
1215 MAX_DIMENSION_COUNT ,
1316 MAX_METRICS_SIZE ,
1417 MAX_METRIC_VALUES_SIZE ,
@@ -198,6 +201,11 @@ class Metrics extends Utility implements MetricsInterface {
198201 */
199202 private storedMetrics : StoredMetrics = { } ;
200203
204+ /**
205+ * Custom timestamp for the metrics
206+ */
207+ #timestamp?: number ;
208+
201209 public constructor ( options : MetricsOptions = { } ) {
202210 super ( ) ;
203211
@@ -571,6 +579,46 @@ class Metrics extends Utility implements MetricsInterface {
571579 this . clearMetadata ( ) ;
572580 }
573581
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+
574622 /**
575623 * Serialize the stored metrics into a JSON object compliant with the Amazon CloudWatch EMF (Embedded Metric Format) schema.
576624 *
@@ -627,7 +675,7 @@ class Metrics extends Utility implements MetricsInterface {
627675
628676 return {
629677 _aws : {
630- Timestamp : new Date ( ) . getTime ( ) ,
678+ Timestamp : this . #timestamp ?? new Date ( ) . getTime ( ) ,
631679 CloudWatchMetrics : [
632680 {
633681 Namespace : this . namespace || DEFAULT_NAMESPACE ,
@@ -940,6 +988,51 @@ class Metrics extends Utility implements MetricsInterface {
940988 }
941989 }
942990 }
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+ }
9431036}
9441037
9451038export { Metrics } ;
0 commit comments