Skip to content

Commit e7de05c

Browse files
committed
Avoid metrics from raising an exception when using plain mocked Lambda context.
1 parent 7cea2a6 commit e7de05c

File tree

4 files changed

+125
-2
lines changed

4 files changed

+125
-2
lines changed

powertools-metrics/pom.xml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@
105105
<artifactId>assertj-core</artifactId>
106106
<scope>test</scope>
107107
</dependency>
108+
<dependency>
109+
<groupId>org.mockito</groupId>
110+
<artifactId>mockito-junit-jupiter</artifactId>
111+
<scope>test</scope>
112+
</dependency>
108113
<dependency>
109114
<groupId>software.amazon.lambda</groupId>
110115
<artifactId>powertools-common</artifactId>
@@ -135,6 +140,13 @@
135140
</profile>
136141
<profile>
137142
<id>generate-graalvm-files</id>
143+
<dependencies>
144+
<dependency>
145+
<groupId>org.mockito</groupId>
146+
<artifactId>mockito-subclass</artifactId>
147+
<scope>test</scope>
148+
</dependency>
149+
</dependencies>
138150
<build>
139151
<plugins>
140152
<plugin>
@@ -154,6 +166,13 @@
154166
</profile>
155167
<profile>
156168
<id>graalvm-native</id>
169+
<dependencies>
170+
<dependency>
171+
<groupId>org.mockito</groupId>
172+
<artifactId>mockito-subclass</artifactId>
173+
<scope>test</scope>
174+
</dependency>
175+
</dependencies>
157176
<build>
158177
<plugins>
159178
<plugin>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ public void captureColdStartMetric(Context context,
204204
}
205205

206206
// Add request ID from context if available
207-
if (context != null) {
207+
if (context != null && context.getAwsRequestId() != null) {
208208
coldStartLogger.putProperty(REQUEST_ID_PROPERTY, context.getAwsRequestId());
209209
}
210210

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,10 @@ private void captureColdStartMetricIfEnabled(Context extractedContext, FlushMetr
113113
}
114114

115115
Metrics metricsInstance = MetricsFactory.getMetricsInstance();
116-
metricsInstance.addMetadata(REQUEST_ID_PROPERTY, extractedContext.getAwsRequestId());
116+
// This can be null e.g. during unit tests when mocking the Lambda context
117+
if (extractedContext.getAwsRequestId() != null) {
118+
metricsInstance.addMetadata(REQUEST_ID_PROPERTY, extractedContext.getAwsRequestId());
119+
}
117120

118121
// Only capture cold start metrics if enabled on annotation
119122
if (metrics.captureColdStart()) {
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package software.amazon.lambda.powertools.metrics;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import java.io.ByteArrayOutputStream;
6+
import java.io.PrintStream;
7+
import java.util.HashMap;
8+
import java.util.Map;
9+
10+
import org.junit.jupiter.api.AfterEach;
11+
import org.junit.jupiter.api.BeforeEach;
12+
import org.junit.jupiter.api.Test;
13+
import org.junit.jupiter.api.extension.ExtendWith;
14+
import org.junitpioneer.jupiter.SetEnvironmentVariable;
15+
import org.mockito.Mock;
16+
import org.mockito.junit.jupiter.MockitoExtension;
17+
18+
import com.amazonaws.services.lambda.runtime.Context;
19+
import com.amazonaws.services.lambda.runtime.RequestHandler;
20+
import com.fasterxml.jackson.databind.JsonNode;
21+
import com.fasterxml.jackson.databind.ObjectMapper;
22+
23+
import software.amazon.lambda.powertools.metrics.model.MetricUnit;
24+
25+
@ExtendWith(MockitoExtension.class)
26+
class RequestHandlerTest {
27+
28+
// For developer convenience, no exceptions should be thrown when using a plain Lambda Context mock
29+
@Mock
30+
Context lambdaContext;
31+
32+
private final PrintStream standardOut = System.out;
33+
private ByteArrayOutputStream outputStreamCaptor;
34+
private final ObjectMapper objectMapper = new ObjectMapper();
35+
36+
@BeforeEach
37+
void setUp() {
38+
outputStreamCaptor = new ByteArrayOutputStream();
39+
System.setOut(new PrintStream(outputStreamCaptor));
40+
}
41+
42+
@AfterEach
43+
void tearDown() throws Exception {
44+
System.setOut(standardOut);
45+
46+
// Reset the singleton state between tests
47+
java.lang.reflect.Field field = MetricsFactory.class.getDeclaredField("metrics");
48+
field.setAccessible(true);
49+
field.set(null, null);
50+
51+
field = MetricsFactory.class.getDeclaredField("provider");
52+
field.setAccessible(true);
53+
field.set(null, new software.amazon.lambda.powertools.metrics.provider.EmfMetricsProvider());
54+
}
55+
56+
@Test
57+
void shouldCaptureMetricsFromAnnotatedHandler() throws Exception {
58+
// Given
59+
RequestHandler<Map<String, Object>, String> handler = new HandlerWithMetricsAnnotation();
60+
Map<String, Object> input = new HashMap<>();
61+
62+
// When
63+
handler.handleRequest(input, lambdaContext);
64+
65+
// Then
66+
String emfOutput = outputStreamCaptor.toString().trim();
67+
JsonNode rootNode = objectMapper.readTree(emfOutput);
68+
69+
assertThat(rootNode.has("test-metric")).isTrue();
70+
assertThat(rootNode.get("test-metric").asDouble()).isEqualTo(100.0);
71+
assertThat(rootNode.get("_aws").get("CloudWatchMetrics").get(0).get("Namespace").asText())
72+
.isEqualTo("CustomNamespace");
73+
assertThat(rootNode.has("Service")).isTrue();
74+
assertThat(rootNode.get("Service").asText()).isEqualTo("CustomService");
75+
}
76+
77+
@SetEnvironmentVariable(key = "POWERTOOLS_METRICS_DISABLED", value = "true")
78+
@Test
79+
void shouldNotCaptureMetricsWhenDisabled() {
80+
// Given
81+
RequestHandler<Map<String, Object>, String> handler = new HandlerWithMetricsAnnotation();
82+
Map<String, Object> input = new HashMap<>();
83+
84+
// When
85+
handler.handleRequest(input, lambdaContext);
86+
87+
// Then
88+
String emfOutput = outputStreamCaptor.toString().trim();
89+
assertThat(emfOutput).isEmpty();
90+
}
91+
92+
static class HandlerWithMetricsAnnotation implements RequestHandler<Map<String, Object>, String> {
93+
@Override
94+
@FlushMetrics(namespace = "CustomNamespace", service = "CustomService")
95+
public String handleRequest(Map<String, Object> input, Context context) {
96+
Metrics metrics = MetricsFactory.getMetricsInstance();
97+
metrics.addMetric("test-metric", 100, MetricUnit.COUNT);
98+
return "OK";
99+
}
100+
}
101+
}

0 commit comments

Comments
 (0)