Skip to content

Commit a1cfe72

Browse files
pankajagrawal16Pankaj Agrawal
and
Pankaj Agrawal
authored
feat: single metric utility method to pick default namespace (#305)
* feat: single metric utility method to pick default namespace * chore: move trace id logic to core for reuse * feat: capture xray trace id and request id by default as property for emf logs Co-authored-by: Pankaj Agrawal <[email protected]>
1 parent 67e8b78 commit a1cfe72

File tree

10 files changed

+147
-21
lines changed

10 files changed

+147
-21
lines changed

powertools-core/src/main/java/software/amazon/lambda/powertools/core/internal/LambdaHandlerProcessor.java

+9
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import static java.util.Optional.empty;
2626
import static java.util.Optional.of;
27+
import static software.amazon.lambda.powertools.core.internal.SystemWrapper.getenv;
2728

2829
public final class LambdaHandlerProcessor {
2930
// SERVICE_NAME cannot be final for testing purposes
@@ -85,4 +86,12 @@ public static void coldStartDone() {
8586
public static boolean isSamLocal() {
8687
return "true".equals(System.getenv("AWS_SAM_LOCAL"));
8788
}
89+
90+
public static Optional<String> getXrayTraceId() {
91+
final String X_AMZN_TRACE_ID = getenv("_X_AMZN_TRACE_ID");
92+
if(X_AMZN_TRACE_ID != null) {
93+
return of(X_AMZN_TRACE_ID.split(";")[0].replace("Root=", ""));
94+
}
95+
return empty();
96+
}
8897
}
+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
package software.amazon.lambda.powertools.logging.internal;
1+
package software.amazon.lambda.powertools.core.internal;
22

3-
class SystemWrapper {
3+
public class SystemWrapper {
44
private SystemWrapper() {
55
}
66

powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java

+1-10
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@
3838

3939
import static java.nio.charset.StandardCharsets.UTF_8;
4040
import static java.util.Optional.empty;
41-
import static java.util.Optional.of;
4241
import static java.util.Optional.ofNullable;
4342
import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.coldStartDone;
4443
import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.extractContext;
44+
import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.getXrayTraceId;
4545
import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.isColdStart;
4646
import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.isHandlerMethod;
4747
import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.placedOnRequestHandler;
@@ -50,7 +50,6 @@
5050
import static software.amazon.lambda.powertools.logging.LoggingUtils.appendKey;
5151
import static software.amazon.lambda.powertools.logging.LoggingUtils.appendKeys;
5252
import static software.amazon.lambda.powertools.logging.LoggingUtils.objectMapper;
53-
import static software.amazon.lambda.powertools.logging.internal.SystemWrapper.getenv;
5453

5554
@Aspect
5655
public final class LambdaLoggingAspect {
@@ -199,12 +198,4 @@ private Optional<String> asJson(final ProceedingJoinPoint pjp,
199198
private Logger logger(final ProceedingJoinPoint pjp) {
200199
return LogManager.getLogger(pjp.getSignature().getDeclaringType());
201200
}
202-
203-
private static Optional<String> getXrayTraceId() {
204-
final String X_AMZN_TRACE_ID = getenv("_X_AMZN_TRACE_ID");
205-
if(X_AMZN_TRACE_ID != null) {
206-
return of(X_AMZN_TRACE_ID.split(";")[0].replace("Root=", ""));
207-
}
208-
return empty();
209-
}
210201
}

powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.mockito.Mock;
4242
import org.mockito.MockedStatic;
4343
import software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor;
44+
import software.amazon.lambda.powertools.core.internal.SystemWrapper;
4445
import software.amazon.lambda.powertools.logging.handlers.PowerLogToolEnabled;
4546
import software.amazon.lambda.powertools.logging.handlers.PowerLogToolEnabledForStream;
4647
import software.amazon.lambda.powertools.logging.handlers.PowerToolDisabled;
@@ -66,7 +67,7 @@
6667
import static org.mockito.Mockito.when;
6768
import static org.mockito.MockitoAnnotations.openMocks;
6869
import static org.skyscreamer.jsonassert.JSONAssert.assertEquals;
69-
import static software.amazon.lambda.powertools.logging.internal.SystemWrapper.getenv;
70+
import static software.amazon.lambda.powertools.core.internal.SystemWrapper.getenv;
7071

7172
class LambdaLoggingAspectTest {
7273

powertools-metrics/src/main/java/software/amazon/cloudwatchlogs/emf/model/MetricsLoggerHelper.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public static long dimensionsCount() {
1616
return metricsContext().getDimensions().size();
1717
}
1818

19-
private static MetricsContext metricsContext() {
19+
public static MetricsContext metricsContext() {
2020
try {
2121
Field f = metricsLogger().getClass().getDeclaredField("context");
2222
f.setAccessible(true);

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

+58
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
package software.amazon.lambda.powertools.metrics;
22

3+
import java.util.Optional;
34
import java.util.function.Consumer;
45

6+
import software.amazon.cloudwatchlogs.emf.config.SystemWrapper;
57
import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger;
8+
import software.amazon.cloudwatchlogs.emf.model.MetricsContext;
9+
import software.amazon.cloudwatchlogs.emf.model.MetricsLoggerHelper;
610
import software.amazon.cloudwatchlogs.emf.model.Unit;
711

12+
import static java.util.Optional.ofNullable;
13+
import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.getXrayTraceId;
14+
import static software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAspect.REQUEST_ID_PROPERTY;
15+
import static software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAspect.TRACE_ID_PROPERTY;
16+
817
/**
918
* A class used to retrieve the instance of the {@code MetricsLogger} used by
1019
* {@code Metrics}.
@@ -26,8 +35,36 @@ public static MetricsLogger metricsLogger() {
2635
return metricsLogger;
2736
}
2837

38+
/**
39+
* Add and immediately flush a single metric. It will use the default namespace
40+
* specified either on {@link Metrics} annotation or via POWERTOOLS_METRICS_NAMESPACE env var.
41+
* It by default captures AwsRequestId as property if used together with {@link Metrics} annotation. It will also
42+
* capture XrayTraceId as property if tracing is enabled.
43+
*
44+
* @param name the name of the metric
45+
* @param value the value of the metric
46+
* @param unit the unit type of the metric
47+
* @param logger the MetricsLogger
48+
*/
49+
public static void withSingleMetric(final String name,
50+
final double value,
51+
final Unit unit,
52+
final Consumer<MetricsLogger> logger) {
53+
MetricsLogger metricsLogger = new MetricsLogger();
54+
try {
55+
metricsLogger.setNamespace(defaultNameSpace());
56+
metricsLogger.putMetric(name, value, unit);
57+
captureRequestAndTraceId(metricsLogger);
58+
logger.accept(metricsLogger);
59+
} finally {
60+
metricsLogger.flush();
61+
}
62+
}
63+
2964
/**
3065
* Add and immediately flush a single metric.
66+
* It by default captures AwsRequestId as property if used together with {@link Metrics} annotation. It will also
67+
* capture XrayTraceId as property if tracing is enabled.
3168
*
3269
* @param name the name of the metric
3370
* @param value the value of the metric
@@ -44,9 +81,30 @@ public static void withSingleMetric(final String name,
4481
try {
4582
metricsLogger.setNamespace(namespace);
4683
metricsLogger.putMetric(name, value, unit);
84+
captureRequestAndTraceId(metricsLogger);
4785
logger.accept(metricsLogger);
4886
} finally {
4987
metricsLogger.flush();
5088
}
5189
}
90+
91+
private static void captureRequestAndTraceId(MetricsLogger metricsLogger) {
92+
awsRequestId().
93+
ifPresent(requestId -> metricsLogger.putProperty(REQUEST_ID_PROPERTY, requestId));
94+
95+
getXrayTraceId()
96+
.ifPresent(traceId -> metricsLogger.putProperty(TRACE_ID_PROPERTY, traceId));
97+
}
98+
99+
private static String defaultNameSpace() {
100+
MetricsContext context = MetricsLoggerHelper.metricsContext();
101+
return "aws-embedded-metrics".equals(context.getNamespace()) ?
102+
SystemWrapper.getenv("POWERTOOLS_METRICS_NAMESPACE"): context.getNamespace();
103+
}
104+
105+
private static Optional<String> awsRequestId() {
106+
MetricsContext context = MetricsLoggerHelper.metricsContext();
107+
return ofNullable(context.getProperty(REQUEST_ID_PROPERTY))
108+
.map(Object::toString);
109+
}
52110
}

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

+8-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import software.amazon.cloudwatchlogs.emf.model.DimensionSet;
1111
import software.amazon.cloudwatchlogs.emf.model.MetricsContext;
1212
import software.amazon.cloudwatchlogs.emf.model.Unit;
13+
import software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor;
1314
import software.amazon.lambda.powertools.metrics.Metrics;
1415
import software.amazon.lambda.powertools.metrics.ValidationException;
1516

@@ -27,6 +28,8 @@
2728
@Aspect
2829
public class LambdaMetricsAspect {
2930
private static final String NAMESPACE = System.getenv("POWERTOOLS_METRICS_NAMESPACE");
31+
public static final String TRACE_ID_PROPERTY = "XrayTraceId";
32+
public static final String REQUEST_ID_PROPERTY = "AwsRequestId";
3033

3134
@SuppressWarnings({"EmptyMethod"})
3235
@Pointcut("@annotation(metrics)")
@@ -49,9 +52,12 @@ public Object around(ProceedingJoinPoint pjp,
4952

5053
extractContext(pjp).ifPresent((context) -> {
5154
coldStartSingleMetricIfApplicable(context.getAwsRequestId(), context.getFunctionName(), metrics);
52-
logger.putProperty("AwsRequestId", context.getAwsRequestId());
55+
logger.putProperty(REQUEST_ID_PROPERTY, context.getAwsRequestId());
5356
});
5457

58+
LambdaHandlerProcessor.getXrayTraceId()
59+
.ifPresent(traceId -> logger.putProperty(TRACE_ID_PROPERTY, traceId));
60+
5561
try {
5662
return pjp.proceed(proceedArgs);
5763

@@ -75,7 +81,7 @@ && isColdStart()) {
7581
metricsLogger.setNamespace(namespace(metrics));
7682
metricsLogger.putMetric("ColdStart", 1, Unit.COUNT);
7783
metricsLogger.setDimensions(DimensionSet.of("Service", service(metrics), "FunctionName", functionName));
78-
metricsLogger.putProperty("AwsRequestId", awsRequestId);
84+
metricsLogger.putProperty(REQUEST_ID_PROPERTY, awsRequestId);
7985
metricsLogger.flush();
8086
}
8187

powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/MetricsLoggerTest.java

+36-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import static java.util.Collections.*;
1919
import static org.assertj.core.api.Assertions.assertThat;
2020
import static org.mockito.Mockito.mockStatic;
21+
import static software.amazon.lambda.powertools.core.internal.SystemWrapper.getenv;
2122

2223
class MetricsLoggerTest {
2324

@@ -44,8 +45,10 @@ static void beforeAll() {
4445

4546
@Test
4647
void singleMetricsCaptureUtility() {
47-
try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class)) {
48+
try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class);
49+
MockedStatic<software.amazon.lambda.powertools.core.internal.SystemWrapper> internalWrapper = mockStatic(software.amazon.lambda.powertools.core.internal.SystemWrapper.class)) {
4850
mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda");
51+
internalWrapper.when(() -> getenv("_X_AMZN_TRACE_ID")).thenReturn("Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1\"");
4952

5053
MetricsUtils.withSingleMetric("Metric1", 1, Unit.COUNT, "test",
5154
metricsLogger -> metricsLogger.setDimensions(DimensionSet.of("Dimension1", "Value1")));
@@ -57,7 +60,38 @@ void singleMetricsCaptureUtility() {
5760
assertThat(logAsJson)
5861
.containsEntry("Metric1", 1.0)
5962
.containsEntry("Dimension1", "Value1")
60-
.containsKey("_aws");
63+
.containsKey("_aws")
64+
.containsEntry("XrayTraceId", "1-5759e988-bd862e3fe1be46a994272793");
65+
});
66+
}
67+
}
68+
69+
@Test
70+
void singleMetricsCaptureUtilityWithDefaultNameSpace() {
71+
try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class);
72+
MockedStatic<software.amazon.lambda.powertools.core.internal.SystemWrapper> internalWrapper = mockStatic(software.amazon.lambda.powertools.core.internal.SystemWrapper.class)) {
73+
mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda");
74+
mocked.when(() -> SystemWrapper.getenv("POWERTOOLS_METRICS_NAMESPACE")).thenReturn("GlobalName");
75+
internalWrapper.when(() -> getenv("_X_AMZN_TRACE_ID")).thenReturn("Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1\"");
76+
77+
MetricsUtils.withSingleMetric("Metric1", 1, Unit.COUNT,
78+
metricsLogger -> metricsLogger.setDimensions(DimensionSet.of("Dimension1", "Value1")));
79+
80+
assertThat(out.toString())
81+
.satisfies(s -> {
82+
Map<String, Object> logAsJson = readAsJson(s);
83+
84+
assertThat(logAsJson)
85+
.containsEntry("Metric1", 1.0)
86+
.containsEntry("Dimension1", "Value1")
87+
.containsKey("_aws")
88+
.containsEntry("XrayTraceId", "1-5759e988-bd862e3fe1be46a994272793");
89+
90+
Map<String, Object> aws = (Map<String, Object>) logAsJson.get("_aws");
91+
92+
assertThat(aws.get("CloudWatchMetrics"))
93+
.asString()
94+
.contains("Namespace=GlobalName");
6195
});
6296
}
6397
}

powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsEnabledHandler.java

+6
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
import com.amazonaws.services.lambda.runtime.Context;
44
import com.amazonaws.services.lambda.runtime.RequestHandler;
55
import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger;
6+
import software.amazon.cloudwatchlogs.emf.model.DimensionSet;
67
import software.amazon.cloudwatchlogs.emf.model.Unit;
78
import software.amazon.lambda.powertools.metrics.Metrics;
89

910
import static software.amazon.lambda.powertools.metrics.MetricsUtils.metricsLogger;
11+
import static software.amazon.lambda.powertools.metrics.MetricsUtils.withSingleMetric;
1012

1113
public class PowertoolsMetricsEnabledHandler implements RequestHandler<Object, Object> {
1214

@@ -16,6 +18,10 @@ public Object handleRequest(Object input, Context context) {
1618
MetricsLogger metricsLogger = metricsLogger();
1719
metricsLogger.putMetric("Metric1", 1, Unit.BYTES);
1820

21+
22+
withSingleMetric("Metric2", 1, Unit.COUNT,
23+
log -> log.setDimensions(DimensionSet.of("Dimension1", "Value1")));
24+
1925
return null;
2026
}
2127
}

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

+24-3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import static org.mockito.Mockito.mockStatic;
3737
import static org.mockito.Mockito.when;
3838
import static org.mockito.MockitoAnnotations.openMocks;
39+
import static software.amazon.lambda.powertools.core.internal.SystemWrapper.getenv;
3940

4041
public class LambdaMetricsAspectTest {
4142
@Mock
@@ -71,13 +72,33 @@ void tearDown() {
7172

7273
@Test
7374
public void metricsWithoutColdStart() {
74-
try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class)) {
75+
try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class);
76+
MockedStatic<software.amazon.lambda.powertools.core.internal.SystemWrapper> internalWrapper = mockStatic(software.amazon.lambda.powertools.core.internal.SystemWrapper.class)) {
77+
7578
mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda");
79+
internalWrapper.when(() -> getenv("_X_AMZN_TRACE_ID")).thenReturn("Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1\"");
80+
7681
requestHandler.handleRequest("input", context);
7782

78-
assertThat(out.toString())
83+
assertThat(out.toString().split("\n"))
84+
.hasSize(2)
7985
.satisfies(s -> {
80-
Map<String, Object> logAsJson = readAsJson(s);
86+
Map<String, Object> logAsJson = readAsJson(s[0]);
87+
88+
assertThat(logAsJson)
89+
.containsEntry("Metric2", 1.0)
90+
.containsEntry("Dimension1", "Value1")
91+
.containsKey("_aws")
92+
.containsEntry("XrayTraceId", "1-5759e988-bd862e3fe1be46a994272793")
93+
.containsEntry("AwsRequestId", "123ABC");
94+
95+
Map<String, Object> aws = (Map<String, Object>) logAsJson.get("_aws");
96+
97+
assertThat(aws.get("CloudWatchMetrics"))
98+
.asString()
99+
.contains("Namespace=ExampleApplication");
100+
101+
logAsJson = readAsJson(s[1]);
81102

82103
assertThat(logAsJson)
83104
.containsEntry("Metric1", 1.0)

0 commit comments

Comments
 (0)