Skip to content

Commit

Permalink
Add validation for dimension, metric, namespace and timestamp (#119)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Mark Kuhn authored Sep 16, 2022
1 parent 3a7e706 commit 125a637
Show file tree
Hide file tree
Showing 28 changed files with 860 additions and 363 deletions.
38 changes: 26 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand All @@ -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();

Expand All @@ -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:

Expand All @@ -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<String, String>() {{
put("Id", "61270781-c6ac-46f1-baf7-22c808af8162");
put("Name", "Transducer");
Expand All @@ -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:

Expand All @@ -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
Expand All @@ -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:

Expand All @@ -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.
Expand All @@ -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:

Expand All @@ -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
Expand Down
18 changes: 11 additions & 7 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -104,6 +107,7 @@ spotless {

test {
outputs.upToDateWhen {false}
useJUnitPlatform()
}

jar {
Expand Down
34 changes: 23 additions & 11 deletions canarytests/agent/src/main/java/emf/canary/ECSRunnable.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
}
Expand Down
14 changes: 10 additions & 4 deletions examples/agent/src/main/java/agent/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down
7 changes: 6 additions & 1 deletion examples/ecs-firelens/src/main/java/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());

Expand Down
11 changes: 9 additions & 2 deletions examples/lambda/src/main/java/Handler.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -14,8 +16,13 @@ public String handleRequest(Map<String, String> 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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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);
Expand All @@ -130,7 +134,7 @@ private String getLocalHost() {

private GetMetricStatisticsRequest buildRequest(String metricName) {
Instant now = Instant.now();
List<Dimension> dimensions =
List<Dimension> dims =
Arrays.asList(
getDimension("ServiceName", serviceName),
getDimension("ServiceType", serviceType),
Expand All @@ -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)
Expand Down
13 changes: 11 additions & 2 deletions src/main/java/software/amazon/cloudwatchlogs/emf/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down
Loading

0 comments on commit 125a637

Please sign in to comment.