Skip to content

Commit f691dcb

Browse files
author
Pankaj Agrawal
committed
feat: disable default emf lib dimensions and allow to override default dimensions
1 parent 771a5ee commit f691dcb

File tree

5 files changed

+197
-22
lines changed

5 files changed

+197
-22
lines changed

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

+41-6
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44
import java.util.function.Consumer;
55

66
import software.amazon.cloudwatchlogs.emf.config.SystemWrapper;
7+
import software.amazon.cloudwatchlogs.emf.environment.EnvironmentProvider;
78
import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger;
9+
import software.amazon.cloudwatchlogs.emf.model.DimensionSet;
810
import software.amazon.cloudwatchlogs.emf.model.MetricsContext;
911
import software.amazon.cloudwatchlogs.emf.model.MetricsLoggerHelper;
1012
import software.amazon.cloudwatchlogs.emf.model.Unit;
1113

14+
import static java.util.Objects.requireNonNull;
1215
import static java.util.Optional.ofNullable;
1316
import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.getXrayTraceId;
1417
import static software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAspect.REQUEST_ID_PROPERTY;
@@ -22,6 +25,7 @@
2225
*/
2326
public final class MetricsUtils {
2427
private static final MetricsLogger metricsLogger = new MetricsLogger();
28+
private static DimensionSet defaultDimensionSet;
2529

2630
private MetricsUtils() {
2731
}
@@ -35,11 +39,22 @@ public static MetricsLogger metricsLogger() {
3539
return metricsLogger;
3640
}
3741

42+
/**
43+
* Configure default dimension to be used by logger.
44+
* By default, @{@link Metrics} annotation captures configured service as a dimension <i>Service</i>
45+
* @param dimensionSet Default value of dimension set for logger
46+
*/
47+
public static void defaultDimensionSet(final DimensionSet dimensionSet) {
48+
requireNonNull(dimensionSet, "Null dimension set not allowed");
49+
MetricsUtils.defaultDimensionSet = dimensionSet;
50+
}
51+
52+
3853
/**
3954
* Add and immediately flush a single metric. It will use the default namespace
4055
* 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.
56+
* It by default captures function_request_id as property if used together with {@link Metrics} annotation. It will also
57+
* capture xray_trace_id as property if tracing is enabled.
4358
*
4459
* @param name the name of the metric
4560
* @param value the value of the metric
@@ -50,7 +65,8 @@ public static void withSingleMetric(final String name,
5065
final double value,
5166
final Unit unit,
5267
final Consumer<MetricsLogger> logger) {
53-
MetricsLogger metricsLogger = new MetricsLogger();
68+
MetricsLogger metricsLogger = logger();
69+
5470
try {
5571
metricsLogger.setNamespace(defaultNameSpace());
5672
metricsLogger.putMetric(name, value, unit);
@@ -63,8 +79,8 @@ public static void withSingleMetric(final String name,
6379

6480
/**
6581
* 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.
82+
* It by default captures function_request_id as property if used together with {@link Metrics} annotation. It will also
83+
* capture xray_trace_id as property if tracing is enabled.
6884
*
6985
* @param name the name of the metric
7086
* @param value the value of the metric
@@ -77,7 +93,8 @@ public static void withSingleMetric(final String name,
7793
final Unit unit,
7894
final String namespace,
7995
final Consumer<MetricsLogger> logger) {
80-
MetricsLogger metricsLogger = new MetricsLogger();
96+
MetricsLogger metricsLogger = logger();
97+
8198
try {
8299
metricsLogger.setNamespace(namespace);
83100
metricsLogger.putMetric(name, value, unit);
@@ -88,6 +105,14 @@ public static void withSingleMetric(final String name,
88105
}
89106
}
90107

108+
public static DimensionSet defaultDimensionSet() {
109+
return defaultDimensionSet;
110+
}
111+
112+
public static boolean hasDefaultDimension() {
113+
return defaultDimensionSet.getDimensionKeys().size() > 0;
114+
}
115+
91116
private static void captureRequestAndTraceId(MetricsLogger metricsLogger) {
92117
awsRequestId().
93118
ifPresent(requestId -> metricsLogger.putProperty(REQUEST_ID_PROPERTY, requestId));
@@ -107,4 +132,14 @@ private static Optional<String> awsRequestId() {
107132
return ofNullable(context.getProperty(REQUEST_ID_PROPERTY))
108133
.map(Object::toString);
109134
}
135+
136+
private static MetricsLogger logger() {
137+
MetricsContext metricsContext = new MetricsContext();
138+
139+
if (hasDefaultDimension()) {
140+
metricsContext.setDefaultDimensions(defaultDimensionSet());
141+
}
142+
143+
return new MetricsLogger(new EnvironmentProvider(), metricsContext);
144+
}
110145
}

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

+17-7
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.placedOnRequestHandler;
2424
import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.placedOnStreamHandler;
2525
import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.serviceName;
26+
import static software.amazon.lambda.powertools.metrics.MetricsUtils.defaultDimensionSet;
27+
import static software.amazon.lambda.powertools.metrics.MetricsUtils.hasDefaultDimension;
2628
import static software.amazon.lambda.powertools.metrics.MetricsUtils.metricsLogger;
2729

2830
@Aspect
@@ -47,8 +49,9 @@ public Object around(ProceedingJoinPoint pjp,
4749

4850
MetricsLogger logger = metricsLogger();
4951

50-
logger.setNamespace(namespace(metrics))
51-
.putDimensions(DimensionSet.of("Service", service(metrics)));
52+
refreshMetricsContext(metrics);
53+
54+
logger.setNamespace(namespace(metrics));
5255

5356
extractContext(pjp).ifPresent((context) -> {
5457
coldStartSingleMetricIfApplicable(context.getAwsRequestId(), context.getFunctionName(), metrics);
@@ -65,7 +68,7 @@ public Object around(ProceedingJoinPoint pjp,
6568
coldStartDone();
6669
validateMetricsAndRefreshOnFailure(metrics);
6770
logger.flush();
68-
refreshMetricsContext();
71+
refreshMetricsContext(metrics);
6972
}
7073
}
7174

@@ -102,25 +105,32 @@ private String namespace(Metrics metrics) {
102105
return !"".equals(metrics.namespace()) ? metrics.namespace() : NAMESPACE;
103106
}
104107

105-
private String service(Metrics metrics) {
108+
private static String service(Metrics metrics) {
106109
return !"".equals(metrics.service()) ? metrics.service() : serviceName();
107110
}
108111

109112
private void validateMetricsAndRefreshOnFailure(Metrics metrics) {
110113
try {
111114
validateBeforeFlushingMetrics(metrics);
112115
} catch (ValidationException e){
113-
refreshMetricsContext();
116+
refreshMetricsContext(metrics);
114117
throw e;
115118
}
116119
}
117120

118121
// This can be simplified after this issues https://github.com/awslabs/aws-embedded-metrics-java/issues/35 is fixed
119-
private static void refreshMetricsContext() {
122+
public static void refreshMetricsContext(Metrics metrics) {
120123
try {
121124
Field f = metricsLogger().getClass().getDeclaredField("context");
122125
f.setAccessible(true);
123-
f.set(metricsLogger(), new MetricsContext());
126+
MetricsContext context = new MetricsContext();
127+
128+
DimensionSet defaultDimensionSet = hasDefaultDimension() ? defaultDimensionSet()
129+
: DimensionSet.of("Service", service(metrics));
130+
131+
context.setDefaultDimensions(defaultDimensionSet);
132+
133+
f.set(metricsLogger(), context);
124134
} catch (NoSuchFieldException | IllegalAccessException e) {
125135
throw new RuntimeException(e);
126136
}

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

+34
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import com.fasterxml.jackson.core.JsonProcessingException;
88
import com.fasterxml.jackson.databind.ObjectMapper;
9+
import org.assertj.core.api.Assertions;
910
import org.junit.jupiter.api.AfterEach;
1011
import org.junit.jupiter.api.BeforeAll;
1112
import org.junit.jupiter.api.BeforeEach;
@@ -16,6 +17,7 @@
1617
import software.amazon.cloudwatchlogs.emf.model.Unit;
1718

1819
import static java.util.Collections.*;
20+
import static org.assertj.core.api.Assertions.*;
1921
import static org.assertj.core.api.Assertions.assertThat;
2022
import static org.mockito.Mockito.mockStatic;
2123
import static software.amazon.lambda.powertools.core.internal.SystemWrapper.getenv;
@@ -43,6 +45,31 @@ static void beforeAll() {
4345
}
4446
}
4547

48+
@Test
49+
void singleMetricsCaptureUtilityWithDefaultDimension() {
50+
try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class);
51+
MockedStatic<software.amazon.lambda.powertools.core.internal.SystemWrapper> internalWrapper = mockStatic(software.amazon.lambda.powertools.core.internal.SystemWrapper.class)) {
52+
mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda");
53+
internalWrapper.when(() -> getenv("_X_AMZN_TRACE_ID")).thenReturn("Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1\"");
54+
55+
MetricsUtils.defaultDimensionSet(DimensionSet.of("Service", "Booking"));
56+
57+
MetricsUtils.withSingleMetric("Metric1", 1, Unit.COUNT, "test",
58+
metricsLogger -> {});
59+
60+
assertThat(out.toString())
61+
.satisfies(s -> {
62+
Map<String, Object> logAsJson = readAsJson(s);
63+
64+
assertThat(logAsJson)
65+
.containsEntry("Metric1", 1.0)
66+
.containsEntry("Service", "Booking")
67+
.containsKey("_aws")
68+
.containsEntry("xray_trace_id", "1-5759e988-bd862e3fe1be46a994272793");
69+
});
70+
}
71+
}
72+
4673
@Test
4774
void singleMetricsCaptureUtility() {
4875
try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class);
@@ -96,6 +123,13 @@ void singleMetricsCaptureUtilityWithDefaultNameSpace() {
96123
}
97124
}
98125

126+
@Test
127+
void shouldThrowExceptionWhenDefaultDimensionIsNull() {
128+
assertThatNullPointerException()
129+
.isThrownBy(() -> MetricsUtils.defaultDimensionSet(null))
130+
.withMessage("Null dimension set not allowed");
131+
}
132+
99133
private Map<String, Object> readAsJson(String s) {
100134
try {
101135
return mapper.readValue(s, Map.class);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package software.amazon.lambda.powertools.metrics.handlers;
2+
3+
import com.amazonaws.services.lambda.runtime.Context;
4+
import com.amazonaws.services.lambda.runtime.RequestHandler;
5+
import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger;
6+
import software.amazon.cloudwatchlogs.emf.model.DimensionSet;
7+
import software.amazon.cloudwatchlogs.emf.model.Unit;
8+
import software.amazon.lambda.powertools.metrics.Metrics;
9+
10+
import static software.amazon.lambda.powertools.metrics.MetricsUtils.defaultDimensionSet;
11+
import static software.amazon.lambda.powertools.metrics.MetricsUtils.metricsLogger;
12+
import static software.amazon.lambda.powertools.metrics.MetricsUtils.withSingleMetric;
13+
14+
public class PowertoolsMetricsEnabledDefaultDimensionHandler implements RequestHandler<Object, Object> {
15+
16+
static {
17+
defaultDimensionSet(DimensionSet.of("CustomDimension", "booking"));
18+
}
19+
20+
@Override
21+
@Metrics(namespace = "ExampleApplication", service = "booking")
22+
public Object handleRequest(Object input, Context context) {
23+
MetricsLogger metricsLogger = metricsLogger();
24+
metricsLogger.putMetric("Metric1", 1, Unit.BYTES);
25+
26+
withSingleMetric("Metric2", 1, Unit.COUNT, log -> {});
27+
28+
return null;
29+
}
30+
}

0 commit comments

Comments
 (0)