Skip to content

Commit 7cf03ba

Browse files
pankajagrawal16Pankaj Agrawal
and
Pankaj Agrawal
authored
feat: Default dimensions from powertools instead of emf library (#317)
* feat: disable default emf lib dimensions and allow to override default dimensions * docs: clarify docs about default dimensions * feat: remove minimum 1 dimension validation since EMF supports no dimension as well * docs: improve installation steps for sqs large message * docs: clarify java11 aspect plugin issue for each module Co-authored-by: Pankaj Agrawal <[email protected]>
1 parent 771a5ee commit 7cf03ba

File tree

11 files changed

+272
-60
lines changed

11 files changed

+272
-60
lines changed

docs/core/metrics.md

+29-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ if no metrics are provided no exception will be raised. If metrics are provided,
108108
not met, `ValidationException` exception will be raised.
109109

110110
!!! tip "Metric validation"
111-
* Minimum of 1 dimension
112111
* Maximum of 9 dimensions
113112

114113
If you want to ensure that at least one metric is emitted, you can pass `raiseOnEmptyMetrics = true` to the **@Metrics** annotation:
@@ -183,6 +182,35 @@ You can use `putMetadata` for advanced use cases, where you want to metadata as
183182

184183
This will be available in CloudWatch Logs to ease operations on high cardinal data.
185184

185+
## Overriding default dimension set
186+
187+
By default, all metrics emitted via module captures `Service` as one of the default dimension. This is either specified via
188+
`POWERTOOLS_SERVICE_NAME` environment variable or via `service` attribute on `Metrics` annotation. If you wish to override the default
189+
Dimension, it can be done via `#!java MetricsUtils.defaultDimensionSet()`.
190+
191+
=== "App.java"
192+
193+
```java hl_lines="8 9 10"
194+
import software.amazon.lambda.powertools.metrics.Metrics;
195+
import static software.amazon.lambda.powertools.metrics.MetricsUtils;
196+
197+
public class App implements RequestHandler<Object, Object> {
198+
199+
MetricsLogger metricsLogger = MetricsUtils.metricsLogger();
200+
201+
static {
202+
MetricsUtils.defaultDimensionSet(DimensionSet.of("CustomDimension", "booking"));
203+
}
204+
205+
@Override
206+
@Metrics(namespace = "ExampleApplication", service = "booking")
207+
public Object handleRequest(Object input, Context context) {
208+
...
209+
MetricsUtils.withSingleMetric("Metric2", 1, Unit.COUNT, log -> {});
210+
}
211+
}
212+
```
213+
186214
## Creating a metric with a different dimension
187215

188216
CloudWatch EMF uses the same dimensions across all your metrics. Use `withSingleMetric` if you have a metric that should have different dimensions.

docs/index.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ You can use [SAM](https://aws.amazon.com/serverless/sam/) to quickly setup a ser
3636

3737
For more information about the project and available options refer to this [repository](https://github.com/aws-samples/cookiecutter-aws-sam-powertools-java/blob/main/README.md)
3838

39+
!!! note "Using Java 9 or later?"
40+
If you are working with lambda function on runtime **Java 9 or later**, please refer **[issue](https://github.com/awslabs/aws-lambda-powertools-java/issues/50)** for a workaround.
41+
3942
=== "Maven"
4043

4144
```xml hl_lines="3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55"
@@ -98,7 +101,6 @@ For more information about the project and available options refer to this [repo
98101
</plugins>
99102
</build>
100103
```
101-
**Note:** If you are working with lambda function on runtime post java8, please refer [issue](https://github.com/awslabs/aws-lambda-powertools-java/issues/50) for workaround.
102104

103105
=== "Gradle"
104106

docs/utilities/batch.md

+3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ are returned to the queue.
2525

2626
To install this utility, add the following dependency to your project.
2727

28+
!!! note "Using Java 9 or later?"
29+
If you are working with lambda function on runtime **Java 9 or later**, please refer **[issue](https://github.com/awslabs/aws-lambda-powertools-java/issues/50)** for a workaround.
30+
2831
=== "Maven"
2932
```xml hl_lines="3 4 5 6 7 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36"
3033
<dependencies>

docs/utilities/sqs_large_message_handling.md

+14-16
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,21 @@ This utility is compatible with versions *[1.1.0+](https://github.com/awslabs/am
3333

3434
To install this utility, add the following dependency to your project.
3535

36-
=== "Maven"
37-
```xml
38-
<dependency>
39-
<groupId>software.amazon.lambda</groupId>
40-
<artifactId>powertools-sqs</artifactId>
41-
<version>1.3.0</version>
42-
</dependency>
43-
```
36+
!!! note "Using Java 9 or later?"
37+
If you are working with lambda function on runtime **Java 9 or later**, please refer **[issue](https://github.com/awslabs/aws-lambda-powertools-java/issues/50)** for a workaround.
4438

45-
=== "Maven Configuration"
46-
47-
Configure the aspectj-maven-plugin to compile-time weave (CTW) the
48-
aws-lambda-powertools-java aspects into your project. You may already have this
49-
plugin in your pom. In that case add the dependency to the `aspectLibraries`
50-
section.
51-
52-
```xml hl_lines="13 14 15 16"
39+
=== "Maven"
40+
```xml hl_lines="3 4 5 6 7 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36"
41+
<dependencies>
42+
...
43+
<dependency>
44+
<groupId>software.amazon.lambda</groupId>
45+
<artifactId>powertools-sqs</artifactId>
46+
<version>1.3.0</version>
47+
</dependency>
48+
...
49+
</dependencies>
50+
<!-- configure the aspectj-maven-plugin to compile-time weave (CTW) the aws-lambda-powertools-java aspects into your project -->
5351
<build>
5452
<plugins>
5553
...

docs/utilities/validation.md

+9-11
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,21 @@ This utility provides JSON Schema validation for payloads held within events and
1515

1616
To install this utility, add the following dependency to your project.
1717

18+
!!! note "Using Java 9 or later?"
19+
If you are working with lambda function on runtime **Java 9 or later**, please refer **[issue](https://github.com/awslabs/aws-lambda-powertools-java/issues/50)** for a workaround.
20+
1821
=== "Maven"
19-
```xml
22+
```xml hl_lines="3 4 5 6 7 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36"
23+
<dependencies>
24+
...
2025
<dependency>
2126
<groupId>com.amazonaws</groupId>
2227
<artifactId>powertools-validation</artifactId>
2328
<version>1.3.0</version>
2429
</dependency>
25-
```
26-
27-
=== "Maven Configuration"
28-
29-
Configure the aspectj-maven-plugin to compile-time weave (CTW) the
30-
aws-lambda-powertools-java aspects into your project. You may already have this
31-
plugin in your pom. In that case add the dependency to the `aspectLibraries`
32-
section.
33-
34-
```xml hl_lines="13 14 15 16"
30+
...
31+
</dependencies>
32+
<!-- configure the aspectj-maven-plugin to compile-time weave (CTW) the aws-lambda-powertools-java aspects into your project -->
3533
<build>
3634
<plugins>
3735
...

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

+19-9
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

@@ -92,8 +95,8 @@ private void validateBeforeFlushingMetrics(Metrics metrics) {
9295
throw new ValidationException("No metrics captured, at least one metrics must be emitted");
9396
}
9497

95-
if (dimensionsCount() == 0 || dimensionsCount() > 9) {
96-
throw new ValidationException(String.format("Number of Dimensions must be in range of 1-9." +
98+
if (dimensionsCount() > 9) {
99+
throw new ValidationException(String.format("Number of Dimensions must be in range of 0-9." +
97100
" Actual size: %d.", dimensionsCount()));
98101
}
99102
}
@@ -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);

0 commit comments

Comments
 (0)