From 125a637a16df3836b53734de9fb3581ba47ff2b3 Mon Sep 17 00:00:00 2001 From: Mark Kuhn Date: Fri, 16 Sep 2022 11:00:04 -0700 Subject: [PATCH] Add validation for dimension, metric, namespace and timestamp (#119) * add validation for dimension, metric, namespace and timestamp * fix thread safety test using invalid log group name * migrate for junit5 for some test classes * fix code smells * fix integ tests * add DimensionSetExceededException to readme * linter fix * update link to timestamp specs * update readme with validation errors * update packages and rm duplicates --- README.md | 38 +- build.gradle | 18 +- .../src/main/java/emf/canary/ECSRunnable.java | 34 +- examples/agent/src/main/java/agent/App.java | 14 +- examples/ecs-firelens/src/main/java/App.java | 7 +- examples/lambda/src/main/java/Handler.java | 11 +- .../emf/MetricsLoggerIntegrationTest.java | 18 +- .../amazon/cloudwatchlogs/emf/Constants.java | 13 +- .../environment/AgentBasedEnvironment.java | 6 +- .../emf/environment/DefaultEnvironment.java | 2 +- .../exception/InvalidDimensionException.java | 23 ++ .../emf/exception/InvalidMetricException.java | 23 ++ .../exception/InvalidNamespaceException.java | 23 ++ .../exception/InvalidTimestampException.java | 23 ++ .../emf/logger/MetricsLogger.java | 41 +- .../emf/model/DimensionSet.java | 39 +- .../emf/model/MetricDirective.java | 24 +- .../emf/model/MetricsContext.java | 50 ++- .../cloudwatchlogs/emf/util/Validator.java | 176 +++++++++ .../emf/environment/ECSEnvironmentTest.java | 4 +- .../emf/logger/MetricsLoggerTest.java | 351 ++++++++++++------ .../logger/MetricsLoggerThreadSafetyTest.java | 38 +- .../emf/model/DimensionSetTest.java | 27 +- .../emf/model/MetricDirectiveTest.java | 71 ++-- .../MetricDirectiveThreadSafetyTest.java | 2 +- .../emf/model/MetricsContextTest.java | 79 ++-- .../emf/model/RootNodeTest.java | 53 +-- .../emf/sinks/AgentSinkTest.java | 15 +- 28 files changed, 860 insertions(+), 363 deletions(-) create mode 100644 src/main/java/software/amazon/cloudwatchlogs/emf/exception/InvalidDimensionException.java create mode 100644 src/main/java/software/amazon/cloudwatchlogs/emf/exception/InvalidMetricException.java create mode 100644 src/main/java/software/amazon/cloudwatchlogs/emf/exception/InvalidNamespaceException.java create mode 100644 src/main/java/software/amazon/cloudwatchlogs/emf/exception/InvalidTimestampException.java create mode 100644 src/main/java/software/amazon/cloudwatchlogs/emf/util/Validator.java diff --git a/README.md b/README.md index fab0edcb..f49a6dd2 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,14 @@ import software.amazon.cloudwatchlogs.emf.model.Unit; class Example { public static void main(String[] args) { MetricsLogger metrics = new MetricsLogger(); - metrics.putDimensions(DimensionSet.of("Service", "Aggregator")); - metrics.putMetric("ProcessingLatency", 100, Unit.MILLISECONDS); + + try { + metrics.putDimensions(DimensionSet.of("Service", "Aggregator")); + metrics.putMetric("ProcessingLatency", 100, Unit.MILLISECONDS); + } catch (InvalidDimensionException | InvalidMetricException e) { + log.error(e); + } + metrics.putProperty("RequestId", "422b1569-16f6-4a03-b8f0-fe3fd9b100f8"); metrics.flush(); } @@ -60,8 +66,14 @@ environment's sink. A full example can be found in the [`examples`](examples) di DefaultEnvironment environment = new DefaultEnvironment(EnvironmentConfigurationProvider.getConfig()); MetricsLogger logger = new MetricsLogger(environment); -logger.setDimensions(DimensionSet.of("Operation", "ProcessRecords")); -logger.putMetric("ExampleMetric", 100, Unit.MILLISECONDS); + +try { + logger.setDimensions(DimensionSet.of("Operation", "ProcessRecords")); + logger.putMetric("ExampleMetric", 100, Unit.MILLISECONDS); +} catch (InvalidDimensionException | InvalidMetricException e) { + log.error(e); +} + logger.putProperty("RequestId", "422b1569-16f6-4a03-b8f0-fe3fd9b100f8"); logger.flush(); @@ -85,7 +97,7 @@ Requirements: - Name Length 1-255 characters - Name must be ASCII characters only - Values must be in the range of 8.515920e-109 to 1.174271e+108. In addition, special values (for example, NaN, +Infinity, -Infinity) are not supported. -- Units must meet CloudWatch Metrics unit requirements, if not it will default to None. See [MetricDatum](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html) for valid values. +- Metrics must meet CloudWatch Metrics requirements, otherwise a `InvalidMetricException` will be thrown. See [MetricDatum](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html) for valid values. Examples: @@ -104,8 +116,8 @@ Requirements: Examples: ```java -putProperty("RequestId", "422b1569-16f6-4a03-b8f0-fe3fd9b100f8") -putProperty("InstanceId", "i-1234567890") +putProperty("RequestId", "422b1569-16f6-4a03-b8f0-fe3fd9b100f8"); +putProperty("InstanceId", "i-1234567890"); putProperty("Device", new HashMap() {{ put("Id", "61270781-c6ac-46f1-baf7-22c808af8162"); put("Name", "Transducer"); @@ -126,6 +138,7 @@ Requirements: - Length 1-255 characters - ASCII characters only +- Dimensions must meet CloudWatch Dimension requirements, otherwise a `InvalidDimensionException` or `DimensionSetExceededException` will be thrown. See [Dimension](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_Dimension.html) for valid values. Examples: @@ -135,8 +148,9 @@ putDimensions(DimensionSet.of("Operation", "Aggregator", "DeviceType", "Actuator ``` - MetricsLogger **setDimensions**(DimensionSet... dimensionSets) +- MetricsLogger **setDimensions**(boolean useDefault, DimensionSet... dimensionSets) -Explicitly override all dimensions. This will remove the default dimensions. +Explicitly override all dimensions. This will remove the default dimensions unless `useDefault` is set to `true`. **WARNING**:Each dimension set will result in a new CloudWatch metric (even dimension sets with the same values). If the cardinality of a particular value is expected to be high, you should consider @@ -146,6 +160,7 @@ Requirements: - Length 1-255 characters - ASCII characters only +- Dimensions must meet CloudWatch Dimension requirements, otherwise a `InvalidDimensionException` or `DimensionSetExceededException` will be thrown. See [Dimension](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_Dimension.html) for valid values. Examples: @@ -167,10 +182,6 @@ setDimensions( ) ``` -- MetricsLogger **setDimensions**(boolean useDefault, DimensionSet... dimensionSets) - -Override all custom dimensions, with an option to configure whether to use default dimensions. - - MetricsLogger **resetDimensions**(boolean useDefault) Explicitly clear all custom dimensions. The behavior of whether default dimensions should be used can be configured by the input parameter. @@ -189,6 +200,7 @@ Requirements: - Name Length 1-255 characters - Name must be ASCII characters only +- Namespace must meet CloudWatch requirements, otherwise a `InvalidNamespaceException` will be thrown. See [Namespaces](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#Namespace) for valid values. Examples: @@ -200,6 +212,8 @@ setNamespace("MyApplication") Sets the timestamp of the metrics. If not set, current time of the client will be used. +Timestamp must meet CloudWatch requirements, otherwise a `InvalidTimestampException` will be thrown. See [Timestamps](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#about_timestamp) for valid values. + Examples: ```java diff --git a/build.gradle b/build.gradle index f259b381..9f833a1c 100644 --- a/build.gradle +++ b/build.gradle @@ -58,27 +58,30 @@ configurations { } dependencies { - annotationProcessor 'org.projectlombok:lombok:1.18.12' + annotationProcessor 'org.projectlombok:lombok:1.18.24' compileOnly 'org.projectlombok:lombok:1.18.12' implementation 'com.fasterxml.jackson.core:jackson-core:2.11.1' implementation 'com.fasterxml.jackson.core:jackson-annotations:2.11.1' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.1' - implementation 'org.slf4j:slf4j-api:1.7.30' + implementation 'org.slf4j:slf4j-api:2.0.1' implementation 'org.javatuples:javatuples:1.2' + implementation 'org.apache.commons:commons-lang3:3.12.0' // Use JUnit test framework testImplementation 'software.amazon.awssdk:cloudwatch:2.13.54' - testImplementation 'junit:junit:4.13' - testImplementation 'org.apache.commons:commons-lang3:3.10' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0' + testImplementation 'org.junit.jupiter:junit-jupiter-params:5.9.0' + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.0' + testImplementation 'org.junit.vintage:junit-vintage-engine:5.9.0' + testImplementation 'org.apache.commons:commons-lang3:3.12.0' testImplementation "org.mockito:mockito-core:2.+" testImplementation "org.powermock:powermock-module-junit4:2.0.2" testImplementation "org.powermock:powermock-api-mockito2:2.0.2" testImplementation "com.github.javafaker:javafaker:1.0.2" testImplementation "com.github.tomakehurst:wiremock-jre8:2.27.0" - testImplementation 'software.amazon.awssdk:cloudwatch:2.13.54' - testCompileOnly 'org.projectlombok:lombok:1.18.12' - testAnnotationProcessor 'org.projectlombok:lombok:1.18.12' + testCompileOnly 'org.projectlombok:lombok:1.18.24' + testAnnotationProcessor 'org.projectlombok:lombok:1.18.24' implementation 'org.openjdk.jmh:jmh-core:1.29' implementation 'org.openjdk.jmh:jmh-generator-annprocess:1.29' @@ -104,6 +107,7 @@ spotless { test { outputs.upToDateWhen {false} + useJUnitPlatform() } jar { diff --git a/canarytests/agent/src/main/java/emf/canary/ECSRunnable.java b/canarytests/agent/src/main/java/emf/canary/ECSRunnable.java index ddccc360..0c0e8d93 100644 --- a/canarytests/agent/src/main/java/emf/canary/ECSRunnable.java +++ b/canarytests/agent/src/main/java/emf/canary/ECSRunnable.java @@ -2,6 +2,9 @@ import software.amazon.cloudwatchlogs.emf.config.Configuration; import software.amazon.cloudwatchlogs.emf.config.EnvironmentConfigurationProvider; +import software.amazon.cloudwatchlogs.emf.exception.InvalidDimensionException; +import software.amazon.cloudwatchlogs.emf.exception.InvalidMetricException; +import software.amazon.cloudwatchlogs.emf.exception.InvalidNamespaceException; import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; import software.amazon.cloudwatchlogs.emf.model.DimensionSet; import software.amazon.cloudwatchlogs.emf.model.Unit; @@ -21,23 +24,32 @@ public void run() { } Configuration config = EnvironmentConfigurationProvider.getConfig(); config.setLogGroupName("/Canary/Java/CloudWatchAgent/Metrics"); - logger.setNamespace("Canary"); - logger.setDimensions( - DimensionSet.of( - "Runtime", "Java8", - "Platform", "ECS", - "Agent", "CloudWatchAgent", - "Version", version)); + + try { + logger.setNamespace("Canary"); + logger.setDimensions( + DimensionSet.of( + "Runtime", "Java8", + "Platform", "ECS", + "Agent", "CloudWatchAgent", + "Version", version)); + } catch (InvalidNamespaceException | InvalidDimensionException e) { + System.out.println(e); + } MemoryMXBean runtimeMXBean = ManagementFactory.getMemoryMXBean(); long heapTotal = Runtime.getRuntime().totalMemory(); long heapUsed = runtimeMXBean.getHeapMemoryUsage().getUsed(); long nonHeapUsed = runtimeMXBean.getNonHeapMemoryUsage().getUsed(); - logger.putMetric("Invoke", 1, Unit.COUNT); - logger.putMetric("Memory.HeapTotal", heapTotal, Unit.COUNT); - logger.putMetric("Memory.HeapUsed", heapUsed, Unit.COUNT); - logger.putMetric("Memory.JVMUsedTotal", heapUsed + nonHeapUsed, Unit.COUNT); + try { + logger.putMetric("Invoke", 1, Unit.COUNT); + logger.putMetric("Memory.HeapTotal", heapTotal, Unit.COUNT); + logger.putMetric("Memory.HeapUsed", heapUsed, Unit.COUNT); + logger.putMetric("Memory.JVMUsedTotal", heapUsed + nonHeapUsed, Unit.COUNT); + } catch (InvalidMetricException e) { + System.out.println(e); + } logger.flush(); } diff --git a/examples/agent/src/main/java/agent/App.java b/examples/agent/src/main/java/agent/App.java index dd7878c3..a63b8464 100644 --- a/examples/agent/src/main/java/agent/App.java +++ b/examples/agent/src/main/java/agent/App.java @@ -3,6 +3,8 @@ import software.amazon.cloudwatchlogs.emf.config.EnvironmentConfigurationProvider; import software.amazon.cloudwatchlogs.emf.environment.DefaultEnvironment; import software.amazon.cloudwatchlogs.emf.environment.Environment; +import software.amazon.cloudwatchlogs.emf.exception.InvalidDimensionException; +import software.amazon.cloudwatchlogs.emf.exception.InvalidMetricException; import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; import software.amazon.cloudwatchlogs.emf.model.DimensionSet; import software.amazon.cloudwatchlogs.emf.model.Unit; @@ -13,13 +15,17 @@ public class App { public static void main(String[] args) { DefaultEnvironment environment = new DefaultEnvironment(EnvironmentConfigurationProvider.getConfig()); - emitMetric(environment); - emitMetric(environment); - emitMetric(environment); + try { + emitMetric(environment); + emitMetric(environment); + emitMetric(environment); + } catch (InvalidMetricException | InvalidDimensionException e) { + System.out.println(e); + } environment.getSink().shutdown().orTimeout(360_000L, TimeUnit.MILLISECONDS); } - private static void emitMetric(Environment environment) { + private static void emitMetric(Environment environment) throws InvalidDimensionException, InvalidMetricException { MetricsLogger logger = new MetricsLogger(environment); logger.setDimensions(DimensionSet.of("Operation", "Agent")); logger.putMetric("ExampleMetric", 100, Unit.MILLISECONDS); diff --git a/examples/ecs-firelens/src/main/java/App.java b/examples/ecs-firelens/src/main/java/App.java index c71f5368..4f191bef 100644 --- a/examples/ecs-firelens/src/main/java/App.java +++ b/examples/ecs-firelens/src/main/java/App.java @@ -21,6 +21,7 @@ import software.amazon.cloudwatchlogs.emf.environment.ECSEnvironment; import software.amazon.cloudwatchlogs.emf.environment.Environment; import software.amazon.cloudwatchlogs.emf.environment.EnvironmentProvider; +import software.amazon.cloudwatchlogs.emf.exception.InvalidMetricException; import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; import software.amazon.cloudwatchlogs.emf.model.Unit; import sun.misc.Signal; @@ -69,7 +70,11 @@ public void handle(HttpExchange he) throws IOException { MetricsLogger logger = new MetricsLogger(); logger.putProperty("Method", he.getRequestMethod()); logger.putProperty("Url", he.getRequestURI()); - logger.putMetric("ProcessingTime", System.currentTimeMillis() - time, Unit.MILLISECONDS); + try { + logger.putMetric("ProcessingTime", System.currentTimeMillis() - time, Unit.MILLISECONDS); + } catch (InvalidMetricException e) { + System.out.println(e); + } logger.flush(); System.out.println(new EnvironmentProvider().resolveEnvironment().join().getClass().getName()); diff --git a/examples/lambda/src/main/java/Handler.java b/examples/lambda/src/main/java/Handler.java index 913dc06a..767c2b75 100644 --- a/examples/lambda/src/main/java/Handler.java +++ b/examples/lambda/src/main/java/Handler.java @@ -1,5 +1,7 @@ import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; +import software.amazon.cloudwatchlogs.emf.exception.InvalidDimensionException; +import software.amazon.cloudwatchlogs.emf.exception.InvalidMetricException; import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; import software.amazon.cloudwatchlogs.emf.model.DimensionSet; import software.amazon.cloudwatchlogs.emf.model.Unit; @@ -14,8 +16,13 @@ public String handleRequest(Map event, Context context) { String response = "200 OK"; MetricsLogger logger = new MetricsLogger(); - logger.putDimensions(DimensionSet.of("Service", "Aggregator")); - logger.putMetric("ProcessingLatency", 100, Unit.MILLISECONDS); + try { + logger.putDimensions(DimensionSet.of("Service", "Aggregator")); + logger.putMetric("ProcessingLatency", 100, Unit.MILLISECONDS); + } catch (InvalidDimensionException | InvalidMetricException e) { + System.out.println(e); + } + logger.putProperty("AccountId", "123456789"); logger.putProperty("RequestId", "422b1569-16f6-4a03-b8f0-fe3fd9b100f8"); logger.putProperty("DeviceId", "61270781-c6ac-46f1-baf7-22c808af8162"); diff --git a/src/integration-test/java/software/amazon/cloudwatchlogs/emf/MetricsLoggerIntegrationTest.java b/src/integration-test/java/software/amazon/cloudwatchlogs/emf/MetricsLoggerIntegrationTest.java index 1471fec7..325fa74f 100644 --- a/src/integration-test/java/software/amazon/cloudwatchlogs/emf/MetricsLoggerIntegrationTest.java +++ b/src/integration-test/java/software/amazon/cloudwatchlogs/emf/MetricsLoggerIntegrationTest.java @@ -33,6 +33,8 @@ import software.amazon.cloudwatchlogs.emf.config.EnvironmentConfigurationProvider; import software.amazon.cloudwatchlogs.emf.environment.DefaultEnvironment; import software.amazon.cloudwatchlogs.emf.environment.Environment; +import software.amazon.cloudwatchlogs.emf.exception.InvalidDimensionException; +import software.amazon.cloudwatchlogs.emf.exception.InvalidMetricException; import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; import software.amazon.cloudwatchlogs.emf.model.DimensionSet; import software.amazon.cloudwatchlogs.emf.model.Unit; @@ -48,6 +50,8 @@ public class MetricsLoggerIntegrationTest { private DimensionSet dimensions = DimensionSet.of(dimensionName, dimensionValue); private EMFIntegrationTestHelper testHelper = new EMFIntegrationTestHelper(); + public MetricsLoggerIntegrationTest() throws InvalidDimensionException {} + @Before public void setUp() { config.setServiceName(serviceName); @@ -56,7 +60,7 @@ public void setUp() { } @Test(timeout = 120_000) - public void testSingleFlushOverTCP() throws InterruptedException { + public void testSingleFlushOverTCP() throws InterruptedException, InvalidMetricException { Environment env = new DefaultEnvironment(EnvironmentConfigurationProvider.getConfig()); String metricName = "TCP-SingleFlush"; int expectedSamples = 1; @@ -69,7 +73,7 @@ public void testSingleFlushOverTCP() throws InterruptedException { } @Test(timeout = 300_000) - public void testMultipleFlushesOverTCP() throws InterruptedException { + public void testMultipleFlushesOverTCP() throws InterruptedException, InvalidMetricException { Environment env = new DefaultEnvironment(EnvironmentConfigurationProvider.getConfig()); String metricName = "TCP-MultipleFlushes"; int expectedSamples = 3; @@ -85,7 +89,7 @@ public void testMultipleFlushesOverTCP() throws InterruptedException { } @Test(timeout = 120_000) - public void testSingleFlushOverUDP() throws InterruptedException { + public void testSingleFlushOverUDP() throws InterruptedException, InvalidMetricException { Environment env = new DefaultEnvironment(EnvironmentConfigurationProvider.getConfig()); String metricName = "UDP-SingleFlush"; int expectedSamples = 1; @@ -98,7 +102,7 @@ public void testSingleFlushOverUDP() throws InterruptedException { } @Test(timeout = 300_000) - public void testMultipleFlushOverUDP() throws InterruptedException { + public void testMultipleFlushOverUDP() throws InterruptedException, InvalidMetricException { Environment env = new DefaultEnvironment(EnvironmentConfigurationProvider.getConfig()); String metricName = "UDP-MultipleFlush"; int expectedSamples = 3; @@ -113,7 +117,7 @@ public void testMultipleFlushOverUDP() throws InterruptedException { assertTrue(retryUntilSucceed(() -> buildRequest(metricName), expectedSamples)); } - private void logMetric(Environment env, String metricName) { + private void logMetric(Environment env, String metricName) throws InvalidMetricException { MetricsLogger logger = new MetricsLogger(env); logger.putDimensions(dimensions); logger.putMetric(metricName, 100, Unit.MILLISECONDS); @@ -130,7 +134,7 @@ private String getLocalHost() { private GetMetricStatisticsRequest buildRequest(String metricName) { Instant now = Instant.now(); - List dimensions = + List dims = Arrays.asList( getDimension("ServiceName", serviceName), getDimension("ServiceType", serviceType), @@ -140,7 +144,7 @@ private GetMetricStatisticsRequest buildRequest(String metricName) { return GetMetricStatisticsRequest.builder() .namespace("aws-embedded-metrics") .metricName(metricName) - .dimensions(dimensions) + .dimensions(dims) .period(60) .startTime(now.minusMillis(5000)) .endTime(now) diff --git a/src/main/java/software/amazon/cloudwatchlogs/emf/Constants.java b/src/main/java/software/amazon/cloudwatchlogs/emf/Constants.java index 68693a5d..a81a845f 100644 --- a/src/main/java/software/amazon/cloudwatchlogs/emf/Constants.java +++ b/src/main/java/software/amazon/cloudwatchlogs/emf/Constants.java @@ -16,15 +16,24 @@ package software.amazon.cloudwatchlogs.emf; +import java.util.concurrent.TimeUnit; + public class Constants { + public static final int MAX_DIMENSION_SET_SIZE = 30; + public static final short MAX_DIMENSION_NAME_LENGTH = 250; + public static final short MAX_DIMENSION_VALUE_LENGTH = 1024; + public static final short MAX_METRIC_NAME_LENGTH = 1024; + public static final short MAX_NAMESPACE_LENGTH = 256; + public static final String VALID_NAMESPACE_REGEX = "^[a-zA-Z0-9._#:/-]+$"; + public static final long MAX_TIMESTAMP_PAST_AGE_SECONDS = TimeUnit.DAYS.toSeconds(14); + public static final long MAX_TIMESTAMP_FUTURE_AGE_SECONDS = TimeUnit.HOURS.toSeconds(2); + public static final int DEFAULT_AGENT_PORT = 25888; public static final String UNKNOWN = "Unknown"; public static final int MAX_METRICS_PER_EVENT = 100; - public static final int MAX_DIMENSION_SET_SIZE = 30; - public static final int MAX_DATAPOINTS_PER_METRIC = 100; /** diff --git a/src/main/java/software/amazon/cloudwatchlogs/emf/environment/AgentBasedEnvironment.java b/src/main/java/software/amazon/cloudwatchlogs/emf/environment/AgentBasedEnvironment.java index 5c6f69d1..3f2cee07 100644 --- a/src/main/java/software/amazon/cloudwatchlogs/emf/environment/AgentBasedEnvironment.java +++ b/src/main/java/software/amazon/cloudwatchlogs/emf/environment/AgentBasedEnvironment.java @@ -36,7 +36,7 @@ protected AgentBasedEnvironment(Configuration config) { @Override public String getName() { - if (!config.getServiceName().isPresent()) { + if (config.getServiceName().isEmpty()) { log.warn("Unknown ServiceName."); return Constants.UNKNOWN; } @@ -51,7 +51,7 @@ public String getLogGroupName() { String serviceName = getName(); // for ECS services, replace "repo:tag" format with "repo-tag" to satisfy // log group regex - serviceName = serviceName.replaceAll(":", "-"); + serviceName = serviceName.replace(":", "-"); return serviceName + "-metrics"; } } @@ -64,7 +64,7 @@ public String getLogStreamName() { public ISink getSink() { if (sink == null) { Endpoint endpoint; - if (!config.getAgentEndpoint().isPresent()) { + if (config.getAgentEndpoint().isEmpty()) { log.info( "Endpoint is not defined. Using default: {}", Endpoint.DEFAULT_TCP_ENDPOINT); diff --git a/src/main/java/software/amazon/cloudwatchlogs/emf/environment/DefaultEnvironment.java b/src/main/java/software/amazon/cloudwatchlogs/emf/environment/DefaultEnvironment.java index eadb2d1b..90d20400 100644 --- a/src/main/java/software/amazon/cloudwatchlogs/emf/environment/DefaultEnvironment.java +++ b/src/main/java/software/amazon/cloudwatchlogs/emf/environment/DefaultEnvironment.java @@ -37,7 +37,7 @@ public boolean probe() { @Override public String getType() { - if (!config.getServiceType().isPresent()) { + if (config.getServiceType().isEmpty()) { log.info("Unknown ServiceType"); return Constants.UNKNOWN; } diff --git a/src/main/java/software/amazon/cloudwatchlogs/emf/exception/InvalidDimensionException.java b/src/main/java/software/amazon/cloudwatchlogs/emf/exception/InvalidDimensionException.java new file mode 100644 index 00000000..a3285277 --- /dev/null +++ b/src/main/java/software/amazon/cloudwatchlogs/emf/exception/InvalidDimensionException.java @@ -0,0 +1,23 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package software.amazon.cloudwatchlogs.emf.exception; + +public class InvalidDimensionException extends Exception { + public InvalidDimensionException(String message) { + super(message); + } +} diff --git a/src/main/java/software/amazon/cloudwatchlogs/emf/exception/InvalidMetricException.java b/src/main/java/software/amazon/cloudwatchlogs/emf/exception/InvalidMetricException.java new file mode 100644 index 00000000..319d3441 --- /dev/null +++ b/src/main/java/software/amazon/cloudwatchlogs/emf/exception/InvalidMetricException.java @@ -0,0 +1,23 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package software.amazon.cloudwatchlogs.emf.exception; + +public class InvalidMetricException extends Exception { + public InvalidMetricException(String message) { + super(message); + } +} diff --git a/src/main/java/software/amazon/cloudwatchlogs/emf/exception/InvalidNamespaceException.java b/src/main/java/software/amazon/cloudwatchlogs/emf/exception/InvalidNamespaceException.java new file mode 100644 index 00000000..50a29618 --- /dev/null +++ b/src/main/java/software/amazon/cloudwatchlogs/emf/exception/InvalidNamespaceException.java @@ -0,0 +1,23 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package software.amazon.cloudwatchlogs.emf.exception; + +public class InvalidNamespaceException extends Exception { + public InvalidNamespaceException(String message) { + super(message); + } +} diff --git a/src/main/java/software/amazon/cloudwatchlogs/emf/exception/InvalidTimestampException.java b/src/main/java/software/amazon/cloudwatchlogs/emf/exception/InvalidTimestampException.java new file mode 100644 index 00000000..76e770e5 --- /dev/null +++ b/src/main/java/software/amazon/cloudwatchlogs/emf/exception/InvalidTimestampException.java @@ -0,0 +1,23 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package software.amazon.cloudwatchlogs.emf.exception; + +public class InvalidTimestampException extends Exception { + public InvalidTimestampException(String message) { + super(message); + } +} diff --git a/src/main/java/software/amazon/cloudwatchlogs/emf/logger/MetricsLogger.java b/src/main/java/software/amazon/cloudwatchlogs/emf/logger/MetricsLogger.java index 8f9d6773..54aa0ee4 100644 --- a/src/main/java/software/amazon/cloudwatchlogs/emf/logger/MetricsLogger.java +++ b/src/main/java/software/amazon/cloudwatchlogs/emf/logger/MetricsLogger.java @@ -22,16 +22,20 @@ import java.util.function.Supplier; import lombok.Getter; import lombok.Setter; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import software.amazon.cloudwatchlogs.emf.environment.Environment; import software.amazon.cloudwatchlogs.emf.environment.EnvironmentProvider; +import software.amazon.cloudwatchlogs.emf.exception.InvalidMetricException; +import software.amazon.cloudwatchlogs.emf.exception.InvalidNamespaceException; +import software.amazon.cloudwatchlogs.emf.exception.InvalidTimestampException; import software.amazon.cloudwatchlogs.emf.model.DimensionSet; import software.amazon.cloudwatchlogs.emf.model.MetricsContext; import software.amazon.cloudwatchlogs.emf.model.Unit; import software.amazon.cloudwatchlogs.emf.sinks.ISink; /** - * An metrics logger. Use this interface to publish logs to CloudWatch Logs and extract metrics to + * A metrics logger. Use this interface to publish logs to CloudWatch Logs and extract metrics to * CloudWatch Metrics asynchronously. */ @Slf4j @@ -86,10 +90,7 @@ public void flush() { ISink sink = environment.getSink(); configureContextForEnvironment(context, environment); sink.accept(context); - context = - flushPreserveDimensions - ? context.createCopyWithContext() - : context.createCopyWithContextWithoutDimensions(); + context = context.createCopyWithContext(flushPreserveDimensions); } finally { rwl.writeLock().unlock(); } @@ -180,7 +181,7 @@ public MetricsLogger resetDimensions(boolean useDefault) { } /** - * Put a metric value. This value will be emitted to CloudWatch Metrics asyncronously and does + * Put a metric value. This value will be emitted to CloudWatch Metrics asynchronously and does * not contribute to your account TPS limits. The value will also be available in your * CloudWatch Logs * @@ -188,25 +189,30 @@ public MetricsLogger resetDimensions(boolean useDefault) { * @param value is the value of the metric * @param unit is the unit of the metric value * @return the current logger + * @throws InvalidMetricException if the metric is invalid */ - public MetricsLogger putMetric(String key, double value, Unit unit) { - return applyReadLock( - () -> { - this.context.putMetric(key, value, unit); - return this; - }); + public MetricsLogger putMetric(String key, double value, Unit unit) + throws InvalidMetricException { + rwl.readLock().lock(); + try { + this.context.putMetric(key, value, unit); + return this; + } finally { + rwl.readLock().unlock(); + } } /** - * Put a metric value. This value will be emitted to CloudWatch Metrics asyncronously and does + * Put a metric value. This value will be emitted to CloudWatch Metrics asynchronously and does * not contribute to your account TPS limits. The value will also be available in your * CloudWatch Logs * * @param key the name of the metric * @param value the value of the metric * @return the current logger + * @throws InvalidMetricException if the metric is invalid */ - public MetricsLogger putMetric(String key, double value) { + public MetricsLogger putMetric(String key, double value) throws InvalidMetricException { this.putMetric(key, value, Unit.NONE); return this; } @@ -234,8 +240,9 @@ public MetricsLogger putMetadata(String key, Object value) { * * @param namespace the namespace of the logs * @return the current logger + * @throws InvalidNamespaceException if the namespace is invalid */ - public MetricsLogger setNamespace(String namespace) { + public MetricsLogger setNamespace(String namespace) throws InvalidNamespaceException { this.context.setNamespace(namespace); return this; } @@ -245,12 +252,14 @@ public MetricsLogger setNamespace(String namespace) { * * @param timestamp value of timestamp to be set * @return the current logger + * @throws InvalidTimestampException if the timestamp is invalid */ - public MetricsLogger setTimestamp(Instant timestamp) { + public MetricsLogger setTimestamp(Instant timestamp) throws InvalidTimestampException { this.context.setTimestamp(timestamp); return this; } + @SneakyThrows private void configureContextForEnvironment(MetricsContext context, Environment environment) { if (context.hasDefaultDimensions()) { return; diff --git a/src/main/java/software/amazon/cloudwatchlogs/emf/model/DimensionSet.java b/src/main/java/software/amazon/cloudwatchlogs/emf/model/DimensionSet.java index 157be2dd..5e8435ba 100644 --- a/src/main/java/software/amazon/cloudwatchlogs/emf/model/DimensionSet.java +++ b/src/main/java/software/amazon/cloudwatchlogs/emf/model/DimensionSet.java @@ -24,12 +24,14 @@ import lombok.Getter; import software.amazon.cloudwatchlogs.emf.Constants; import software.amazon.cloudwatchlogs.emf.exception.DimensionSetExceededException; +import software.amazon.cloudwatchlogs.emf.exception.InvalidDimensionException; +import software.amazon.cloudwatchlogs.emf.util.Validator; /** A combination of dimension values. */ public class DimensionSet { @Getter(AccessLevel.PACKAGE) - private Map dimensionRecords = new LinkedHashMap<>(); + private final Map dimensionRecords = new LinkedHashMap<>(); /** * Return a dimension set that contains a single pair of key-value. @@ -37,8 +39,9 @@ public class DimensionSet { * @param d1 Name of the single dimension * @param v1 Value of the single dimension * @return a DimensionSet from the parameters + * @throws InvalidDimensionException if the dimension name or value is invalid */ - public static DimensionSet of(String d1, String v1) { + public static DimensionSet of(String d1, String v1) throws InvalidDimensionException { return fromEntries(entryOf(d1, v1)); } @@ -50,8 +53,10 @@ public static DimensionSet of(String d1, String v1) { * @param d2 Name of the second dimension * @param v2 Value of the second dimension * @return a DimensionSet from the parameters + * @throws InvalidDimensionException if the dimension name or value is invalid */ - public static DimensionSet of(String d1, String v1, String d2, String v2) { + public static DimensionSet of(String d1, String v1, String d2, String v2) + throws InvalidDimensionException { return fromEntries(entryOf(d1, v1), entryOf(d2, v2)); } @@ -65,9 +70,10 @@ public static DimensionSet of(String d1, String v1, String d2, String v2) { * @param d3 Name of the third dimension * @param v3 Value of the third dimension * @return a DimensionSet from the parameters + * @throws InvalidDimensionException if the dimension name or value is invalid */ - public static DimensionSet of( - String d1, String v1, String d2, String v2, String d3, String v3) { + public static DimensionSet of(String d1, String v1, String d2, String v2, String d3, String v3) + throws InvalidDimensionException { return fromEntries(entryOf(d1, v1), entryOf(d2, v2), entryOf(d3, v3)); } @@ -83,16 +89,11 @@ public static DimensionSet of( * @param d4 Name of the fourth dimension * @param v4 Value of the fourth dimension * @return a DimensionSet from the parameters + * @throws InvalidDimensionException if the dimension name or value is invalid */ public static DimensionSet of( - String d1, - String v1, - String d2, - String v2, - String d3, - String v3, - String d4, - String v4) { + String d1, String v1, String d2, String v2, String d3, String v3, String d4, String v4) + throws InvalidDimensionException { return fromEntries(entryOf(d1, v1), entryOf(d2, v2), entryOf(d3, v3), entryOf(d4, v4)); } @@ -111,6 +112,7 @@ public static DimensionSet of( * @param d5 Name of the fifth dimension * @param v5 Value of the fifth dimension * @return a DimensionSet from the parameters + * @throws InvalidDimensionException if the dimension name or value is invalid */ public static DimensionSet of( String d1, @@ -122,7 +124,8 @@ public static DimensionSet of( String d4, String v4, String d5, - String v5) { + String v5) + throws InvalidDimensionException { return fromEntries( entryOf(d1, v1), @@ -132,7 +135,8 @@ public static DimensionSet of( entryOf(d5, v5)); } - private static DimensionSet fromEntries(DimensionEntry... entries) { + private static DimensionSet fromEntries(DimensionEntry... entries) + throws InvalidDimensionException { DimensionSet ds = new DimensionSet(); for (DimensionEntry entry : entries) { ds.addDimension(entry.key, entry.value); @@ -149,8 +153,11 @@ private static DimensionEntry entryOf(String key, String value) { * * @param dimension Name of the dimension * @param value Value of the dimension + * @throws InvalidDimensionException if the dimension name or value is invalid */ - public void addDimension(String dimension, String value) { + public void addDimension(String dimension, String value) throws InvalidDimensionException { + Validator.validateDimensionSet(dimension, value); + if (this.getDimensionKeys().size() >= Constants.MAX_DIMENSION_SET_SIZE) { throw new DimensionSetExceededException(); } diff --git a/src/main/java/software/amazon/cloudwatchlogs/emf/model/MetricDirective.java b/src/main/java/software/amazon/cloudwatchlogs/emf/model/MetricDirective.java index e2d8e6d1..f369614d 100644 --- a/src/main/java/software/amazon/cloudwatchlogs/emf/model/MetricDirective.java +++ b/src/main/java/software/amazon/cloudwatchlogs/emf/model/MetricDirective.java @@ -152,30 +152,20 @@ boolean hasNoMetrics() { } /** - * Create a copy of the metric directive without having the existing metrics + * Create a copy of the metric directive * - * @return A object of metric directive + * @param preserveDimensions indicates whether the custom dimensions should be preserved + * @return A metric directive object */ - MetricDirective copyWithoutMetrics() { + MetricDirective copyWithoutMetrics(boolean preserveDimensions) { MetricDirective metricDirective = new MetricDirective(); metricDirective.setDefaultDimensions(this.defaultDimensions); - metricDirective.setDimensions(this.dimensions); metricDirective.setNamespace(this.namespace); metricDirective.shouldUseDefaultDimension = this.shouldUseDefaultDimension; - return metricDirective; - } - /** - * Create a copy of the metric directive without having the existing metrics and custom - * dimensions. The original state of default dimensions are preserved. - * - * @return an object of metric directive - */ - MetricDirective copyWithoutMetricsAndDimensions() { - MetricDirective metricDirective = new MetricDirective(); - metricDirective.setDefaultDimensions(this.defaultDimensions); - metricDirective.setNamespace(this.namespace); - metricDirective.shouldUseDefaultDimension = this.shouldUseDefaultDimension; + if (preserveDimensions) { + metricDirective.setDimensions(this.dimensions); + } return metricDirective; } diff --git a/src/main/java/software/amazon/cloudwatchlogs/emf/model/MetricsContext.java b/src/main/java/software/amazon/cloudwatchlogs/emf/model/MetricsContext.java index 18cd5015..abdaf478 100644 --- a/src/main/java/software/amazon/cloudwatchlogs/emf/model/MetricsContext.java +++ b/src/main/java/software/amazon/cloudwatchlogs/emf/model/MetricsContext.java @@ -21,11 +21,16 @@ import java.util.*; import lombok.Getter; import software.amazon.cloudwatchlogs.emf.Constants; +import software.amazon.cloudwatchlogs.emf.exception.InvalidDimensionException; +import software.amazon.cloudwatchlogs.emf.exception.InvalidMetricException; +import software.amazon.cloudwatchlogs.emf.exception.InvalidNamespaceException; +import software.amazon.cloudwatchlogs.emf.exception.InvalidTimestampException; +import software.amazon.cloudwatchlogs.emf.util.Validator; /** Stores metrics and their associated properties and dimensions. */ public class MetricsContext { - @Getter private RootNode rootNode; + @Getter private final RootNode rootNode; private MetricDirective metricDirective; @@ -48,7 +53,8 @@ public MetricsContext( String namespace, Map properties, List dimensionSets, - DimensionSet defaultDimensionSet) { + DimensionSet defaultDimensionSet) + throws InvalidNamespaceException { this(); setNamespace(namespace); setDefaultDimensions(defaultDimensionSet); @@ -69,8 +75,10 @@ public String getNamespace() { * Update the namespace with the parameter. * * @param namespace The new namespace + * @throws InvalidNamespaceException if the namespace is invalid */ - public void setNamespace(String namespace) { + public void setNamespace(String namespace) throws InvalidNamespaceException { + Validator.validateNamespace(namespace); metricDirective.setNamespace(namespace); } @@ -105,8 +113,10 @@ public boolean hasDefaultDimensions() { * @param key Name of the metric * @param value Value of the metric * @param unit The unit of the metric + * @throws InvalidMetricException if the metric is invalid */ - public void putMetric(String key, double value, Unit unit) { + public void putMetric(String key, double value, Unit unit) throws InvalidMetricException { + Validator.validateMetric(key, value, unit); metricDirective.putMetric(key, value, unit); } @@ -120,8 +130,9 @@ public void putMetric(String key, double value, Unit unit) { * * @param key Name of the metric * @param value Value of the metric + * @throws InvalidMetricException if the metric is invalid */ - public void putMetric(String key, double value) { + public void putMetric(String key, double value) throws InvalidMetricException { putMetric(key, value, Unit.NONE); } @@ -167,8 +178,9 @@ public void putDimension(DimensionSet dimensionSet) { * * @param dimension the name of the dimension * @param value the value associated with the dimension + * @throws InvalidDimensionException if the dimension is invalid */ - public void putDimension(String dimension, String value) { + public void putDimension(String dimension, String value) throws InvalidDimensionException { metricDirective.putDimensionSet(DimensionSet.of(dimension, value)); } @@ -224,24 +236,26 @@ public Instant getTimestamp() { * Update timestamp field in the metadata * * @param timestamp value of timestamp to be set + * @throws InvalidTimestampException if the timestamp is invalid */ - public void setTimestamp(Instant timestamp) { + public void setTimestamp(Instant timestamp) throws InvalidTimestampException { + Validator.validateTimestamp(timestamp); rootNode.getAws().setTimestamp(timestamp); } - /** @return Creates an independently flushable context. */ - public MetricsContext createCopyWithContext() { - return new MetricsContext(metricDirective.copyWithoutMetrics()); - } - - /** @return Creates an independently flushable context without metrics and custom dimensions */ - public MetricsContext createCopyWithContextWithoutDimensions() { - return new MetricsContext(metricDirective.copyWithoutMetricsAndDimensions()); + /** + * Create a copy of the context + * + * @param preserveDimensions indicates whether default dimensions should be preserved + * @return Creates an independently flushable context + */ + public MetricsContext createCopyWithContext(boolean preserveDimensions) { + return new MetricsContext(metricDirective.copyWithoutMetrics(preserveDimensions)); } /** * Serialize the metrics in this context to strings. The EMF backend requires no more than 100 - * metrics in one log event. If there're more than 100 metrics, we split the metrics into + * metrics in one log event. If there are more than 100 metrics, we split the metrics into * multiple log events. * *

If a metric has more than 100 data points, we also split the metric. @@ -300,9 +314,9 @@ public List serialize() throws JsonProcessingException { private RootNode buildRootNode(Map metrics) { Metadata metadata = rootNode.getAws(); - MetricDirective metricDirective = metadata.getCloudWatchMetrics().get(0); + MetricDirective md = metadata.getCloudWatchMetrics().get(0); Metadata clonedMetadata = - metadata.withCloudWatchMetrics(Arrays.asList(metricDirective.withMetrics(metrics))); + metadata.withCloudWatchMetrics(Arrays.asList(md.withMetrics(metrics))); return rootNode.withAws(clonedMetadata); } diff --git a/src/main/java/software/amazon/cloudwatchlogs/emf/util/Validator.java b/src/main/java/software/amazon/cloudwatchlogs/emf/util/Validator.java new file mode 100644 index 00000000..18a8981f --- /dev/null +++ b/src/main/java/software/amazon/cloudwatchlogs/emf/util/Validator.java @@ -0,0 +1,176 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package software.amazon.cloudwatchlogs.emf.util; + +import java.time.Instant; +import org.apache.commons.lang3.StringUtils; +import software.amazon.cloudwatchlogs.emf.Constants; +import software.amazon.cloudwatchlogs.emf.exception.*; +import software.amazon.cloudwatchlogs.emf.model.Unit; + +public class Validator { + private Validator() { + throw new IllegalStateException("Utility class"); + } + + /** + * Validates Dimension Set. + * + * @see CloudWatch + * Dimensions + * @param dimensionName Dimension name + * @param dimensionValue Dimension value + * @throws InvalidDimensionException if the dimension name or value is invalid + */ + public static void validateDimensionSet(String dimensionName, String dimensionValue) + throws InvalidDimensionException { + + if (dimensionName == null || dimensionName.trim().isEmpty()) { + throw new InvalidDimensionException("Dimension name cannot be empty"); + } + + if (dimensionValue == null || dimensionValue.trim().isEmpty()) { + throw new InvalidDimensionException("Dimension value cannot be empty"); + } + + if (dimensionName.length() > Constants.MAX_DIMENSION_NAME_LENGTH) { + throw new InvalidDimensionException( + "Dimension name exceeds maximum length of " + + Constants.MAX_DIMENSION_NAME_LENGTH + + ": " + + dimensionName); + } + + if (dimensionValue.length() > Constants.MAX_DIMENSION_VALUE_LENGTH) { + throw new InvalidDimensionException( + "Dimension value exceeds maximum length of " + + Constants.MAX_DIMENSION_VALUE_LENGTH + + ": " + + dimensionValue); + } + + if (!StringUtils.isAsciiPrintable(dimensionName)) { + throw new InvalidDimensionException( + "Dimension name has invalid characters: " + dimensionName); + } + + if (!StringUtils.isAsciiPrintable(dimensionValue)) { + throw new InvalidDimensionException( + "Dimension value has invalid characters: " + dimensionValue); + } + + if (dimensionName.startsWith(":")) { + throw new InvalidDimensionException("Dimension name cannot start with ':'"); + } + } + + /** + * Validates Metric. + * + * @see CloudWatch + * Metric + * @param name Metric name + * @param value Metric value + * @param unit Metric unit + * @throws InvalidMetricException if metric is invalid + */ + public static void validateMetric(String name, double value, Unit unit) + throws InvalidMetricException { + if (name == null || name.trim().isEmpty()) { + throw new InvalidMetricException( + "Metric name " + name + " must include at least one non-whitespace character"); + } + + if (name.length() > Constants.MAX_METRIC_NAME_LENGTH) { + throw new InvalidMetricException( + "Metric name exceeds maximum length of " + + Constants.MAX_METRIC_NAME_LENGTH + + ": " + + name); + } + + if (!Double.isFinite(value)) { + throw new InvalidMetricException("Metric value is not a number"); + } + + if (unit == null) { + throw new InvalidMetricException("Metric unit cannot be null"); + } + } + + /** + * Validates Namespace. + * + * @see CloudWatch + * Namespace + * @param namespace Namespace + * @throws InvalidNamespaceException if the namespace is invalid + */ + public static void validateNamespace(String namespace) throws InvalidNamespaceException { + if (namespace == null || namespace.trim().isEmpty()) { + throw new InvalidNamespaceException( + "Namespace must include at least one non-whitespace character"); + } + + if (namespace.length() > Constants.MAX_NAMESPACE_LENGTH) { + throw new InvalidNamespaceException( + "Namespace exceeds maximum length of " + + Constants.MAX_NAMESPACE_LENGTH + + ": " + + namespace); + } + + if (!namespace.matches(Constants.VALID_NAMESPACE_REGEX)) { + throw new InvalidNamespaceException( + "Namespace contains invalid characters: " + namespace); + } + } + + /** + * Validates Timestamp. + * + * @see CloudWatch + * Timestamp + * @param timestamp Timestamp + * @throws InvalidTimestampException if timestamp is invalid + */ + public static void validateTimestamp(Instant timestamp) throws InvalidTimestampException { + if (timestamp == null) { + throw new InvalidTimestampException("Timestamp cannot be null"); + } + + if (timestamp.isAfter( + Instant.now().plusSeconds(Constants.MAX_TIMESTAMP_FUTURE_AGE_SECONDS))) { + throw new InvalidTimestampException( + "Timestamp cannot be more than " + + Constants.MAX_TIMESTAMP_FUTURE_AGE_SECONDS + + " seconds in the future"); + } + + if (timestamp.isBefore( + Instant.now().minusSeconds(Constants.MAX_TIMESTAMP_PAST_AGE_SECONDS))) { + throw new InvalidTimestampException( + "Timestamp cannot be more than " + + Constants.MAX_TIMESTAMP_PAST_AGE_SECONDS + + " seconds in the past"); + } + } +} diff --git a/src/test/java/software/amazon/cloudwatchlogs/emf/environment/ECSEnvironmentTest.java b/src/test/java/software/amazon/cloudwatchlogs/emf/environment/ECSEnvironmentTest.java index 22e80a86..a58ea522 100644 --- a/src/test/java/software/amazon/cloudwatchlogs/emf/environment/ECSEnvironmentTest.java +++ b/src/test/java/software/amazon/cloudwatchlogs/emf/environment/ECSEnvironmentTest.java @@ -151,10 +151,10 @@ public void testGetLogGroupNameReturnNonEmpty() { @Test public void testGetLogGroupNameReplaceColon() { - String serviceName = "testRepo:testTag"; + String serviceName = "testRepo:test:tag"; when(config.getServiceName()).thenReturn(Optional.of(serviceName)); - assertEquals(environment.getLogGroupName(), "testRepo-testTag-metrics"); + assertEquals("testRepo-test-tag-metrics", environment.getLogGroupName()); } @Test diff --git a/src/test/java/software/amazon/cloudwatchlogs/emf/logger/MetricsLoggerTest.java b/src/test/java/software/amazon/cloudwatchlogs/emf/logger/MetricsLoggerTest.java index 5756dab9..569cac1b 100644 --- a/src/test/java/software/amazon/cloudwatchlogs/emf/logger/MetricsLoggerTest.java +++ b/src/test/java/software/amazon/cloudwatchlogs/emf/logger/MetricsLoggerTest.java @@ -16,30 +16,34 @@ package software.amazon.cloudwatchlogs.emf.logger; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; import java.time.Instant; import java.util.List; import java.util.concurrent.CompletableFuture; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import software.amazon.cloudwatchlogs.emf.Constants; import software.amazon.cloudwatchlogs.emf.environment.Environment; import software.amazon.cloudwatchlogs.emf.environment.EnvironmentProvider; +import software.amazon.cloudwatchlogs.emf.exception.InvalidDimensionException; +import software.amazon.cloudwatchlogs.emf.exception.InvalidMetricException; +import software.amazon.cloudwatchlogs.emf.exception.InvalidNamespaceException; +import software.amazon.cloudwatchlogs.emf.exception.InvalidTimestampException; import software.amazon.cloudwatchlogs.emf.model.DimensionSet; import software.amazon.cloudwatchlogs.emf.model.MetricsContext; import software.amazon.cloudwatchlogs.emf.sinks.SinkShunt; -public class MetricsLoggerTest { +class MetricsLoggerTest { private MetricsLogger logger; private EnvironmentProvider envProvider; private SinkShunt sink; private Environment environment; - @Before + @BeforeEach public void setUp() { envProvider = mock(EnvironmentProvider.class); environment = mock(Environment.class); @@ -48,34 +52,76 @@ public void setUp() { when(envProvider.resolveEnvironment()) .thenReturn(CompletableFuture.completedFuture(environment)); when(environment.getSink()).thenReturn(sink); + when(environment.getLogGroupName()).thenReturn("test-log-group"); + when(environment.getName()).thenReturn("test-env-name"); + when(environment.getType()).thenReturn("test-env-type"); + logger = new MetricsLogger(envProvider); } @Test - public void testPutProperty() { + void putProperty_setsProperty() { String propertyName = "Property"; String propertyValue = "PropValue"; logger.putProperty(propertyName, propertyValue); logger.flush(); - Assert.assertEquals(propertyValue, sink.getContext().getProperty(propertyName)); + assertEquals(propertyValue, sink.getContext().getProperty(propertyName)); } @Test - public void testPutDimension() { + void putDimensions_setsDimension() throws InvalidDimensionException { String dimensionName = "dim"; String dimensionValue = "dimValue"; logger.putDimensions(DimensionSet.of(dimensionName, dimensionValue)); logger.flush(); - Assert.assertEquals(1, sink.getContext().getDimensions().size()); - Assert.assertEquals( + assertEquals(1, sink.getContext().getDimensions().size()); + assertEquals( dimensionValue, sink.getContext().getDimensions().get(0).getDimensionValue(dimensionName)); } + @ParameterizedTest + @ValueSource(strings = {"", " ", "ƊĪⱮḔǸŠƗȌŅ", ":dim"}) + void whenSetDimension_withInvalidName_thenThrowInvalidDimensionException(String dimensionName) { + assertThrows( + InvalidDimensionException.class, () -> DimensionSet.of(dimensionName, "dimValue")); + } + + @ParameterizedTest + @ValueSource(strings = {"", " ", "ṼẬḺƯỂ"}) + void whenSetDimension_withInvalidValue_thenThrowInvalidDimensionException( + String dimensionValue) { + assertThrows( + InvalidDimensionException.class, () -> DimensionSet.of("dimName", dimensionValue)); + } + + @Test + void whenSetDimension_withNameTooLong_thenThrowDimensionException() { + String dimensionName = "a".repeat(Constants.MAX_DIMENSION_NAME_LENGTH + 1); + String dimensionValue = "dimValue"; + assertThrows( + InvalidDimensionException.class, + () -> DimensionSet.of(dimensionName, dimensionValue)); + } + @Test - public void testOverrideDefaultDimensions() { + void whenSetDimension_withValueTooLong_thenThrowDimensionException() { + String dimensionName = "dim"; + String dimensionValue = "a".repeat(Constants.MAX_DIMENSION_VALUE_LENGTH + 1); + assertThrows( + InvalidDimensionException.class, + () -> DimensionSet.of(dimensionName, dimensionValue)); + } + + @Test + void whenSetDimension_withNullName_thenThrowDimensionException() { + assertThrows(InvalidDimensionException.class, () -> DimensionSet.of(null, "dimValue")); + } + + @Test + void setDefaultDimensions_overridesDefaultDimensions() throws InvalidDimensionException { String dimensionName = "dim"; String dimensionValue = "dimValue"; String defaultDimName = "defaultDim"; @@ -88,13 +134,12 @@ public void testOverrideDefaultDimensions() { logger.setDimensions(DimensionSet.of(dimensionName, dimensionValue)); logger.flush(); - Assert.assertEquals(1, sink.getContext().getDimensions().size()); - Assert.assertNull( - sink.getContext().getDimensions().get(0).getDimensionValue(defaultDimName)); + assertEquals(1, sink.getContext().getDimensions().size()); + assertNull(sink.getContext().getDimensions().get(0).getDimensionValue(defaultDimName)); } @Test - public void testResetWithDefaultDimensions() { + void resetDimensions_resetsDimensionsWithDefaultDimensions() throws InvalidDimensionException { String dimensionName = "dim"; String dimensionValue = "dimValue"; logger.putDimensions(DimensionSet.of("foo", "bar")); @@ -102,15 +147,16 @@ public void testResetWithDefaultDimensions() { logger.putDimensions(DimensionSet.of(dimensionName, dimensionValue)); logger.flush(); - Assert.assertEquals(sink.getContext().getDimensions().size(), 1); - Assert.assertEquals(sink.getContext().getDimensions().get(0).getDimensionKeys().size(), 4); - Assert.assertEquals( + assertEquals(1, sink.getContext().getDimensions().size()); + assertEquals(4, sink.getContext().getDimensions().get(0).getDimensionKeys().size()); + assertEquals( sink.getContext().getDimensions().get(0).getDimensionValue(dimensionName), dimensionValue); } @Test - public void testResetWithoutDefaultDimensions() { + void resetDimensions_resetsDimensionsWithoutDefaultDimensions() + throws InvalidDimensionException { String dimensionName = "dim"; String dimensionValue = "dimValue"; logger.putDimensions(DimensionSet.of("foo", "bar")); @@ -118,15 +164,15 @@ public void testResetWithoutDefaultDimensions() { logger.putDimensions(DimensionSet.of(dimensionName, dimensionValue)); logger.flush(); - Assert.assertEquals(sink.getContext().getDimensions().size(), 1); - Assert.assertEquals(sink.getContext().getDimensions().get(0).getDimensionKeys().size(), 1); - Assert.assertEquals( + assertEquals(1, sink.getContext().getDimensions().size()); + assertEquals(1, sink.getContext().getDimensions().get(0).getDimensionKeys().size()); + assertEquals( sink.getContext().getDimensions().get(0).getDimensionValue(dimensionName), dimensionValue); } @Test - public void testOverridePreviousDimensions() { + void setDimensions_overridesPreviousDimensions() throws InvalidDimensionException { String dimensionName = "dim"; String dimensionValue = "dimValue"; @@ -134,55 +180,197 @@ public void testOverridePreviousDimensions() { logger.setDimensions(DimensionSet.of(dimensionName, dimensionValue)); logger.flush(); - Assert.assertEquals(1, sink.getContext().getDimensions().size()); - Assert.assertEquals(1, sink.getContext().getDimensions().get(0).getDimensionKeys().size()); - Assert.assertEquals( + assertEquals(1, sink.getContext().getDimensions().size()); + assertEquals(1, sink.getContext().getDimensions().get(0).getDimensionKeys().size()); + assertEquals( dimensionValue, sink.getContext().getDimensions().get(0).getDimensionValue(dimensionName)); } @Test - public void testSetDimensionsAndPreserveDefault() { + void setDimensions_overridesPreviousDimensionsAndPreservesDefault() + throws InvalidDimensionException { String dimensionName = "dim"; String dimensionValue = "dimValue"; logger.putDimensions(DimensionSet.of("foo", "bar")); logger.setDimensions(true, DimensionSet.of(dimensionName, dimensionValue)); logger.flush(); - Assert.assertEquals(sink.getContext().getDimensions().size(), 1); - Assert.assertEquals(sink.getContext().getDimensions().get(0).getDimensionKeys().size(), 4); - Assert.assertEquals( + assertEquals(1, sink.getContext().getDimensions().size()); + assertEquals(4, sink.getContext().getDimensions().get(0).getDimensionKeys().size()); + assertEquals( sink.getContext().getDimensions().get(0).getDimensionValue(dimensionName), dimensionValue); } @Test - public void testSetNamespace() { + void setDimensions_clearsDefaultDimensions() throws InvalidMetricException { + MetricsLogger logger = new MetricsLogger(envProvider); + logger.setDimensions(); + logger.putMetric("Count", 1); + logger.flush(); + List dimensions = sink.getContext().getDimensions(); + + assertEquals(0, dimensions.size()); + assertEquals(1, sink.getLogEvents().size()); + + String logEvent = sink.getLogEvents().get(0); + assertTrue(logEvent.contains("\"Dimensions\":[]")); + } + + @Test + void flush_PreservesDimensions() throws InvalidDimensionException { + MetricsLogger logger = new MetricsLogger(envProvider); + logger.setDimensions(DimensionSet.of("Name", "Test")); + logger.flush(); + expectDimension("Name", "Test"); + + logger.flush(); + expectDimension("Name", "Test"); + } + + @Test + void flush_doesNotPreserveDimensions() throws InvalidDimensionException { + logger.putDimensions(DimensionSet.of("Name", "Test")); + logger.setFlushPreserveDimensions(false); + + logger.flush(); + assertEquals(4, sink.getContext().getDimensions().get(0).getDimensionKeys().size()); + expectDimension("Name", "Test"); + + logger.flush(); + assertEquals(3, sink.getContext().getDimensions().get(0).getDimensionKeys().size()); + expectDimension("Name", null); + } + + @Test + void setDimensions_clearsAllDimensions() { + MetricsLogger logger = new MetricsLogger(envProvider); + + logger.setDimensions(); + logger.flush(); + + List dimensions = sink.getContext().getDimensions(); + assertEquals(0, dimensions.size()); + } + + @Test + void whenSetDimensions_withMultipleFlush_thenClearsDimensions() { + MetricsLogger logger = new MetricsLogger(envProvider); + + logger.setDimensions(); + logger.flush(); + + assertEquals(0, sink.getContext().getDimensions().size()); + + logger.flush(); + assertEquals(0, sink.getContext().getDimensions().size()); + } + + @Test + void whenPutMetric_withTooLongName_thenThrowInvalidMetricException() { + String name1 = "a".repeat(Constants.MAX_METRIC_NAME_LENGTH + 1); + assertThrows(InvalidMetricException.class, () -> logger.putMetric(name1, 1)); + } + + @Test + void whenPutMetric_withNullName_thenThrowInvalidMetricException() { + assertThrows(InvalidMetricException.class, () -> logger.putMetric(null, 1)); + } + + @Test + void whenPutMetric_withEmptyName_thenThrowInvalidMetricException() { + assertThrows(InvalidMetricException.class, () -> logger.putMetric("", 1)); + } + + @ParameterizedTest + @ValueSource(doubles = {Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY}) + void whenPutMetric_withInvalidValue_thenThrowInvalidMetricException(double value) { + EnvironmentProvider envProvider = mock(EnvironmentProvider.class); + MetricsLogger logger = new MetricsLogger(envProvider); + assertThrows(InvalidMetricException.class, () -> logger.putMetric("name", value)); + } + + @Test + void whenPutMetric_withNullUnit_thenThrowInvalidMetricException() { + assertThrows(InvalidMetricException.class, () -> logger.putMetric("test", 1, null)); + } + + @Test + void setNamespace_setsNamespace() throws InvalidNamespaceException { String namespace = "testNamespace"; logger.setNamespace(namespace); logger.flush(); - Assert.assertEquals(namespace, sink.getContext().getNamespace()); + assertEquals(namespace, sink.getContext().getNamespace()); + } + + @ParameterizedTest + @ValueSource(strings = {"", " ", "ṆẪⱮḖⱾⱣǞḈȄ"}) + void whenSetNamespace_withInvalidValue_thenThrowInvalidNamespaceException(String namespace) { + EnvironmentProvider envProvider = mock(EnvironmentProvider.class); + MetricsLogger logger = new MetricsLogger(envProvider); + assertThrows(InvalidNamespaceException.class, () -> logger.setNamespace(namespace)); + } + + @Test + void whenSetNamespace_withNameTooLong_thenThrowInvalidNamespaceException() { + String namespace = "a".repeat(Constants.MAX_NAMESPACE_LENGTH + 1); + assertThrows(InvalidNamespaceException.class, () -> logger.setNamespace(namespace)); } @Test - public void testFlushWithDefaultTimestamp() { + void flush_usesDefaultTimestamp() { logger.flush(); - Assert.assertNotNull(sink.getContext().getTimestamp()); + assertNotNull(sink.getContext().getTimestamp()); } @Test - public void testSetTimestamp() { + void setTimestamp_setsTimestamp() throws InvalidTimestampException { Instant now = Instant.now(); logger.setTimestamp(now); logger.flush(); - Assert.assertEquals(now, sink.getContext().getTimestamp()); + assertEquals(now, sink.getContext().getTimestamp()); + } + + @Test + void whenSetTimestamp_withInvalidValueInFuture_thenThrowException() { + Instant now = Instant.now(); + Instant invalidTimestamp = now.plusSeconds(Constants.MAX_TIMESTAMP_FUTURE_AGE_SECONDS + 1); + assertThrows(InvalidTimestampException.class, () -> logger.setTimestamp(invalidTimestamp)); } @Test - public void testFlushWithConfiguredServiceName() { + void whenSetTimestamp_withInvalidValueInPast_thenThrowException() { + Instant now = Instant.now(); + Instant invalidTimestamp = now.minusSeconds(Constants.MAX_TIMESTAMP_PAST_AGE_SECONDS + 1); + assertThrows(InvalidTimestampException.class, () -> logger.setTimestamp(invalidTimestamp)); + } + + @Test + void setTimestamp_withValidValueInFuture() throws InvalidTimestampException { + Instant now = Instant.now(); + Instant validTimestamp = now.plusSeconds(Constants.MAX_TIMESTAMP_FUTURE_AGE_SECONDS - 1); + logger.setTimestamp(validTimestamp); + logger.flush(); + + assertEquals(validTimestamp, sink.getContext().getTimestamp()); + } + + @Test + void setTimestamp_withValidValueInPast() throws InvalidTimestampException { + Instant now = Instant.now(); + Instant validTimestamp = now.minusSeconds(Constants.MAX_TIMESTAMP_PAST_AGE_SECONDS - 1); + logger.setTimestamp(validTimestamp); + logger.flush(); + + assertEquals(validTimestamp, sink.getContext().getTimestamp()); + } + + @Test + void testFlushWithConfiguredServiceName() { String serviceName = "TestServiceName"; when(environment.getName()).thenReturn(serviceName); logger.flush(); @@ -191,7 +379,7 @@ public void testFlushWithConfiguredServiceName() { } @Test - public void testFlushWithConfiguredServiceType() { + void testFlushWithConfiguredServiceType() { String serviceType = "TestServiceType"; when(environment.getType()).thenReturn(serviceType); logger.flush(); @@ -200,7 +388,7 @@ public void testFlushWithConfiguredServiceType() { } @Test - public void testFlushWithConfiguredLogGroup() { + void testFlushWithConfiguredLogGroup() { String logGroup = "MyLogGroup"; when(environment.getLogGroupName()).thenReturn(logGroup); logger.flush(); @@ -209,7 +397,7 @@ public void testFlushWithConfiguredLogGroup() { } @Test - public void testFlushWithDefaultDimensionDefined() { + void testFlushWithDefaultDimensionDefined() throws InvalidDimensionException { MetricsContext metricsContext = new MetricsContext(); metricsContext.setDefaultDimensions(DimensionSet.of("foo", "bar")); logger = new MetricsLogger(envProvider, metricsContext); @@ -223,7 +411,7 @@ public void testFlushWithDefaultDimensionDefined() { @SuppressWarnings("") @Test - public void testUseDefaultEnvironmentOnResolverException() { + void testUseDefaultEnvironmentOnResolverException() { String serviceType = "TestServiceType"; CompletableFuture future = CompletableFuture.supplyAsync( @@ -242,56 +430,7 @@ public void testUseDefaultEnvironmentOnResolverException() { } @Test - public void testNoDefaultDimensions() { - MetricsLogger logger = new MetricsLogger(envProvider); - logger.setDimensions(); - logger.putMetric("Count", 1); - logger.flush(); - List dimensions = sink.getContext().getDimensions(); - - assertEquals(0, dimensions.size()); - assertEquals(1, sink.getLogEvents().size()); - - String logEvent = sink.getLogEvents().get(0); - assertTrue(logEvent.contains("\"Dimensions\":[]")); - } - - @Test - public void testNoDefaultDimensionsAfterSetDimension() { - MetricsLogger logger = new MetricsLogger(envProvider); - - logger.setDimensions(DimensionSet.of("Name", "Test")); - logger.flush(); - expectDimension("Name", "Test"); - } - - @Test - public void testFlushPreserveDimensions() { - MetricsLogger logger = new MetricsLogger(envProvider); - logger.setDimensions(DimensionSet.of("Name", "Test")); - logger.flush(); - expectDimension("Name", "Test"); - - logger.flush(); - expectDimension("Name", "Test"); - } - - @Test - public void testFlushDoesntPreserveDimensions() { - logger.putDimensions(DimensionSet.of("Name", "Test")); - logger.setFlushPreserveDimensions(false); - - logger.flush(); - Assert.assertEquals(sink.getContext().getDimensions().get(0).getDimensionKeys().size(), 4); - expectDimension("Name", "Test"); - - logger.flush(); - Assert.assertEquals(sink.getContext().getDimensions().get(0).getDimensionKeys().size(), 3); - expectDimension("Name", null); - } - - @Test - public void testFlushDoesntPreserveMetrics() { + void flush_doesNotPreserveMetrics() throws InvalidMetricException, InvalidDimensionException { MetricsLogger logger = new MetricsLogger(envProvider); logger.setDimensions(DimensionSet.of("Name", "Test")); logger.putMetric("Count", 1.0); @@ -302,30 +441,6 @@ public void testFlushDoesntPreserveMetrics() { assertFalse(sink.getLogEvents().get(0).contains("Count")); } - @Test - public void testNoDimensionsAfterSetEmptyDimensionSet() { - MetricsLogger logger = new MetricsLogger(envProvider); - - logger.setDimensions(); - logger.flush(); - - List dimensions = sink.getContext().getDimensions(); - assertEquals(0, dimensions.size()); - } - - @Test - public void testNoDimensionsAfterSetEmptyDimensionSetWithMultipleFlush() { - MetricsLogger logger = new MetricsLogger(envProvider); - - logger.setDimensions(); - logger.flush(); - - assertEquals(0, sink.getContext().getDimensions().size()); - - logger.flush(); - assertEquals(0, sink.getContext().getDimensions().size()); - } - private void expectDimension(String dimension, String value) { List dimensions = sink.getContext().getDimensions(); assertEquals(1, dimensions.size()); diff --git a/src/test/java/software/amazon/cloudwatchlogs/emf/logger/MetricsLoggerThreadSafetyTest.java b/src/test/java/software/amazon/cloudwatchlogs/emf/logger/MetricsLoggerThreadSafetyTest.java index f5afc128..f17c5a34 100644 --- a/src/test/java/software/amazon/cloudwatchlogs/emf/logger/MetricsLoggerThreadSafetyTest.java +++ b/src/test/java/software/amazon/cloudwatchlogs/emf/logger/MetricsLoggerThreadSafetyTest.java @@ -1,3 +1,19 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package software.amazon.cloudwatchlogs.emf.logger; import static org.junit.Assert.assertEquals; @@ -22,6 +38,7 @@ import org.junit.Test; import software.amazon.cloudwatchlogs.emf.environment.Environment; import software.amazon.cloudwatchlogs.emf.environment.EnvironmentProvider; +import software.amazon.cloudwatchlogs.emf.exception.InvalidDimensionException; import software.amazon.cloudwatchlogs.emf.model.DimensionSet; import software.amazon.cloudwatchlogs.emf.model.MetricsContext; import software.amazon.cloudwatchlogs.emf.model.Unit; @@ -46,6 +63,10 @@ public void setUp() { when(envProvider.resolveEnvironment()) .thenReturn(CompletableFuture.completedFuture(environment)); when(environment.getSink()).thenReturn(sink); + when(environment.getLogGroupName()).thenReturn("test-log-group"); + when(environment.getName()).thenReturn("test-env-name"); + when(environment.getType()).thenReturn("test-env-type"); + logger = new MetricsLogger(envProvider); } @@ -132,7 +153,7 @@ public void testConcurrentPutDimension() throws InterruptedException { Assert.assertEquals(sink.getContext().getDimensions().size(), N_THREAD * N_PUT_DIMENSIONS); for (DimensionSet dim : dimensions) { Assert.assertEquals( - dim.getDimensionKeys().size(), 1); // default dimensions are disabled + 1, dim.getDimensionKeys().size()); // default dimensions are disabled } // check content Collections.sort( @@ -146,7 +167,8 @@ public void testConcurrentPutDimension() throws InterruptedException { } @Test - public void testConcurrentPutDimensionAfterSetDimension() throws InterruptedException { + public void testConcurrentPutDimensionAfterSetDimension() + throws InterruptedException, InvalidDimensionException { final int N_THREAD = 100; final int N_PUT_DIMENSIONS = 100; @@ -188,7 +210,7 @@ public void testConcurrentPutDimensionAfterSetDimension() throws InterruptedExce sink.getContext().getDimensions().size(), N_THREAD * N_PUT_DIMENSIONS + 1); for (DimensionSet dim : dimensions) { Assert.assertEquals( - dim.getDimensionKeys().size(), 1); // there are no default dimensions after set + 1, dim.getDimensionKeys().size()); // there are no default dimensions after set } // check content Collections.sort( @@ -243,12 +265,12 @@ public void testConcurrentFlush() throws InterruptedException, JsonProcessingExc assertEquals(allMetrics.size(), N_THREAD); for (MetricDefinitionCopy metric : allMetrics) { - assertEquals(metric.getValues().size(), 1); + assertEquals(1, metric.getValues().size()); } Collections.sort(allMetrics, Comparator.comparingDouble(m -> m.getValues().get(0))); for (int i = 0; i < N_THREAD; i++) { assertEquals(allMetrics.get(i).getName(), "Metric-" + i); - assertEquals(allMetrics.get(i).getValues().get(0), i, 1e-5); + assertEquals(i, allMetrics.get(i).getValues().get(0), 1e-5); } } @@ -306,12 +328,12 @@ public void testConcurrentFlushAndPutMetric() assertEquals(allMetrics.size(), N_THREAD * N_PUT_METRIC / 2); for (MetricDefinitionCopy metric : allMetrics) { - assertEquals(metric.getValues().size(), 1); + assertEquals(1, metric.getValues().size()); } Collections.sort(allMetrics, Comparator.comparingDouble(m -> m.getValues().get(0))); for (int i = 0; i < N_THREAD * N_PUT_METRIC / 2; i++) { assertEquals(allMetrics.get(i).getName(), "Metric-" + i); - assertEquals(allMetrics.get(i).getValues().get(0), i, 1e-5); + assertEquals(i, allMetrics.get(i).getValues().get(0), 1e-5); } } @@ -375,7 +397,7 @@ public void testConcurrentFlushAndMethodsOtherThanPutMetric() throws Interrupted // check dimension size assertEquals(dimensions.size(), N_THREAD * N_PUT_DIMENSIONS / 3); for (DimensionSet dim : dimensions) { - Assert.assertEquals(dim.getDimensionKeys().size(), 1); // there are 3 default dimensions + Assert.assertEquals(1, dim.getDimensionKeys().size()); // there are 3 default dimensions } // check dimension content Collections.sort( diff --git a/src/test/java/software/amazon/cloudwatchlogs/emf/model/DimensionSetTest.java b/src/test/java/software/amazon/cloudwatchlogs/emf/model/DimensionSetTest.java index f9e9ea1b..2d464eed 100644 --- a/src/test/java/software/amazon/cloudwatchlogs/emf/model/DimensionSetTest.java +++ b/src/test/java/software/amazon/cloudwatchlogs/emf/model/DimensionSetTest.java @@ -16,24 +16,24 @@ package software.amazon.cloudwatchlogs.emf.model; -import static org.junit.Assert.*; - -import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import software.amazon.cloudwatchlogs.emf.exception.DimensionSetExceededException; +import software.amazon.cloudwatchlogs.emf.exception.InvalidDimensionException; -public class DimensionSetTest { +class DimensionSetTest { @Test - public void testAddDimension() { + void testAddDimension() throws InvalidDimensionException { int dimensionsToBeAdded = 30; DimensionSet dimensionSet = generateDimensionSet(dimensionsToBeAdded); - assertEquals(dimensionsToBeAdded, dimensionSet.getDimensionKeys().size()); + Assertions.assertEquals(dimensionsToBeAdded, dimensionSet.getDimensionKeys().size()); } @Test - public void testAddDimensionLimitExceeded() { + void testAddDimensionLimitExceeded() { Exception exception = - assertThrows( + Assertions.assertThrows( DimensionSetExceededException.class, () -> { int dimensionSetSize = 33; @@ -43,13 +43,13 @@ public void testAddDimensionLimitExceeded() { String expectedMessage = "Maximum number of dimensions"; String actualMessage = exception.getMessage(); - assertTrue(actualMessage.contains(expectedMessage)); + Assertions.assertTrue(actualMessage.contains(expectedMessage)); } @Test - public void testMergeDimensionSets() { + void testMergeDimensionSets() { Exception exception = - assertThrows( + Assertions.assertThrows( DimensionSetExceededException.class, () -> { int dimensionSetSize = 28; @@ -62,10 +62,11 @@ public void testMergeDimensionSets() { String expectedMessage = "Maximum number of dimensions"; String actualMessage = exception.getMessage(); - assertTrue(actualMessage.contains(expectedMessage)); + Assertions.assertTrue(actualMessage.contains(expectedMessage)); } - private DimensionSet generateDimensionSet(int numOfDimensions) { + private DimensionSet generateDimensionSet(int numOfDimensions) + throws InvalidDimensionException { DimensionSet dimensionSet = new DimensionSet(); for (int i = 0; i < numOfDimensions; i++) { diff --git a/src/test/java/software/amazon/cloudwatchlogs/emf/model/MetricDirectiveTest.java b/src/test/java/software/amazon/cloudwatchlogs/emf/model/MetricDirectiveTest.java index 8234f369..8469b562 100644 --- a/src/test/java/software/amazon/cloudwatchlogs/emf/model/MetricDirectiveTest.java +++ b/src/test/java/software/amazon/cloudwatchlogs/emf/model/MetricDirectiveTest.java @@ -16,106 +16,109 @@ package software.amazon.cloudwatchlogs.emf.model; -import static org.junit.Assert.assertEquals; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Arrays; import java.util.Collections; -import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import software.amazon.cloudwatchlogs.emf.exception.InvalidDimensionException; -public class MetricDirectiveTest { - private ObjectMapper objectMapper = +class MetricDirectiveTest { + private final ObjectMapper objectMapper = new ObjectMapper().configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true); @Test - public void testDefaultNamespace() throws JsonProcessingException { + void testDefaultNamespace() throws JsonProcessingException { MetricDirective metricDirective = new MetricDirective(); String serializedMetricDirective = objectMapper.writeValueAsString(metricDirective); - assertEquals( + Assertions.assertEquals( "{\"Dimensions\":[[]],\"Metrics\":[],\"Namespace\":\"aws-embedded-metrics\"}", serializedMetricDirective); } @Test - public void testSetNamespace() throws JsonProcessingException { + void testSetNamespace() throws JsonProcessingException { MetricDirective metricDirective = new MetricDirective(); metricDirective.setNamespace("test-lambda-metrics"); String serializedMetricDirective = objectMapper.writeValueAsString(metricDirective); - assertEquals( + Assertions.assertEquals( "{\"Dimensions\":[[]],\"Metrics\":[],\"Namespace\":\"test-lambda-metrics\"}", serializedMetricDirective); } @Test - public void testPutMetric() throws JsonProcessingException { + void testPutMetric() throws JsonProcessingException { MetricDirective metricDirective = new MetricDirective(); metricDirective.putMetric("Time", 10); String serializedMetricDirective = objectMapper.writeValueAsString(metricDirective); - assertEquals( + Assertions.assertEquals( "{\"Dimensions\":[[]],\"Metrics\":[{\"Name\":\"Time\",\"Unit\":\"None\"}],\"Namespace\":\"aws-embedded-metrics\"}", serializedMetricDirective); } @Test - public void testPutSameMetricMultipleTimes() { + void testPutSameMetricMultipleTimes() { MetricDirective metricDirective = new MetricDirective(); metricDirective.putMetric("Time", 10); metricDirective.putMetric("Time", 20); - assertEquals(1, metricDirective.getAllMetrics().size()); + Assertions.assertEquals(1, metricDirective.getAllMetrics().size()); MetricDefinition[] mds = metricDirective.getAllMetrics().toArray(new MetricDefinition[0]); - assertEquals(Arrays.asList(10d, 20d), mds[0].getValues()); + Assertions.assertEquals(Arrays.asList(10d, 20d), mds[0].getValues()); } @Test - public void testPutMetricWithoutUnit() { + void testPutMetricWithoutUnit() { MetricDirective metricDirective = new MetricDirective(); metricDirective.putMetric("Time", 10); - assertEquals(Unit.NONE, metricDirective.getMetrics().get("Time").getUnit()); + Assertions.assertEquals(Unit.NONE, metricDirective.getMetrics().get("Time").getUnit()); } @Test - public void testPutMetricWithUnit() { + void testPutMetricWithUnit() { MetricDirective metricDirective = new MetricDirective(); metricDirective.putMetric("Time", 10, Unit.MILLISECONDS); - assertEquals(Unit.MILLISECONDS, metricDirective.getMetrics().get("Time").getUnit()); + Assertions.assertEquals( + Unit.MILLISECONDS, metricDirective.getMetrics().get("Time").getUnit()); } @Test - public void testPutDimensions() throws JsonProcessingException { + void testPutDimensions() throws JsonProcessingException, InvalidDimensionException { MetricDirective metricDirective = new MetricDirective(); metricDirective.putDimensionSet( DimensionSet.of("Region", "us-east-1", "Instance", "inst-1")); String serializedMetricDirective = objectMapper.writeValueAsString(metricDirective); - assertEquals( + Assertions.assertEquals( "{\"Dimensions\":[[\"Region\",\"Instance\"]],\"Metrics\":[],\"Namespace\":\"aws-embedded-metrics\"}", serializedMetricDirective); } @Test - public void testPutDimensionSetWhenMultipleDimensionSets() throws JsonProcessingException { + void testPutDimensionSetWhenMultipleDimensionSets() + throws JsonProcessingException, InvalidDimensionException { MetricDirective metricDirective = new MetricDirective(); metricDirective.putDimensionSet(DimensionSet.of("Region", "us-east-1")); metricDirective.putDimensionSet(DimensionSet.of("Instance", "inst-1")); String serializedMetricDirective = objectMapper.writeValueAsString(metricDirective); - assertEquals( + Assertions.assertEquals( "{\"Dimensions\":[[\"Region\"],[\"Instance\"]],\"Metrics\":[],\"Namespace\":\"aws-embedded-metrics\"}", serializedMetricDirective); } @Test - public void testPutDimensionSetWhenDuplicateDimensionSets() throws JsonProcessingException { + void testPutDimensionSetWhenDuplicateDimensionSets() + throws JsonProcessingException, InvalidDimensionException { MetricDirective metricDirective = new MetricDirective(); metricDirective.putDimensionSet(new DimensionSet()); metricDirective.putDimensionSet(DimensionSet.of("Region", "us-east-1")); @@ -134,14 +137,14 @@ public void testPutDimensionSetWhenDuplicateDimensionSets() throws JsonProcessin String serializedMetricDirective = objectMapper.writeValueAsString(metricDirective); - assertEquals( + Assertions.assertEquals( "{\"Dimensions\":[[],[\"Region\"],[\"Instance\",\"Region\"],[\"Instance\"]],\"Metrics\":[],\"Namespace\":\"aws-embedded-metrics\"}", serializedMetricDirective); } @Test - public void testPutDimensionSetWhenDuplicateDimensionSetsWillSortCorrectly() - throws JsonProcessingException { + void testPutDimensionSetWhenDuplicateDimensionSetsWillSortCorrectly() + throws JsonProcessingException, InvalidDimensionException { MetricDirective metricDirective = new MetricDirective(); metricDirective.putDimensionSet(new DimensionSet()); metricDirective.putDimensionSet(DimensionSet.of("Region", "us-east-1")); @@ -160,22 +163,23 @@ public void testPutDimensionSetWhenDuplicateDimensionSetsWillSortCorrectly() String serializedMetricDirective = objectMapper.writeValueAsString(metricDirective); - assertEquals( + Assertions.assertEquals( "{\"Dimensions\":[[\"Instance\",\"Region\"],[\"Instance\"],[\"Region\"],[]],\"Metrics\":[],\"Namespace\":\"aws-embedded-metrics\"}", serializedMetricDirective); } @Test - public void testGetDimensionAfterSetDimensions() { + void testGetDimensionAfterSetDimensions() throws InvalidDimensionException { MetricDirective metricDirective = new MetricDirective(); metricDirective.setDefaultDimensions(DimensionSet.of("Dim", "Default")); metricDirective.setDimensions(Arrays.asList(DimensionSet.of("Name", "Test"))); - assertEquals(1, metricDirective.getAllDimensions().size()); + Assertions.assertEquals(1, metricDirective.getAllDimensions().size()); } @Test - public void testPutDimensionsWhenDefaultDimensionsDefined() throws JsonProcessingException { + void testPutDimensionsWhenDefaultDimensionsDefined() + throws JsonProcessingException, InvalidDimensionException { MetricDirective metricDirective = new MetricDirective(); metricDirective.setDefaultDimensions(DimensionSet.of("Version", "1")); metricDirective.putDimensionSet(DimensionSet.of("Region", "us-east-1")); @@ -183,13 +187,14 @@ public void testPutDimensionsWhenDefaultDimensionsDefined() throws JsonProcessin String serializedMetricDirective = objectMapper.writeValueAsString(metricDirective); - assertEquals( + Assertions.assertEquals( "{\"Dimensions\":[[\"Version\",\"Region\"],[\"Version\",\"Instance\"]],\"Metrics\":[],\"Namespace\":\"aws-embedded-metrics\"}", serializedMetricDirective); } @Test - public void testPutDimensionsAfterSetDimensions() throws JsonProcessingException { + void testPutDimensionsAfterSetDimensions() + throws JsonProcessingException, InvalidDimensionException { MetricDirective metricDirective = new MetricDirective(); metricDirective.setDimensions(Collections.singletonList(DimensionSet.of("Version", "1"))); metricDirective.putDimensionSet(DimensionSet.of("Region", "us-east-1")); @@ -197,7 +202,7 @@ public void testPutDimensionsAfterSetDimensions() throws JsonProcessingException String serializedMetricDirective = objectMapper.writeValueAsString(metricDirective); - assertEquals( + Assertions.assertEquals( "{\"Dimensions\":[[\"Version\"],[\"Region\"],[\"Instance\"]],\"Metrics\":[],\"Namespace\":\"aws-embedded-metrics\"}", serializedMetricDirective); } diff --git a/src/test/java/software/amazon/cloudwatchlogs/emf/model/MetricDirectiveThreadSafetyTest.java b/src/test/java/software/amazon/cloudwatchlogs/emf/model/MetricDirectiveThreadSafetyTest.java index 7eb1f873..28cd1b08 100644 --- a/src/test/java/software/amazon/cloudwatchlogs/emf/model/MetricDirectiveThreadSafetyTest.java +++ b/src/test/java/software/amazon/cloudwatchlogs/emf/model/MetricDirectiveThreadSafetyTest.java @@ -85,7 +85,7 @@ public void testConcurrentPutMetricWithSameKey() throws InterruptedException { t.join(); } - assertEquals(metricDirective.getAllMetrics().size(), 1); + assertEquals(1, metricDirective.getAllMetrics().size()); MetricDefinition md = metricDirective.getAllMetrics().toArray(new MetricDefinition[0])[0]; Collections.sort(md.getValues()); for (int i = 0; i < N_THREAD * N_PUT_METRIC; i++) { diff --git a/src/test/java/software/amazon/cloudwatchlogs/emf/model/MetricsContextTest.java b/src/test/java/software/amazon/cloudwatchlogs/emf/model/MetricsContextTest.java index b1913c9a..a1d40a8d 100644 --- a/src/test/java/software/amazon/cloudwatchlogs/emf/model/MetricsContextTest.java +++ b/src/test/java/software/amazon/cloudwatchlogs/emf/model/MetricsContextTest.java @@ -16,10 +16,6 @@ package software.amazon.cloudwatchlogs.emf.model; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.json.JsonMapper; @@ -27,13 +23,17 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import software.amazon.cloudwatchlogs.emf.Constants; +import software.amazon.cloudwatchlogs.emf.exception.InvalidDimensionException; +import software.amazon.cloudwatchlogs.emf.exception.InvalidMetricException; +import software.amazon.cloudwatchlogs.emf.exception.InvalidTimestampException; -public class MetricsContextTest { +class MetricsContextTest { @Test - public void testSerializeLessThan100Metrics() throws JsonProcessingException { + void testSerializeLessThan100Metrics() throws JsonProcessingException, InvalidMetricException { MetricsContext mc = new MetricsContext(); int metricCount = 10; for (int i = 0; i < metricCount; i++) { @@ -42,19 +42,19 @@ public void testSerializeLessThan100Metrics() throws JsonProcessingException { } List events = mc.serialize(); - assertEquals(1, events.size()); + Assertions.assertEquals(1, events.size()); List metrics = parseMetrics(events.get(0)); - assertEquals(metrics.size(), metricCount); + Assertions.assertEquals(metrics.size(), metricCount); for (MetricDefinition metric : metrics) { MetricDefinition originalMetric = mc.getRootNode().metrics().get(metric.getName()); - assertEquals(originalMetric.getName(), metric.getName()); - assertEquals(originalMetric.getUnit(), metric.getUnit()); + Assertions.assertEquals(originalMetric.getName(), metric.getName()); + Assertions.assertEquals(originalMetric.getUnit(), metric.getUnit()); } } @Test - public void testSerializeMoreThen100Metrics() throws JsonProcessingException { + void testSerializeMoreThen100Metrics() throws JsonProcessingException, InvalidMetricException { MetricsContext mc = new MetricsContext(); int metricCount = 253; int expectedEventCount = 3; @@ -64,22 +64,23 @@ public void testSerializeMoreThen100Metrics() throws JsonProcessingException { } List events = mc.serialize(); - assertEquals(expectedEventCount, events.size()); + Assertions.assertEquals(expectedEventCount, events.size()); List allMetrics = new ArrayList<>(); for (String event : events) { allMetrics.addAll(parseMetrics(event)); } - assertEquals(metricCount, allMetrics.size()); + Assertions.assertEquals(metricCount, allMetrics.size()); for (MetricDefinition metric : allMetrics) { MetricDefinition originalMetric = mc.getRootNode().metrics().get(metric.getName()); - assertEquals(originalMetric.getName(), metric.getName()); - assertEquals(originalMetric.getUnit(), metric.getUnit()); + Assertions.assertEquals(originalMetric.getName(), metric.getName()); + Assertions.assertEquals(originalMetric.getUnit(), metric.getUnit()); } } @Test - public void testSerializeAMetricWith101DataPoints() throws JsonProcessingException { + void testSerializeAMetricWith101DataPoints() + throws JsonProcessingException, InvalidMetricException { MetricsContext mc = new MetricsContext(); int dataPointCount = 101; int expectedEventCount = 2; @@ -89,7 +90,7 @@ public void testSerializeAMetricWith101DataPoints() throws JsonProcessingExcepti } List events = mc.serialize(); - assertEquals(expectedEventCount, events.size()); + Assertions.assertEquals(expectedEventCount, events.size()); List allMetrics = new ArrayList<>(); for (String event : events) { allMetrics.addAll(parseMetrics(event)); @@ -98,12 +99,13 @@ public void testSerializeAMetricWith101DataPoints() throws JsonProcessingExcepti for (int i = 0; i < Constants.MAX_DATAPOINTS_PER_METRIC; i++) { expectedValues.add((double) i); } - assertEquals(expectedValues, allMetrics.get(0).getValues()); - assertEquals(List.of(100.0), allMetrics.get(1).getValues()); + Assertions.assertEquals(expectedValues, allMetrics.get(0).getValues()); + Assertions.assertEquals(List.of(100.0), allMetrics.get(1).getValues()); } @Test - public void testSerializeMetricsWith101DataPoints() throws JsonProcessingException { + void testSerializeMetricsWith101DataPoints() + throws JsonProcessingException, InvalidMetricException { MetricsContext mc = new MetricsContext(); int dataPointCount = 101; int expectedEventCount = 2; @@ -114,40 +116,41 @@ public void testSerializeMetricsWith101DataPoints() throws JsonProcessingExcepti mc.putMetric("metric2", 2); List events = mc.serialize(); - assertEquals(expectedEventCount, events.size()); + Assertions.assertEquals(expectedEventCount, events.size()); List metricsFromEvent1 = parseMetrics(events.get(0)); List metricsFromEvent2 = parseMetrics(events.get(1)); - assertEquals(2, metricsFromEvent1.size()); + Assertions.assertEquals(2, metricsFromEvent1.size()); List expectedValues = new ArrayList<>(); for (int i = 0; i < Constants.MAX_DATAPOINTS_PER_METRIC; i++) { expectedValues.add((double) i); } - assertEquals(expectedValues, metricsFromEvent1.get(0).getValues()); - assertEquals(List.of(2.0), metricsFromEvent1.get(1).getValues()); + Assertions.assertEquals(expectedValues, metricsFromEvent1.get(0).getValues()); + Assertions.assertEquals(List.of(2.0), metricsFromEvent1.get(1).getValues()); - assertEquals(1, metricsFromEvent2.size()); - assertEquals(List.of(100.0), metricsFromEvent2.get(0).getValues()); + Assertions.assertEquals(1, metricsFromEvent2.size()); + Assertions.assertEquals(List.of(100.0), metricsFromEvent2.get(0).getValues()); } @Test - public void testSerializeZeroMetric() throws JsonProcessingException { + void testSerializeZeroMetric() throws JsonProcessingException, InvalidDimensionException { MetricsContext mc = new MetricsContext(); mc.putDimension(DimensionSet.of("Region", "IAD")); List events = mc.serialize(); int expectedEventCount = 1; - assertEquals(expectedEventCount, events.size()); + Assertions.assertEquals(expectedEventCount, events.size()); Map rootNode = parseRootNode(events.get(0)); // If there's no metric added, the _aws would be filtered out from the log event - assertFalse(rootNode.containsKey("_aws")); + Assertions.assertFalse(rootNode.containsKey("_aws")); } @Test @SuppressWarnings("unchecked") - public void testSetTimestamp() throws JsonProcessingException { + void testSetTimestamp() + throws JsonProcessingException, InvalidTimestampException, InvalidMetricException { MetricsContext mc = new MetricsContext(); mc.putMetric("Metric", 0); @@ -157,24 +160,24 @@ public void testSetTimestamp() throws JsonProcessingException { List events = mc.serialize(); int expectedEventCount = 1; - assertEquals(expectedEventCount, events.size()); + Assertions.assertEquals(expectedEventCount, events.size()); Map rootNode = parseRootNode(events.get(0)); - assertTrue(rootNode.containsKey("_aws")); + Assertions.assertTrue(rootNode.containsKey("_aws")); Map metadata = (Map) rootNode.get("_aws"); - assertTrue(metadata.containsKey("Timestamp")); - assertEquals(now.toEpochMilli(), metadata.get("Timestamp")); + Assertions.assertTrue(metadata.containsKey("Timestamp")); + Assertions.assertEquals(now.toEpochMilli(), metadata.get("Timestamp")); } @Test - public void testPutMetadata() { + void testPutMetadata() { MetricsContext mc = new MetricsContext(); mc.putMetadata("Metadata", "MetadataValue"); Map customFields = mc.getRootNode().getAws().getCustomMetadata(); - assertEquals(customFields.size(), 1); - assertEquals(customFields.get("Metadata"), "MetadataValue"); + Assertions.assertEquals(1, customFields.size()); + Assertions.assertEquals("MetadataValue", customFields.get("Metadata")); } @SuppressWarnings("unchecked") diff --git a/src/test/java/software/amazon/cloudwatchlogs/emf/model/RootNodeTest.java b/src/test/java/software/amazon/cloudwatchlogs/emf/model/RootNodeTest.java index d72e8d71..a01d87db 100644 --- a/src/test/java/software/amazon/cloudwatchlogs/emf/model/RootNodeTest.java +++ b/src/test/java/software/amazon/cloudwatchlogs/emf/model/RootNodeTest.java @@ -16,46 +16,46 @@ package software.amazon.cloudwatchlogs.emf.model; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.List; import java.util.Map; -import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import software.amazon.cloudwatchlogs.emf.exception.InvalidDimensionException; +import software.amazon.cloudwatchlogs.emf.exception.InvalidMetricException; -public class RootNodeTest { +class RootNodeTest { @Test - public void testPutProperty() { + void testPutProperty() { RootNode rootNode = new RootNode(); rootNode.putProperty("Property", "Value"); - assertEquals("Value", rootNode.getTargetMembers().get("Property")); + Assertions.assertEquals("Value", rootNode.getTargetMembers().get("Property")); } @Test - public void testPutSamePropertyMultipleTimes() { + void testPutSamePropertyMultipleTimes() { RootNode rootNode = new RootNode(); rootNode.putProperty("Property", "Value"); rootNode.putProperty("Property", "NewValue"); - assertEquals("NewValue", rootNode.getTargetMembers().get("Property")); + Assertions.assertEquals("NewValue", rootNode.getTargetMembers().get("Property")); } @Test - public void testGetDimension() { + void testGetDimension() throws InvalidDimensionException { RootNode rootNode = new RootNode(); MetricDirective metricDirective = rootNode.getAws().createMetricDirective(); metricDirective.putDimensionSet(DimensionSet.of("Dim1", "DimValue1")); - assertEquals("DimValue1", rootNode.getTargetMembers().get("Dim1")); + Assertions.assertEquals("DimValue1", rootNode.getTargetMembers().get("Dim1")); } @Test - public void testGetTargetMembers() { + void testGetTargetMembers() throws InvalidMetricException, InvalidDimensionException { RootNode rootNode = new RootNode(); MetricsContext mc = new MetricsContext(rootNode); @@ -69,15 +69,16 @@ public void testGetTargetMembers() { mc.putProperty("Prop1", "PropValue1"); - assertEquals(List.of(10.0, 20.0), rootNode.getTargetMembers().get("Count")); - assertEquals(100.0, rootNode.getTargetMembers().get("Latency")); - assertEquals("DimVal1", rootNode.getTargetMembers().get("Dim1")); - assertEquals("PropValue1", rootNode.getTargetMembers().get("Prop1")); + Assertions.assertEquals(List.of(10.0, 20.0), rootNode.getTargetMembers().get("Count")); + Assertions.assertEquals(100.0, rootNode.getTargetMembers().get("Latency")); + Assertions.assertEquals("DimVal1", rootNode.getTargetMembers().get("Dim1")); + Assertions.assertEquals("PropValue1", rootNode.getTargetMembers().get("Prop1")); } @SuppressWarnings("unchecked") @Test - public void testSerializeRootNode() throws JsonProcessingException { + void testSerializeRootNode() + throws JsonProcessingException, InvalidMetricException, InvalidDimensionException { MetricsContext mc = new MetricsContext(); mc.setDefaultDimensions(DimensionSet.of("DefaultDim", "DefaultDimValue")); @@ -91,24 +92,24 @@ public void testSerializeRootNode() throws JsonProcessingException { objectMapper.readValue( emf_logs.get(0), new TypeReference>() {}); - assertEquals(5, emf_map.keySet().size()); - assertEquals("us-east-1", emf_map.get("Region")); - assertEquals("PropertyValue", emf_map.get("Property")); - assertEquals("DefaultDimValue", emf_map.get("DefaultDim")); - assertEquals(10.0, emf_map.get("Count")); + Assertions.assertEquals(5, emf_map.keySet().size()); + Assertions.assertEquals("us-east-1", emf_map.get("Region")); + Assertions.assertEquals("PropertyValue", emf_map.get("Property")); + Assertions.assertEquals("DefaultDimValue", emf_map.get("DefaultDim")); + Assertions.assertEquals(10.0, emf_map.get("Count")); Map metadata = (Map) emf_map.get("_aws"); - assertTrue(metadata.containsKey("Timestamp")); - assertTrue(metadata.containsKey("CloudWatchMetrics")); + Assertions.assertTrue(metadata.containsKey("Timestamp")); + Assertions.assertTrue(metadata.containsKey("CloudWatchMetrics")); } @Test - public void testSerializeRootNodeWithoutAnyMetrics() throws JsonProcessingException { + void testSerializeRootNodeWithoutAnyMetrics() throws JsonProcessingException { RootNode root = new RootNode(); String property = "foo"; String value = "bar"; root.putProperty(property, value); - assertEquals("{\"foo\":\"bar\"}", root.serialize()); + Assertions.assertEquals("{\"foo\":\"bar\"}", root.serialize()); } } diff --git a/src/test/java/software/amazon/cloudwatchlogs/emf/sinks/AgentSinkTest.java b/src/test/java/software/amazon/cloudwatchlogs/emf/sinks/AgentSinkTest.java index 4970ca42..47f26835 100644 --- a/src/test/java/software/amazon/cloudwatchlogs/emf/sinks/AgentSinkTest.java +++ b/src/test/java/software/amazon/cloudwatchlogs/emf/sinks/AgentSinkTest.java @@ -31,6 +31,7 @@ import org.junit.Test; import software.amazon.cloudwatchlogs.emf.Constants; import software.amazon.cloudwatchlogs.emf.exception.EMFClientException; +import software.amazon.cloudwatchlogs.emf.exception.InvalidMetricException; import software.amazon.cloudwatchlogs.emf.model.MetricsContext; import software.amazon.cloudwatchlogs.emf.sinks.retry.RetryStrategy; @@ -38,7 +39,7 @@ public class AgentSinkTest { @Test - public void testAccept() throws JsonProcessingException { + public void testAccept() throws JsonProcessingException, InvalidMetricException { // arrange Fixture fixture = new Fixture(); String prop = "TestProp"; @@ -79,7 +80,7 @@ public void testAccept() throws JsonProcessingException { } @Test - public void testEmptyLogGroupName() throws JsonProcessingException { + public void testEmptyLogGroupName() throws JsonProcessingException, InvalidMetricException { // arrange Fixture fixture = new Fixture(); String logGroupName = ""; @@ -110,7 +111,7 @@ public void testEmptyLogGroupName() throws JsonProcessingException { } @Test - public void testFailuresAreRetried() { + public void testFailuresAreRetried() throws InvalidMetricException { // arrange Fixture fixture = new Fixture(); fixture.client.messagesToFail = Constants.MAX_ATTEMPTS_PER_MESSAGE - 1; @@ -136,7 +137,7 @@ public void testFailuresAreRetried() { } @Test - public void testFailuresAreRetriedWithMaximumLimit() { + public void testFailuresAreRetriedWithMaximumLimit() throws InvalidMetricException { // arrange Fixture fixture = new Fixture(); fixture.client.messagesToFail = Constants.MAX_ATTEMPTS_PER_MESSAGE + 1; @@ -162,7 +163,7 @@ public void testFailuresAreRetriedWithMaximumLimit() { } @Test - public void failedMessagesAreQueued() { + public void failedMessagesAreQueued() throws InvalidMetricException { // arrange Fixture fixture = new Fixture(); fixture.client.messagesToFail = Constants.MAX_ATTEMPTS_PER_MESSAGE * 2; @@ -190,7 +191,7 @@ public void failedMessagesAreQueued() { } @Test - public void queuedMessagesAreBounded() { + public void queuedMessagesAreBounded() throws InvalidMetricException { // arrange Fixture fixture = new Fixture(); fixture.client.messagesToFail = Constants.MAX_ATTEMPTS_PER_MESSAGE * 3; @@ -218,7 +219,7 @@ public void queuedMessagesAreBounded() { } @Test - public void oldestMessagesAreDropped() { + public void oldestMessagesAreDropped() throws InvalidMetricException { // arrange Fixture fixture = new Fixture(); AgentSink sink =