Skip to content

Commit 125a637

Browse files
author
Mark Kuhn
authored
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
1 parent 3a7e706 commit 125a637

File tree

28 files changed

+860
-363
lines changed

28 files changed

+860
-363
lines changed

README.md

+26-12
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,14 @@ import software.amazon.cloudwatchlogs.emf.model.Unit;
3535
class Example {
3636
public static void main(String[] args) {
3737
MetricsLogger metrics = new MetricsLogger();
38-
metrics.putDimensions(DimensionSet.of("Service", "Aggregator"));
39-
metrics.putMetric("ProcessingLatency", 100, Unit.MILLISECONDS);
38+
39+
try {
40+
metrics.putDimensions(DimensionSet.of("Service", "Aggregator"));
41+
metrics.putMetric("ProcessingLatency", 100, Unit.MILLISECONDS);
42+
} catch (InvalidDimensionException | InvalidMetricException e) {
43+
log.error(e);
44+
}
45+
4046
metrics.putProperty("RequestId", "422b1569-16f6-4a03-b8f0-fe3fd9b100f8");
4147
metrics.flush();
4248
}
@@ -60,8 +66,14 @@ environment's sink. A full example can be found in the [`examples`](examples) di
6066
DefaultEnvironment environment = new DefaultEnvironment(EnvironmentConfigurationProvider.getConfig());
6167

6268
MetricsLogger logger = new MetricsLogger(environment);
63-
logger.setDimensions(DimensionSet.of("Operation", "ProcessRecords"));
64-
logger.putMetric("ExampleMetric", 100, Unit.MILLISECONDS);
69+
70+
try {
71+
logger.setDimensions(DimensionSet.of("Operation", "ProcessRecords"));
72+
logger.putMetric("ExampleMetric", 100, Unit.MILLISECONDS);
73+
} catch (InvalidDimensionException | InvalidMetricException e) {
74+
log.error(e);
75+
}
76+
6577
logger.putProperty("RequestId", "422b1569-16f6-4a03-b8f0-fe3fd9b100f8");
6678
logger.flush();
6779

@@ -85,7 +97,7 @@ Requirements:
8597
- Name Length 1-255 characters
8698
- Name must be ASCII characters only
8799
- 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.
88-
- 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.
100+
- 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.
89101

90102
Examples:
91103

@@ -104,8 +116,8 @@ Requirements:
104116
Examples:
105117

106118
```java
107-
putProperty("RequestId", "422b1569-16f6-4a03-b8f0-fe3fd9b100f8")
108-
putProperty("InstanceId", "i-1234567890")
119+
putProperty("RequestId", "422b1569-16f6-4a03-b8f0-fe3fd9b100f8");
120+
putProperty("InstanceId", "i-1234567890");
109121
putProperty("Device", new HashMap<String, String>() {{
110122
put("Id", "61270781-c6ac-46f1-baf7-22c808af8162");
111123
put("Name", "Transducer");
@@ -126,6 +138,7 @@ Requirements:
126138

127139
- Length 1-255 characters
128140
- ASCII characters only
141+
- 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.
129142

130143
Examples:
131144

@@ -135,8 +148,9 @@ putDimensions(DimensionSet.of("Operation", "Aggregator", "DeviceType", "Actuator
135148
```
136149

137150
- MetricsLogger **setDimensions**(DimensionSet... dimensionSets)
151+
- MetricsLogger **setDimensions**(boolean useDefault, DimensionSet... dimensionSets)
138152

139-
Explicitly override all dimensions. This will remove the default dimensions.
153+
Explicitly override all dimensions. This will remove the default dimensions unless `useDefault` is set to `true`.
140154

141155
**WARNING**:Each dimension set will result in a new CloudWatch metric (even dimension sets with the same values).
142156
If the cardinality of a particular value is expected to be high, you should consider
@@ -146,6 +160,7 @@ Requirements:
146160

147161
- Length 1-255 characters
148162
- ASCII characters only
163+
- 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.
149164

150165
Examples:
151166

@@ -167,10 +182,6 @@ setDimensions(
167182
)
168183
```
169184

170-
- MetricsLogger **setDimensions**(boolean useDefault, DimensionSet... dimensionSets)
171-
172-
Override all custom dimensions, with an option to configure whether to use default dimensions.
173-
174185
- MetricsLogger **resetDimensions**(boolean useDefault)
175186

176187
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:
189200

190201
- Name Length 1-255 characters
191202
- Name must be ASCII characters only
203+
- 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.
192204

193205
Examples:
194206

@@ -200,6 +212,8 @@ setNamespace("MyApplication")
200212

201213
Sets the timestamp of the metrics. If not set, current time of the client will be used.
202214

215+
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.
216+
203217
Examples:
204218

205219
```java

build.gradle

+11-7
Original file line numberDiff line numberDiff line change
@@ -58,27 +58,30 @@ configurations {
5858
}
5959

6060
dependencies {
61-
annotationProcessor 'org.projectlombok:lombok:1.18.12'
61+
annotationProcessor 'org.projectlombok:lombok:1.18.24'
6262

6363
compileOnly 'org.projectlombok:lombok:1.18.12'
6464
implementation 'com.fasterxml.jackson.core:jackson-core:2.11.1'
6565
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.11.1'
6666
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.1'
67-
implementation 'org.slf4j:slf4j-api:1.7.30'
67+
implementation 'org.slf4j:slf4j-api:2.0.1'
6868
implementation 'org.javatuples:javatuples:1.2'
69+
implementation 'org.apache.commons:commons-lang3:3.12.0'
6970

7071
// Use JUnit test framework
7172
testImplementation 'software.amazon.awssdk:cloudwatch:2.13.54'
72-
testImplementation 'junit:junit:4.13'
73-
testImplementation 'org.apache.commons:commons-lang3:3.10'
73+
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
74+
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.9.0'
75+
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.0'
76+
testImplementation 'org.junit.vintage:junit-vintage-engine:5.9.0'
77+
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
7478
testImplementation "org.mockito:mockito-core:2.+"
7579
testImplementation "org.powermock:powermock-module-junit4:2.0.2"
7680
testImplementation "org.powermock:powermock-api-mockito2:2.0.2"
7781
testImplementation "com.github.javafaker:javafaker:1.0.2"
7882
testImplementation "com.github.tomakehurst:wiremock-jre8:2.27.0"
79-
testImplementation 'software.amazon.awssdk:cloudwatch:2.13.54'
80-
testCompileOnly 'org.projectlombok:lombok:1.18.12'
81-
testAnnotationProcessor 'org.projectlombok:lombok:1.18.12'
83+
testCompileOnly 'org.projectlombok:lombok:1.18.24'
84+
testAnnotationProcessor 'org.projectlombok:lombok:1.18.24'
8285

8386
implementation 'org.openjdk.jmh:jmh-core:1.29'
8487
implementation 'org.openjdk.jmh:jmh-generator-annprocess:1.29'
@@ -104,6 +107,7 @@ spotless {
104107

105108
test {
106109
outputs.upToDateWhen {false}
110+
useJUnitPlatform()
107111
}
108112

109113
jar {

canarytests/agent/src/main/java/emf/canary/ECSRunnable.java

+23-11
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
import software.amazon.cloudwatchlogs.emf.config.Configuration;
44
import software.amazon.cloudwatchlogs.emf.config.EnvironmentConfigurationProvider;
5+
import software.amazon.cloudwatchlogs.emf.exception.InvalidDimensionException;
6+
import software.amazon.cloudwatchlogs.emf.exception.InvalidMetricException;
7+
import software.amazon.cloudwatchlogs.emf.exception.InvalidNamespaceException;
58
import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger;
69
import software.amazon.cloudwatchlogs.emf.model.DimensionSet;
710
import software.amazon.cloudwatchlogs.emf.model.Unit;
@@ -21,23 +24,32 @@ public void run() {
2124
}
2225
Configuration config = EnvironmentConfigurationProvider.getConfig();
2326
config.setLogGroupName("/Canary/Java/CloudWatchAgent/Metrics");
24-
logger.setNamespace("Canary");
25-
logger.setDimensions(
26-
DimensionSet.of(
27-
"Runtime", "Java8",
28-
"Platform", "ECS",
29-
"Agent", "CloudWatchAgent",
30-
"Version", version));
27+
28+
try {
29+
logger.setNamespace("Canary");
30+
logger.setDimensions(
31+
DimensionSet.of(
32+
"Runtime", "Java8",
33+
"Platform", "ECS",
34+
"Agent", "CloudWatchAgent",
35+
"Version", version));
36+
} catch (InvalidNamespaceException | InvalidDimensionException e) {
37+
System.out.println(e);
38+
}
3139

3240
MemoryMXBean runtimeMXBean = ManagementFactory.getMemoryMXBean();
3341
long heapTotal = Runtime.getRuntime().totalMemory();
3442
long heapUsed = runtimeMXBean.getHeapMemoryUsage().getUsed();
3543
long nonHeapUsed = runtimeMXBean.getNonHeapMemoryUsage().getUsed();
3644

37-
logger.putMetric("Invoke", 1, Unit.COUNT);
38-
logger.putMetric("Memory.HeapTotal", heapTotal, Unit.COUNT);
39-
logger.putMetric("Memory.HeapUsed", heapUsed, Unit.COUNT);
40-
logger.putMetric("Memory.JVMUsedTotal", heapUsed + nonHeapUsed, Unit.COUNT);
45+
try {
46+
logger.putMetric("Invoke", 1, Unit.COUNT);
47+
logger.putMetric("Memory.HeapTotal", heapTotal, Unit.COUNT);
48+
logger.putMetric("Memory.HeapUsed", heapUsed, Unit.COUNT);
49+
logger.putMetric("Memory.JVMUsedTotal", heapUsed + nonHeapUsed, Unit.COUNT);
50+
} catch (InvalidMetricException e) {
51+
System.out.println(e);
52+
}
4153

4254
logger.flush();
4355
}

examples/agent/src/main/java/agent/App.java

+10-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import software.amazon.cloudwatchlogs.emf.config.EnvironmentConfigurationProvider;
44
import software.amazon.cloudwatchlogs.emf.environment.DefaultEnvironment;
55
import software.amazon.cloudwatchlogs.emf.environment.Environment;
6+
import software.amazon.cloudwatchlogs.emf.exception.InvalidDimensionException;
7+
import software.amazon.cloudwatchlogs.emf.exception.InvalidMetricException;
68
import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger;
79
import software.amazon.cloudwatchlogs.emf.model.DimensionSet;
810
import software.amazon.cloudwatchlogs.emf.model.Unit;
@@ -13,13 +15,17 @@ public class App {
1315

1416
public static void main(String[] args) {
1517
DefaultEnvironment environment = new DefaultEnvironment(EnvironmentConfigurationProvider.getConfig());
16-
emitMetric(environment);
17-
emitMetric(environment);
18-
emitMetric(environment);
18+
try {
19+
emitMetric(environment);
20+
emitMetric(environment);
21+
emitMetric(environment);
22+
} catch (InvalidMetricException | InvalidDimensionException e) {
23+
System.out.println(e);
24+
}
1925
environment.getSink().shutdown().orTimeout(360_000L, TimeUnit.MILLISECONDS);
2026
}
2127

22-
private static void emitMetric(Environment environment) {
28+
private static void emitMetric(Environment environment) throws InvalidDimensionException, InvalidMetricException {
2329
MetricsLogger logger = new MetricsLogger(environment);
2430
logger.setDimensions(DimensionSet.of("Operation", "Agent"));
2531
logger.putMetric("ExampleMetric", 100, Unit.MILLISECONDS);

examples/ecs-firelens/src/main/java/App.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import software.amazon.cloudwatchlogs.emf.environment.ECSEnvironment;
2222
import software.amazon.cloudwatchlogs.emf.environment.Environment;
2323
import software.amazon.cloudwatchlogs.emf.environment.EnvironmentProvider;
24+
import software.amazon.cloudwatchlogs.emf.exception.InvalidMetricException;
2425
import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger;
2526
import software.amazon.cloudwatchlogs.emf.model.Unit;
2627
import sun.misc.Signal;
@@ -69,7 +70,11 @@ public void handle(HttpExchange he) throws IOException {
6970
MetricsLogger logger = new MetricsLogger();
7071
logger.putProperty("Method", he.getRequestMethod());
7172
logger.putProperty("Url", he.getRequestURI());
72-
logger.putMetric("ProcessingTime", System.currentTimeMillis() - time, Unit.MILLISECONDS);
73+
try {
74+
logger.putMetric("ProcessingTime", System.currentTimeMillis() - time, Unit.MILLISECONDS);
75+
} catch (InvalidMetricException e) {
76+
System.out.println(e);
77+
}
7378
logger.flush();
7479
System.out.println(new EnvironmentProvider().resolveEnvironment().join().getClass().getName());
7580

examples/lambda/src/main/java/Handler.java

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import com.amazonaws.services.lambda.runtime.Context;
22
import com.amazonaws.services.lambda.runtime.RequestHandler;
3+
import software.amazon.cloudwatchlogs.emf.exception.InvalidDimensionException;
4+
import software.amazon.cloudwatchlogs.emf.exception.InvalidMetricException;
35
import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger;
46
import software.amazon.cloudwatchlogs.emf.model.DimensionSet;
57
import software.amazon.cloudwatchlogs.emf.model.Unit;
@@ -14,8 +16,13 @@ public String handleRequest(Map<String, String> event, Context context) {
1416
String response = "200 OK";
1517
MetricsLogger logger = new MetricsLogger();
1618

17-
logger.putDimensions(DimensionSet.of("Service", "Aggregator"));
18-
logger.putMetric("ProcessingLatency", 100, Unit.MILLISECONDS);
19+
try {
20+
logger.putDimensions(DimensionSet.of("Service", "Aggregator"));
21+
logger.putMetric("ProcessingLatency", 100, Unit.MILLISECONDS);
22+
} catch (InvalidDimensionException | InvalidMetricException e) {
23+
System.out.println(e);
24+
}
25+
1926
logger.putProperty("AccountId", "123456789");
2027
logger.putProperty("RequestId", "422b1569-16f6-4a03-b8f0-fe3fd9b100f8");
2128
logger.putProperty("DeviceId", "61270781-c6ac-46f1-baf7-22c808af8162");

src/integration-test/java/software/amazon/cloudwatchlogs/emf/MetricsLoggerIntegrationTest.java

+11-7
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
import software.amazon.cloudwatchlogs.emf.config.EnvironmentConfigurationProvider;
3434
import software.amazon.cloudwatchlogs.emf.environment.DefaultEnvironment;
3535
import software.amazon.cloudwatchlogs.emf.environment.Environment;
36+
import software.amazon.cloudwatchlogs.emf.exception.InvalidDimensionException;
37+
import software.amazon.cloudwatchlogs.emf.exception.InvalidMetricException;
3638
import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger;
3739
import software.amazon.cloudwatchlogs.emf.model.DimensionSet;
3840
import software.amazon.cloudwatchlogs.emf.model.Unit;
@@ -48,6 +50,8 @@ public class MetricsLoggerIntegrationTest {
4850
private DimensionSet dimensions = DimensionSet.of(dimensionName, dimensionValue);
4951
private EMFIntegrationTestHelper testHelper = new EMFIntegrationTestHelper();
5052

53+
public MetricsLoggerIntegrationTest() throws InvalidDimensionException {}
54+
5155
@Before
5256
public void setUp() {
5357
config.setServiceName(serviceName);
@@ -56,7 +60,7 @@ public void setUp() {
5660
}
5761

5862
@Test(timeout = 120_000)
59-
public void testSingleFlushOverTCP() throws InterruptedException {
63+
public void testSingleFlushOverTCP() throws InterruptedException, InvalidMetricException {
6064
Environment env = new DefaultEnvironment(EnvironmentConfigurationProvider.getConfig());
6165
String metricName = "TCP-SingleFlush";
6266
int expectedSamples = 1;
@@ -69,7 +73,7 @@ public void testSingleFlushOverTCP() throws InterruptedException {
6973
}
7074

7175
@Test(timeout = 300_000)
72-
public void testMultipleFlushesOverTCP() throws InterruptedException {
76+
public void testMultipleFlushesOverTCP() throws InterruptedException, InvalidMetricException {
7377
Environment env = new DefaultEnvironment(EnvironmentConfigurationProvider.getConfig());
7478
String metricName = "TCP-MultipleFlushes";
7579
int expectedSamples = 3;
@@ -85,7 +89,7 @@ public void testMultipleFlushesOverTCP() throws InterruptedException {
8589
}
8690

8791
@Test(timeout = 120_000)
88-
public void testSingleFlushOverUDP() throws InterruptedException {
92+
public void testSingleFlushOverUDP() throws InterruptedException, InvalidMetricException {
8993
Environment env = new DefaultEnvironment(EnvironmentConfigurationProvider.getConfig());
9094
String metricName = "UDP-SingleFlush";
9195
int expectedSamples = 1;
@@ -98,7 +102,7 @@ public void testSingleFlushOverUDP() throws InterruptedException {
98102
}
99103

100104
@Test(timeout = 300_000)
101-
public void testMultipleFlushOverUDP() throws InterruptedException {
105+
public void testMultipleFlushOverUDP() throws InterruptedException, InvalidMetricException {
102106
Environment env = new DefaultEnvironment(EnvironmentConfigurationProvider.getConfig());
103107
String metricName = "UDP-MultipleFlush";
104108
int expectedSamples = 3;
@@ -113,7 +117,7 @@ public void testMultipleFlushOverUDP() throws InterruptedException {
113117
assertTrue(retryUntilSucceed(() -> buildRequest(metricName), expectedSamples));
114118
}
115119

116-
private void logMetric(Environment env, String metricName) {
120+
private void logMetric(Environment env, String metricName) throws InvalidMetricException {
117121
MetricsLogger logger = new MetricsLogger(env);
118122
logger.putDimensions(dimensions);
119123
logger.putMetric(metricName, 100, Unit.MILLISECONDS);
@@ -130,7 +134,7 @@ private String getLocalHost() {
130134

131135
private GetMetricStatisticsRequest buildRequest(String metricName) {
132136
Instant now = Instant.now();
133-
List<Dimension> dimensions =
137+
List<Dimension> dims =
134138
Arrays.asList(
135139
getDimension("ServiceName", serviceName),
136140
getDimension("ServiceType", serviceType),
@@ -140,7 +144,7 @@ private GetMetricStatisticsRequest buildRequest(String metricName) {
140144
return GetMetricStatisticsRequest.builder()
141145
.namespace("aws-embedded-metrics")
142146
.metricName(metricName)
143-
.dimensions(dimensions)
147+
.dimensions(dims)
144148
.period(60)
145149
.startTime(now.minusMillis(5000))
146150
.endTime(now)

src/main/java/software/amazon/cloudwatchlogs/emf/Constants.java

+11-2
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,24 @@
1616

1717
package software.amazon.cloudwatchlogs.emf;
1818

19+
import java.util.concurrent.TimeUnit;
20+
1921
public class Constants {
22+
public static final int MAX_DIMENSION_SET_SIZE = 30;
23+
public static final short MAX_DIMENSION_NAME_LENGTH = 250;
24+
public static final short MAX_DIMENSION_VALUE_LENGTH = 1024;
25+
public static final short MAX_METRIC_NAME_LENGTH = 1024;
26+
public static final short MAX_NAMESPACE_LENGTH = 256;
27+
public static final String VALID_NAMESPACE_REGEX = "^[a-zA-Z0-9._#:/-]+$";
28+
public static final long MAX_TIMESTAMP_PAST_AGE_SECONDS = TimeUnit.DAYS.toSeconds(14);
29+
public static final long MAX_TIMESTAMP_FUTURE_AGE_SECONDS = TimeUnit.HOURS.toSeconds(2);
30+
2031
public static final int DEFAULT_AGENT_PORT = 25888;
2132

2233
public static final String UNKNOWN = "Unknown";
2334

2435
public static final int MAX_METRICS_PER_EVENT = 100;
2536

26-
public static final int MAX_DIMENSION_SET_SIZE = 30;
27-
2837
public static final int MAX_DATAPOINTS_PER_METRIC = 100;
2938

3039
/**

0 commit comments

Comments
 (0)