Skip to content

Add validation for dimension, metric, namespace and timestamp #119

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Sep 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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