Skip to content

Commit ef3347a

Browse files
committed
Add support for setTimestamp.
1 parent a635610 commit ef3347a

File tree

6 files changed

+122
-0
lines changed

6 files changed

+122
-0
lines changed

powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/Metrics.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package software.amazon.lambda.powertools.metrics;
1616

1717
import com.amazonaws.services.lambda.runtime.Context;
18+
import java.time.Instant;
1819

1920
import software.amazon.lambda.powertools.metrics.model.DimensionSet;
2021
import software.amazon.lambda.powertools.metrics.model.MetricResolution;
@@ -76,6 +77,13 @@ default void addDimension(String key, String value) {
7677
*/
7778
void addDimension(DimensionSet dimensionSet);
7879

80+
/**
81+
* Set a custom timestamp for the metrics
82+
*
83+
* @param timestamp the timestamp to use for the metrics
84+
*/
85+
void setTimestamp(Instant timestamp);
86+
7987
/**
8088
* Add metadata
8189
*

powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/EmfMetricsLogger.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.getXrayTraceId;
1818
import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.isColdStart;
1919

20+
import java.time.Instant;
2021
import java.util.HashMap;
2122
import java.util.LinkedHashMap;
2223
import java.util.Map;
@@ -135,6 +136,13 @@ public void setRaiseOnEmptyMetrics(boolean raiseOnEmptyMetrics) {
135136
this.raiseOnEmptyMetrics = raiseOnEmptyMetrics;
136137
}
137138

139+
@Override
140+
public void setTimestamp(Instant timestamp) {
141+
Validator.validateTimestamp(timestamp);
142+
143+
emfLogger.setTimestamp(timestamp);
144+
}
145+
138146
@Override
139147
public void clearDefaultDimensions() {
140148
emfLogger.resetDimensions(false);

powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/Validator.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414

1515
package software.amazon.lambda.powertools.metrics.internal;
1616

17+
import java.time.Instant;
18+
import java.util.concurrent.TimeUnit;
19+
1720
import org.apache.commons.lang3.StringUtils;
1821

1922
/**
@@ -24,6 +27,8 @@ public class Validator {
2427
private static final int MAX_DIMENSION_VALUE_LENGTH = 1024;
2528
private static final int MAX_NAMESPACE_LENGTH = 255;
2629
private static final String NAMESPACE_REGEX = "^[a-zA-Z0-9._#/]+$";
30+
public static final long MAX_TIMESTAMP_PAST_AGE_SECONDS = TimeUnit.DAYS.toSeconds(14);
31+
public static final long MAX_TIMESTAMP_FUTURE_AGE_SECONDS = TimeUnit.HOURS.toSeconds(2);
2732

2833
private Validator() {
2934
// Private constructor to prevent instantiation
@@ -50,6 +55,37 @@ public static void validateNamespace(String namespace) {
5055
}
5156
}
5257

58+
/**
59+
* Validates Timestamp.
60+
*
61+
* @see <a
62+
* href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#about_timestamp">CloudWatch
63+
* Timestamp</a>
64+
* @param timestamp Timestamp
65+
* @throws IllegalArgumentException if timestamp is invalid
66+
*/
67+
public static void validateTimestamp(Instant timestamp) {
68+
if (timestamp == null) {
69+
throw new IllegalArgumentException("Timestamp cannot be null");
70+
}
71+
72+
if (timestamp.isAfter(
73+
Instant.now().plusSeconds(MAX_TIMESTAMP_FUTURE_AGE_SECONDS))) {
74+
throw new IllegalArgumentException(
75+
"Timestamp cannot be more than "
76+
+ MAX_TIMESTAMP_FUTURE_AGE_SECONDS
77+
+ " seconds in the future");
78+
}
79+
80+
if (timestamp.isBefore(
81+
Instant.now().minusSeconds(MAX_TIMESTAMP_PAST_AGE_SECONDS))) {
82+
throw new IllegalArgumentException(
83+
"Timestamp cannot be more than "
84+
+ MAX_TIMESTAMP_PAST_AGE_SECONDS
85+
+ " seconds in the past");
86+
}
87+
}
88+
5389
/**
5490
* Validates a dimension key-value pair.
5591
*

powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/internal/EmfMetricsLoggerTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.lang.reflect.Method;
2424
import java.nio.charset.StandardCharsets;
2525
import java.nio.file.Files;
26+
import java.time.Instant;
2627
import java.util.stream.Stream;
2728

2829
import org.junit.jupiter.api.AfterEach;
@@ -187,6 +188,25 @@ void shouldAddDimension() throws Exception {
187188
assertThat(hasDimension).isTrue();
188189
}
189190

191+
@Test
192+
void shouldSetCustomTimestamp() throws Exception {
193+
// Given
194+
Instant customTimestamp = Instant.now();
195+
196+
// When
197+
metrics.setTimestamp(customTimestamp);
198+
metrics.addMetric("test-metric", 100);
199+
metrics.flush();
200+
201+
// Then
202+
String emfOutput = outputStreamCaptor.toString().trim();
203+
JsonNode rootNode = objectMapper.readTree(emfOutput);
204+
205+
assertThat(rootNode.has("_aws")).isTrue();
206+
assertThat(rootNode.get("_aws").has("Timestamp")).isTrue();
207+
assertThat(rootNode.get("_aws").get("Timestamp").asLong()).isEqualTo(customTimestamp.toEpochMilli());
208+
}
209+
190210
@Test
191211
void shouldAddDimensionSet() throws Exception {
192212
// Given

powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/internal/ValidatorTest.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import static org.assertj.core.api.Assertions.assertThatCode;
1818
import static org.assertj.core.api.Assertions.assertThatThrownBy;
1919

20+
import java.time.Instant;
21+
2022
import org.junit.jupiter.api.Test;
2123

2224
class ValidatorTest {
@@ -177,4 +179,46 @@ void shouldAcceptValidDimension() {
177179
assertThatCode(() -> Validator.validateDimension("ValidKey", "ValidValue"))
178180
.doesNotThrowAnyException();
179181
}
182+
183+
@Test
184+
void shouldThrowExceptionWhenTimestampIsNull() {
185+
// When/Then
186+
assertThatThrownBy(() -> Validator.validateTimestamp(null))
187+
.isInstanceOf(IllegalArgumentException.class)
188+
.hasMessage("Timestamp cannot be null");
189+
}
190+
191+
@Test
192+
void shouldThrowExceptionWhenTimestampIsTooFarInFuture() {
193+
// Given
194+
Instant futureTooFar = Instant.now().plusSeconds(Validator.MAX_TIMESTAMP_FUTURE_AGE_SECONDS + 1);
195+
196+
// When/Then
197+
assertThatThrownBy(() -> Validator.validateTimestamp(futureTooFar))
198+
.isInstanceOf(IllegalArgumentException.class)
199+
.hasMessage("Timestamp cannot be more than " + Validator.MAX_TIMESTAMP_FUTURE_AGE_SECONDS
200+
+ " seconds in the future");
201+
}
202+
203+
@Test
204+
void shouldThrowExceptionWhenTimestampIsTooFarInPast() {
205+
// Given
206+
Instant pastTooFar = Instant.now().minusSeconds(Validator.MAX_TIMESTAMP_PAST_AGE_SECONDS + 1);
207+
208+
// When/Then
209+
assertThatThrownBy(() -> Validator.validateTimestamp(pastTooFar))
210+
.isInstanceOf(IllegalArgumentException.class)
211+
.hasMessage("Timestamp cannot be more than " + Validator.MAX_TIMESTAMP_PAST_AGE_SECONDS
212+
+ " seconds in the past");
213+
}
214+
215+
@Test
216+
void shouldAcceptValidTimestamp() {
217+
// Given
218+
Instant validTimestamp = Instant.now();
219+
220+
// When/Then
221+
assertThatCode(() -> Validator.validateTimestamp(validTimestamp))
222+
.doesNotThrowAnyException();
223+
}
180224
}

powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/testutils/TestMetrics.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package software.amazon.lambda.powertools.metrics.testutils;
22

3+
import java.time.Instant;
34
import java.util.Collections;
45

56
import com.amazonaws.services.lambda.runtime.Context;
@@ -61,6 +62,11 @@ public void clearDefaultDimensions() {
6162
// Test placeholder
6263
}
6364

65+
@Override
66+
public void setTimestamp(Instant timestamp) {
67+
// Test placeholder
68+
}
69+
6470
@Override
6571
public void captureColdStartMetric(Context context, DimensionSet dimensions) {
6672
// Test placeholder

0 commit comments

Comments
 (0)