diff --git a/docs/core/metrics.md b/docs/core/metrics.md index cb470caa5..0839be050 100644 --- a/docs/core/metrics.md +++ b/docs/core/metrics.md @@ -108,7 +108,6 @@ if no metrics are provided no exception will be raised. If metrics are provided, not met, `ValidationException` exception will be raised. !!! tip "Metric validation" - * Minimum of 1 dimension * Maximum of 9 dimensions 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 This will be available in CloudWatch Logs to ease operations on high cardinal data. +## Overriding default dimension set + +By default, all metrics emitted via module captures `Service` as one of the default dimension. This is either specified via +`POWERTOOLS_SERVICE_NAME` environment variable or via `service` attribute on `Metrics` annotation. If you wish to override the default +Dimension, it can be done via `#!java MetricsUtils.defaultDimensionSet()`. + +=== "App.java" + + ```java hl_lines="8 9 10" + import software.amazon.lambda.powertools.metrics.Metrics; + import static software.amazon.lambda.powertools.metrics.MetricsUtils; + + public class App implements RequestHandler { + + MetricsLogger metricsLogger = MetricsUtils.metricsLogger(); + + static { + MetricsUtils.defaultDimensionSet(DimensionSet.of("CustomDimension", "booking")); + } + + @Override + @Metrics(namespace = "ExampleApplication", service = "booking") + public Object handleRequest(Object input, Context context) { + ... + MetricsUtils.withSingleMetric("Metric2", 1, Unit.COUNT, log -> {}); + } + } + ``` + ## Creating a metric with a different dimension CloudWatch EMF uses the same dimensions across all your metrics. Use `withSingleMetric` if you have a metric that should have different dimensions. diff --git a/docs/index.md b/docs/index.md index 5b21a2acf..279aba39b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -36,6 +36,9 @@ You can use [SAM](https://aws.amazon.com/serverless/sam/) to quickly setup a ser 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) +!!! note "Using Java 9 or later?" + 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. + === "Maven" ```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 ``` - **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. === "Gradle" diff --git a/docs/utilities/batch.md b/docs/utilities/batch.md index 8fa9cd95e..cb74acc20 100644 --- a/docs/utilities/batch.md +++ b/docs/utilities/batch.md @@ -25,6 +25,9 @@ are returned to the queue. To install this utility, add the following dependency to your project. +!!! note "Using Java 9 or later?" + 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. + === "Maven" ```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" diff --git a/docs/utilities/sqs_large_message_handling.md b/docs/utilities/sqs_large_message_handling.md index 9804196e5..a3e948cf1 100644 --- a/docs/utilities/sqs_large_message_handling.md +++ b/docs/utilities/sqs_large_message_handling.md @@ -33,23 +33,21 @@ This utility is compatible with versions *[1.1.0+](https://github.com/awslabs/am To install this utility, add the following dependency to your project. -=== "Maven" - ```xml - - software.amazon.lambda - powertools-sqs - 1.3.0 - - ``` +!!! note "Using Java 9 or later?" +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. -=== "Maven Configuration" - - Configure the aspectj-maven-plugin to compile-time weave (CTW) the - aws-lambda-powertools-java aspects into your project. You may already have this - plugin in your pom. In that case add the dependency to the `aspectLibraries` - section. - - ```xml hl_lines="13 14 15 16" +=== "Maven" + ```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" + + ... + + software.amazon.lambda + powertools-sqs + 1.3.0 + + ... + + ... diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index cfbdabd6a..436e2172f 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -15,23 +15,21 @@ This utility provides JSON Schema validation for payloads held within events and To install this utility, add the following dependency to your project. +!!! note "Using Java 9 or later?" + 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. + === "Maven" - ```xml + ```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" + + ... com.amazonaws powertools-validation 1.3.0 - ``` - -=== "Maven Configuration" - - Configure the aspectj-maven-plugin to compile-time weave (CTW) the - aws-lambda-powertools-java aspects into your project. You may already have this - plugin in your pom. In that case add the dependency to the `aspectLibraries` - section. - - ```xml hl_lines="13 14 15 16" + ... + + ... diff --git a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/MetricsUtils.java b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/MetricsUtils.java index 314b53d24..89c2de6aa 100644 --- a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/MetricsUtils.java +++ b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/MetricsUtils.java @@ -4,11 +4,14 @@ import java.util.function.Consumer; import software.amazon.cloudwatchlogs.emf.config.SystemWrapper; +import software.amazon.cloudwatchlogs.emf.environment.EnvironmentProvider; import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; +import software.amazon.cloudwatchlogs.emf.model.DimensionSet; import software.amazon.cloudwatchlogs.emf.model.MetricsContext; import software.amazon.cloudwatchlogs.emf.model.MetricsLoggerHelper; import software.amazon.cloudwatchlogs.emf.model.Unit; +import static java.util.Objects.requireNonNull; import static java.util.Optional.ofNullable; import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.getXrayTraceId; import static software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAspect.REQUEST_ID_PROPERTY; @@ -22,6 +25,7 @@ */ public final class MetricsUtils { private static final MetricsLogger metricsLogger = new MetricsLogger(); + private static DimensionSet defaultDimensionSet; private MetricsUtils() { } @@ -35,11 +39,22 @@ public static MetricsLogger metricsLogger() { return metricsLogger; } + /** + * Configure default dimension to be used by logger. + * By default, @{@link Metrics} annotation captures configured service as a dimension Service + * @param dimensionSet Default value of dimension set for logger + */ + public static void defaultDimensionSet(final DimensionSet dimensionSet) { + requireNonNull(dimensionSet, "Null dimension set not allowed"); + MetricsUtils.defaultDimensionSet = dimensionSet; + } + + /** * Add and immediately flush a single metric. It will use the default namespace * specified either on {@link Metrics} annotation or via POWERTOOLS_METRICS_NAMESPACE env var. - * It by default captures AwsRequestId as property if used together with {@link Metrics} annotation. It will also - * capture XrayTraceId as property if tracing is enabled. + * It by default captures function_request_id as property if used together with {@link Metrics} annotation. It will also + * capture xray_trace_id as property if tracing is enabled. * * @param name the name of the metric * @param value the value of the metric @@ -50,7 +65,8 @@ public static void withSingleMetric(final String name, final double value, final Unit unit, final Consumer logger) { - MetricsLogger metricsLogger = new MetricsLogger(); + MetricsLogger metricsLogger = logger(); + try { metricsLogger.setNamespace(defaultNameSpace()); metricsLogger.putMetric(name, value, unit); @@ -63,8 +79,8 @@ public static void withSingleMetric(final String name, /** * Add and immediately flush a single metric. - * It by default captures AwsRequestId as property if used together with {@link Metrics} annotation. It will also - * capture XrayTraceId as property if tracing is enabled. + * It by default captures function_request_id as property if used together with {@link Metrics} annotation. It will also + * capture xray_trace_id as property if tracing is enabled. * * @param name the name of the metric * @param value the value of the metric @@ -77,7 +93,8 @@ public static void withSingleMetric(final String name, final Unit unit, final String namespace, final Consumer logger) { - MetricsLogger metricsLogger = new MetricsLogger(); + MetricsLogger metricsLogger = logger(); + try { metricsLogger.setNamespace(namespace); metricsLogger.putMetric(name, value, unit); @@ -88,6 +105,14 @@ public static void withSingleMetric(final String name, } } + public static DimensionSet defaultDimensionSet() { + return defaultDimensionSet; + } + + public static boolean hasDefaultDimension() { + return defaultDimensionSet.getDimensionKeys().size() > 0; + } + private static void captureRequestAndTraceId(MetricsLogger metricsLogger) { awsRequestId(). ifPresent(requestId -> metricsLogger.putProperty(REQUEST_ID_PROPERTY, requestId)); @@ -107,4 +132,14 @@ private static Optional awsRequestId() { return ofNullable(context.getProperty(REQUEST_ID_PROPERTY)) .map(Object::toString); } + + private static MetricsLogger logger() { + MetricsContext metricsContext = new MetricsContext(); + + if (hasDefaultDimension()) { + metricsContext.setDefaultDimensions(defaultDimensionSet()); + } + + return new MetricsLogger(new EnvironmentProvider(), metricsContext); + } } diff --git a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/LambdaMetricsAspect.java b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/LambdaMetricsAspect.java index 87de57b1e..2adff4b8a 100644 --- a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/LambdaMetricsAspect.java +++ b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/internal/LambdaMetricsAspect.java @@ -23,6 +23,8 @@ import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.placedOnRequestHandler; import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.placedOnStreamHandler; import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.serviceName; +import static software.amazon.lambda.powertools.metrics.MetricsUtils.defaultDimensionSet; +import static software.amazon.lambda.powertools.metrics.MetricsUtils.hasDefaultDimension; import static software.amazon.lambda.powertools.metrics.MetricsUtils.metricsLogger; @Aspect @@ -47,8 +49,9 @@ public Object around(ProceedingJoinPoint pjp, MetricsLogger logger = metricsLogger(); - logger.setNamespace(namespace(metrics)) - .putDimensions(DimensionSet.of("Service", service(metrics))); + refreshMetricsContext(metrics); + + logger.setNamespace(namespace(metrics)); extractContext(pjp).ifPresent((context) -> { coldStartSingleMetricIfApplicable(context.getAwsRequestId(), context.getFunctionName(), metrics); @@ -65,7 +68,7 @@ public Object around(ProceedingJoinPoint pjp, coldStartDone(); validateMetricsAndRefreshOnFailure(metrics); logger.flush(); - refreshMetricsContext(); + refreshMetricsContext(metrics); } } @@ -92,8 +95,8 @@ private void validateBeforeFlushingMetrics(Metrics metrics) { throw new ValidationException("No metrics captured, at least one metrics must be emitted"); } - if (dimensionsCount() == 0 || dimensionsCount() > 9) { - throw new ValidationException(String.format("Number of Dimensions must be in range of 1-9." + + if (dimensionsCount() > 9) { + throw new ValidationException(String.format("Number of Dimensions must be in range of 0-9." + " Actual size: %d.", dimensionsCount())); } } @@ -102,7 +105,7 @@ private String namespace(Metrics metrics) { return !"".equals(metrics.namespace()) ? metrics.namespace() : NAMESPACE; } - private String service(Metrics metrics) { + private static String service(Metrics metrics) { return !"".equals(metrics.service()) ? metrics.service() : serviceName(); } @@ -110,17 +113,24 @@ private void validateMetricsAndRefreshOnFailure(Metrics metrics) { try { validateBeforeFlushingMetrics(metrics); } catch (ValidationException e){ - refreshMetricsContext(); + refreshMetricsContext(metrics); throw e; } } // This can be simplified after this issues https://github.com/awslabs/aws-embedded-metrics-java/issues/35 is fixed - private static void refreshMetricsContext() { + public static void refreshMetricsContext(Metrics metrics) { try { Field f = metricsLogger().getClass().getDeclaredField("context"); f.setAccessible(true); - f.set(metricsLogger(), new MetricsContext()); + MetricsContext context = new MetricsContext(); + + DimensionSet defaultDimensionSet = hasDefaultDimension() ? defaultDimensionSet() + : DimensionSet.of("Service", service(metrics)); + + context.setDefaultDimensions(defaultDimensionSet); + + f.set(metricsLogger(), context); } catch (NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException(e); } diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/MetricsLoggerTest.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/MetricsLoggerTest.java index 3679b04ed..2ff2cce8e 100644 --- a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/MetricsLoggerTest.java +++ b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/MetricsLoggerTest.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -16,6 +17,7 @@ import software.amazon.cloudwatchlogs.emf.model.Unit; import static java.util.Collections.*; +import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mockStatic; import static software.amazon.lambda.powertools.core.internal.SystemWrapper.getenv; @@ -43,6 +45,31 @@ static void beforeAll() { } } + @Test + void singleMetricsCaptureUtilityWithDefaultDimension() { + try (MockedStatic mocked = mockStatic(SystemWrapper.class); + MockedStatic internalWrapper = mockStatic(software.amazon.lambda.powertools.core.internal.SystemWrapper.class)) { + mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); + internalWrapper.when(() -> getenv("_X_AMZN_TRACE_ID")).thenReturn("Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1\""); + + MetricsUtils.defaultDimensionSet(DimensionSet.of("Service", "Booking")); + + MetricsUtils.withSingleMetric("Metric1", 1, Unit.COUNT, "test", + metricsLogger -> {}); + + assertThat(out.toString()) + .satisfies(s -> { + Map logAsJson = readAsJson(s); + + assertThat(logAsJson) + .containsEntry("Metric1", 1.0) + .containsEntry("Service", "Booking") + .containsKey("_aws") + .containsEntry("xray_trace_id", "1-5759e988-bd862e3fe1be46a994272793"); + }); + } + } + @Test void singleMetricsCaptureUtility() { try (MockedStatic mocked = mockStatic(SystemWrapper.class); @@ -96,6 +123,13 @@ void singleMetricsCaptureUtilityWithDefaultNameSpace() { } } + @Test + void shouldThrowExceptionWhenDefaultDimensionIsNull() { + assertThatNullPointerException() + .isThrownBy(() -> MetricsUtils.defaultDimensionSet(null)) + .withMessage("Null dimension set not allowed"); + } + private Map readAsJson(String s) { try { return mapper.readValue(s, Map.class); diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsEnabledDefaultDimensionHandler.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsEnabledDefaultDimensionHandler.java new file mode 100644 index 000000000..308601e83 --- /dev/null +++ b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsEnabledDefaultDimensionHandler.java @@ -0,0 +1,30 @@ +package software.amazon.lambda.powertools.metrics.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; +import software.amazon.cloudwatchlogs.emf.model.DimensionSet; +import software.amazon.cloudwatchlogs.emf.model.Unit; +import software.amazon.lambda.powertools.metrics.Metrics; + +import static software.amazon.lambda.powertools.metrics.MetricsUtils.defaultDimensionSet; +import static software.amazon.lambda.powertools.metrics.MetricsUtils.metricsLogger; +import static software.amazon.lambda.powertools.metrics.MetricsUtils.withSingleMetric; + +public class PowertoolsMetricsEnabledDefaultDimensionHandler implements RequestHandler { + + static { + defaultDimensionSet(DimensionSet.of("CustomDimension", "booking")); + } + + @Override + @Metrics(namespace = "ExampleApplication", service = "booking") + public Object handleRequest(Object input, Context context) { + MetricsLogger metricsLogger = metricsLogger(); + metricsLogger.putMetric("Metric1", 1, Unit.BYTES); + + withSingleMetric("Metric2", 1, Unit.COUNT, log -> {}); + + return null; + } +} diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsNoDimensionsHandler.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsNoDimensionsHandler.java index 52fccb155..1c4cc3f77 100644 --- a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsNoDimensionsHandler.java +++ b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/handlers/PowertoolsMetricsNoDimensionsHandler.java @@ -3,6 +3,7 @@ import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; +import software.amazon.cloudwatchlogs.emf.model.DimensionSet; import software.amazon.lambda.powertools.metrics.Metrics; import static software.amazon.lambda.powertools.metrics.MetricsUtils.metricsLogger; @@ -10,10 +11,11 @@ public class PowertoolsMetricsNoDimensionsHandler implements RequestHandler { @Override - @Metrics(namespace = "ExampleApplication", service = "booking", captureColdStart = true) + @Metrics(namespace = "ExampleApplication", service = "booking") public Object handleRequest(Object input, Context context) { MetricsLogger metricsLogger = metricsLogger(); - metricsLogger.setDimensions(); + metricsLogger.putMetric("CoolMetric", 1); + metricsLogger.setDimensions(new DimensionSet()); return null; } diff --git a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/internal/LambdaMetricsAspectTest.java b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/internal/LambdaMetricsAspectTest.java index ca05c3fbe..438558f71 100644 --- a/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/internal/LambdaMetricsAspectTest.java +++ b/powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/internal/LambdaMetricsAspectTest.java @@ -18,9 +18,12 @@ import org.mockito.Mock; import org.mockito.MockedStatic; import software.amazon.cloudwatchlogs.emf.config.SystemWrapper; +import software.amazon.cloudwatchlogs.emf.model.DimensionSet; import software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.metrics.MetricsUtils; import software.amazon.lambda.powertools.metrics.ValidationException; import software.amazon.lambda.powertools.metrics.handlers.PowertoolsMetricsColdStartEnabledHandler; +import software.amazon.lambda.powertools.metrics.handlers.PowertoolsMetricsEnabledDefaultDimensionHandler; import software.amazon.lambda.powertools.metrics.handlers.PowertoolsMetricsEnabledHandler; import software.amazon.lambda.powertools.metrics.handlers.PowertoolsMetricsEnabledStreamHandler; import software.amazon.lambda.powertools.metrics.handlers.PowertoolsMetricsExceptionWhenNoMetricsHandler; @@ -62,7 +65,6 @@ void setUp() throws IllegalAccessException { setupContext(); writeStaticField(LambdaHandlerProcessor.class, "IS_COLD_START", null, true); System.setOut(new PrintStream(out)); - requestHandler = new PowertoolsMetricsEnabledHandler(); } @AfterEach @@ -78,6 +80,8 @@ public void metricsWithoutColdStart() { mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); internalWrapper.when(() -> getenv("_X_AMZN_TRACE_ID")).thenReturn("Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1\""); + MetricsUtils.defaultDimensionSet(new DimensionSet()); + requestHandler = new PowertoolsMetricsEnabledHandler(); requestHandler.handleRequest("input", context); assertThat(out.toString().split("\n")) @@ -109,13 +113,57 @@ public void metricsWithoutColdStart() { } } + @Test + public void metricsWithDefaultDimensionSpecified() { + try (MockedStatic mocked = mockStatic(SystemWrapper.class); + MockedStatic internalWrapper = mockStatic(software.amazon.lambda.powertools.core.internal.SystemWrapper.class)) { + + mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); + internalWrapper.when(() -> getenv("_X_AMZN_TRACE_ID")).thenReturn("Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1\""); + + requestHandler = new PowertoolsMetricsEnabledDefaultDimensionHandler(); + + requestHandler.handleRequest("input", context); + + assertThat(out.toString().split("\n")) + .hasSize(2) + .satisfies(s -> { + Map logAsJson = readAsJson(s[0]); + + assertThat(logAsJson) + .containsEntry("Metric2", 1.0) + .containsEntry("CustomDimension", "booking") + .containsKey("_aws") + .containsEntry("xray_trace_id", "1-5759e988-bd862e3fe1be46a994272793") + .containsEntry("function_request_id", "123ABC"); + + Map aws = (Map) logAsJson.get("_aws"); + + assertThat(aws.get("CloudWatchMetrics")) + .asString() + .contains("Namespace=ExampleApplication"); + + logAsJson = readAsJson(s[1]); + + assertThat(logAsJson) + .containsEntry("Metric1", 1.0) + .containsEntry("CustomDimension", "booking") + .containsEntry("function_request_id", "123ABC") + .containsKey("_aws"); + }); + } + } + @Test public void metricsWithColdStart() { - requestHandler = new PowertoolsMetricsColdStartEnabledHandler(); try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); + + MetricsUtils.defaultDimensionSet(new DimensionSet()); + requestHandler = new PowertoolsMetricsColdStartEnabledHandler(); + requestHandler.handleRequest("input", context); assertThat(out.toString().split("\n")) @@ -144,10 +192,13 @@ public void metricsWithColdStart() { @Test public void noColdStartMetricsWhenColdStartDone() { - requestHandler = new PowertoolsMetricsColdStartEnabledHandler(); try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); + + MetricsUtils.defaultDimensionSet(new DimensionSet()); + requestHandler = new PowertoolsMetricsColdStartEnabledHandler(); + requestHandler.handleRequest("input", context); requestHandler.handleRequest("input", context); @@ -186,11 +237,13 @@ public void noColdStartMetricsWhenColdStartDone() { @Test public void metricsWithStreamHandler() throws IOException { - streamHandler = new PowertoolsMetricsEnabledStreamHandler(); try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); + MetricsUtils.defaultDimensionSet(new DimensionSet()); + streamHandler = new PowertoolsMetricsEnabledStreamHandler(); + streamHandler.handleRequest(new ByteArrayInputStream(new byte[]{}), new ByteArrayOutputStream(), context); assertThat(out.toString()) @@ -208,10 +261,12 @@ public void metricsWithStreamHandler() throws IOException { @Test public void exceptionWhenNoMetricsEmitted() { - requestHandler = new PowertoolsMetricsExceptionWhenNoMetricsHandler(); try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); + MetricsUtils.defaultDimensionSet(new DimensionSet()); + requestHandler = new PowertoolsMetricsExceptionWhenNoMetricsHandler(); + assertThatExceptionOfType(ValidationException.class) .isThrownBy(() -> requestHandler.handleRequest("input", context)) .withMessage("No metrics captured, at least one metrics must be emitted"); @@ -220,10 +275,13 @@ public void exceptionWhenNoMetricsEmitted() { @Test public void noExceptionWhenNoMetricsEmitted() { - requestHandler = new PowertoolsMetricsNoExceptionWhenNoMetricsHandler(); try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); + + MetricsUtils.defaultDimensionSet(new DimensionSet()); + requestHandler = new PowertoolsMetricsNoExceptionWhenNoMetricsHandler(); + requestHandler.handleRequest("input", context); assertThat(out.toString()) @@ -238,35 +296,49 @@ public void noExceptionWhenNoMetricsEmitted() { } @Test - public void exceptionWhenNoDimensionsSet() { - requestHandler = new PowertoolsMetricsNoDimensionsHandler(); + public void allowWhenNoDimensionsSet() { try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); + MetricsUtils.defaultDimensionSet(new DimensionSet()); - assertThatExceptionOfType(ValidationException.class) - .isThrownBy(() -> requestHandler.handleRequest("input", context)) - .withMessage("Number of Dimensions must be in range of 1-9. Actual size: 0."); + requestHandler = new PowertoolsMetricsNoDimensionsHandler(); + requestHandler.handleRequest("input", context); + + assertThat(out.toString()) + .satisfies(s -> { + Map logAsJson = readAsJson(s); + assertThat(logAsJson) + .containsEntry("CoolMetric", 1.0) + .containsEntry("function_request_id", "123ABC") + .containsKey("_aws"); + }); } } @Test public void exceptionWhenTooManyDimensionsSet() { - requestHandler = new PowertoolsMetricsTooManyDimensionsHandler(); try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); + MetricsUtils.defaultDimensionSet(new DimensionSet()); + + requestHandler = new PowertoolsMetricsTooManyDimensionsHandler(); + assertThatExceptionOfType(ValidationException.class) .isThrownBy(() -> requestHandler.handleRequest("input", context)) - .withMessage("Number of Dimensions must be in range of 1-9. Actual size: 14."); + .withMessage("Number of Dimensions must be in range of 0-9. Actual size: 14."); } } @Test public void metricsPublishedEvenHandlerThrowsException() { - requestHandler = new PowertoolsMetricsWithExceptionInHandler(); try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda"); + + MetricsUtils.defaultDimensionSet(new DimensionSet()); + requestHandler = new PowertoolsMetricsWithExceptionInHandler(); + assertThatExceptionOfType(IllegalStateException.class) .isThrownBy(() -> requestHandler.handleRequest("input", context)) .withMessage("Whoops, unexpected exception");