Skip to content

Commit b89607f

Browse files
authored
Add dogstatsd support (#599)
1 parent 5009ae8 commit b89607f

File tree

76 files changed

+41481
-10
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+41481
-10
lines changed

.git-blame-ignore-revs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
a8ec45c8ea4ba559247b654d01b0d35b21a68865
22
33f3224cb40e3fa8c56ddb88962e3a4e9319685d
33
430a1a0a5dd4efe78e21526c37bec9dbce036401
4-
5-
6-
7-
8-
d0129c1095216d5c830900c8a6223ef5d4274de1
4+
d0129c1095216d5c830900c8a6223ef5d4274de1

allowed-licenses.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,18 @@
4949
{
5050
"moduleLicense": null,
5151
"moduleName": "org.jetbrains.kotlin:kotlin-stdlib-common"
52+
},
53+
{
54+
"moduleLicense": "Eclipse Public License - v 2.0",
55+
"moduleName": "com.github.jnr:jnr-posix"
56+
},
57+
{
58+
"moduleLicense": "GNU General Public License Version 2",
59+
"moduleName": "com.github.jnr:jnr-posix"
60+
},
61+
{
62+
"moduleLicense": "GNU LESSER GENERAL PUBLIC LICENSE, Version 3",
63+
"moduleName": "com.github.jnr:jnr-posix"
5264
}
5365
]
5466
}

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ tasks.register("writeVersionToExamples") {
357357
include(name: 'examples/spring-newrelic/build.gradle')
358358
include(name: 'examples/spring-otel/build.gradle')
359359
include(name: 'examples/spring-datadog/build.gradle')
360+
include(name: 'examples/spring-datadog-statsd/build.gradle')
360361
}
361362
}
362363
ant.replaceregexp(match: '<commercetools.version>.+</commercetools.version>', replace: "<commercetools.version>${globalVersion}</commercetools.version>", flags:'g', byline:true) {

commercetools/commercetools-monitoring-datadog/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
dependencies {
22
api project(":rmf:rmf-java-base")
3+
implementation "com.datadoghq:java-dogstatsd-client:4.3.0"
34
implementation "com.datadoghq:datadog-api-client:2.23.0"
45

56
testImplementation project(":commercetools:commercetools-sdk-java-api")

commercetools/commercetools-monitoring-datadog/src/main/java/com/commercetools/monitoring/datadog/DatadogMiddleware.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
/**
2020
* <p>The DatadogTelemetry middleware can be used to report outgoing request to commercetools to Datadog.
21+
* This middleware uses Datadog API to submit telemetry data.
2122
* It can be registered as TelemetryMiddleware to the {@link io.vrap.rmf.base.client.ClientBuilder#withTelemetryMiddleware(TelemetryMiddleware) ClientBuilder}
2223
* or the ApiRootBuilder.</p>
2324
*

commercetools/commercetools-monitoring-datadog/src/main/java/com/commercetools/monitoring/datadog/DatadogResponseSerializer.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
import io.vrap.rmf.base.client.ApiHttpResponse;
1717
import io.vrap.rmf.base.client.ResponseSerializer;
1818

19+
/**
20+
* This serializer uses API to submit metrics to datadog.
21+
* If you are using dogstatsd, use {@link com.commercetools.monitoring.datadog.statsd.DatadogResponseSerializer} to submit metrics to datadog with statsd.
22+
*/
1923
public class DatadogResponseSerializer implements ResponseSerializer {
2024
private final ResponseSerializer serializer;
2125

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
2+
package com.commercetools.monitoring.datadog.statsd;
3+
4+
import static com.commercetools.monitoring.datadog.DatadogInfo.*;
5+
import static java.lang.String.format;
6+
7+
import java.time.Duration;
8+
import java.time.Instant;
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
import java.util.concurrent.CompletableFuture;
12+
import java.util.function.Function;
13+
14+
import com.timgroup.statsd.StatsDClient;
15+
16+
import io.vrap.rmf.base.client.ApiHttpRequest;
17+
import io.vrap.rmf.base.client.ApiHttpResponse;
18+
import io.vrap.rmf.base.client.http.TelemetryMiddleware;
19+
20+
/**
21+
* <p>The DatadogTelemetry middleware can be used to report outgoing request to commercetools to Datadog.
22+
* This middleware uses Datadog StatsD protocol to submit telemetry data.
23+
* It can be registered as TelemetryMiddleware to the {@link io.vrap.rmf.base.client.ClientBuilder#withTelemetryMiddleware(TelemetryMiddleware) ClientBuilder}
24+
* or the ApiRootBuilder.</p>
25+
*
26+
* {@include.example example.DatadogApiRootBuilderTest#addDatadogTelemetry()}
27+
*
28+
* The middleware adds the following metrics to Datadog:
29+
* <ul>
30+
* <li>commercetools.client.duration: The duration of the request in milliseconds</li>
31+
* <li>commercetools.client.total_requests: The total number of requests</li>
32+
* <li>commercetools.client.error_requests: The total number of requests with a status code greater or equal to 400</li>
33+
* </ul>
34+
*
35+
* <p>The metrics are enriched with the response status code, server address, port and request method.</p>
36+
*
37+
* <p>See also the <a href="https://github.com/commercetools/commercetools-sdk-java-v2/tree/main/examples/spring-datadog-statsd">Spring MVC example application</a> in the examples folder for further details.</p>
38+
*/
39+
public class DatadogMiddleware implements TelemetryMiddleware {
40+
41+
private final StatsDClient statsDClient;
42+
43+
public DatadogMiddleware(final StatsDClient datadogStatsDClient) {
44+
this.statsDClient = datadogStatsDClient;
45+
}
46+
47+
@Override
48+
public CompletableFuture<ApiHttpResponse<byte[]>> invoke(ApiHttpRequest request,
49+
Function<ApiHttpRequest, CompletableFuture<ApiHttpResponse<byte[]>>> next) {
50+
final Instant start = Instant.now();
51+
52+
return next.apply(request).thenApply(response -> {
53+
final List<String> tags = new ArrayList<>(4);
54+
tags.add(format("%s:%s", HTTP_RESPONSE_STATUS_CODE, response.getStatusCode()));
55+
tags.add(format("%s:%s", HTTP_REQUEST_METHOD, request.getMethod().name()));
56+
tags.add(format("%s:%s", SERVER_ADDRESS, request.getUri().getHost()));
57+
if (request.getUri().getPort() > 0) {
58+
tags.add(format("%s:%s", SERVER_PORT, request.getUri().getPort()));
59+
}
60+
61+
this.statsDClient.recordHistogramValue(PREFIX + "." + CLIENT_DURATION,
62+
Duration.between(start, Instant.now()).toMillis(), tags.toArray(new String[0]));
63+
64+
this.statsDClient.incrementCounter(PREFIX + "." + CLIENT_REQUEST_TOTAL, tags.toArray(new String[0]));
65+
if (response.getStatusCode() >= 400) {
66+
this.statsDClient.incrementCounter(PREFIX + "." + CLIENT_REQUEST_ERROR, tags.toArray(new String[0]));
67+
}
68+
return response;
69+
});
70+
}
71+
72+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
2+
package com.commercetools.monitoring.datadog.statsd;
3+
4+
import static com.commercetools.monitoring.datadog.DatadogInfo.*;
5+
import static java.lang.String.format;
6+
7+
import java.time.Duration;
8+
import java.time.Instant;
9+
10+
import com.fasterxml.jackson.core.JsonProcessingException;
11+
import com.fasterxml.jackson.core.type.TypeReference;
12+
import com.fasterxml.jackson.databind.JavaType;
13+
import com.timgroup.statsd.StatsDClient;
14+
15+
import io.vrap.rmf.base.client.ApiHttpResponse;
16+
import io.vrap.rmf.base.client.ResponseSerializer;
17+
18+
/**
19+
* This serializer uses dogstatsd library to submit metrics to datadog.
20+
* If you are not using statsd, use {@link com.commercetools.monitoring.datadog.DatadogResponseSerializer} to submit metrics to datadog with API.
21+
*/
22+
public class DatadogResponseSerializer implements ResponseSerializer {
23+
private final ResponseSerializer serializer;
24+
25+
private final StatsDClient statsDClient;
26+
27+
public DatadogResponseSerializer(final ResponseSerializer serializer, final StatsDClient datadogStatsDClient) {
28+
this.serializer = serializer;
29+
this.statsDClient = datadogStatsDClient;
30+
}
31+
32+
@Override
33+
public <O> ApiHttpResponse<O> convertResponse(ApiHttpResponse<byte[]> response, Class<O> outputType) {
34+
Instant start = Instant.now();
35+
ApiHttpResponse<O> result = serializer.convertResponse(response, outputType);
36+
long durationInMillis = Duration.between(start, Instant.now()).toMillis();
37+
this.statsDClient.recordHistogramValue(PREFIX + "." + JSON_SERIALIZATION, durationInMillis,
38+
format("%s:%s", RESPONSE_BODY_TYPE, outputType.getCanonicalName()));
39+
return result;
40+
}
41+
42+
@Override
43+
public <O> ApiHttpResponse<O> convertResponse(ApiHttpResponse<byte[]> response, JavaType outputType) {
44+
Instant start = Instant.now();
45+
ApiHttpResponse<O> result = serializer.convertResponse(response, outputType);
46+
long durationInMillis = Duration.between(start, Instant.now()).toMillis();
47+
this.statsDClient.recordHistogramValue(PREFIX + "." + JSON_SERIALIZATION, durationInMillis,
48+
format("%s:%s", RESPONSE_BODY_TYPE, outputType.toString()));
49+
return result;
50+
}
51+
52+
@Override
53+
public <O> ApiHttpResponse<O> convertResponse(ApiHttpResponse<byte[]> response, TypeReference<O> outputType) {
54+
Instant start = Instant.now();
55+
ApiHttpResponse<O> result = serializer.convertResponse(response, outputType);
56+
long durationInMillis = Duration.between(start, Instant.now()).toMillis();
57+
this.statsDClient.recordHistogramValue(PREFIX + "." + JSON_SERIALIZATION, durationInMillis,
58+
format("%s:%s", RESPONSE_BODY_TYPE, outputType.getType().getTypeName()));
59+
return result;
60+
}
61+
62+
@Override
63+
public byte[] toJsonByteArray(Object value) throws JsonProcessingException {
64+
Instant start = Instant.now();
65+
byte[] result = serializer.toJsonByteArray(value);
66+
long durationInMillis = Duration.between(start, Instant.now()).toMillis();
67+
this.statsDClient.recordHistogramValue(PREFIX + "." + JSON_DESERIALIZATION, durationInMillis,
68+
format("%s:%s", REQUEST_BODY_TYPE, value.getClass().getCanonicalName()));
69+
return result;
70+
}
71+
72+
}

commercetools/commercetools-monitoring-datadog/src/test/java/example/DatadogApiRootBuilderTest.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33

44
import com.commercetools.api.defaultconfig.ApiRootBuilder;
55
import com.commercetools.api.defaultconfig.ServiceRegion;
6-
import com.commercetools.monitoring.datadog.DatadogMiddleware;
7-
import com.commercetools.monitoring.datadog.DatadogResponseSerializer;
8-
import com.datadog.api.client.ApiClient;
6+
import com.commercetools.monitoring.datadog.statsd.DatadogMiddleware;
7+
import com.commercetools.monitoring.datadog.statsd.DatadogResponseSerializer;
8+
import com.timgroup.statsd.NonBlockingStatsDClientBuilder;
99

1010
import io.vrap.rmf.base.client.ApiHttpClient;
1111
import io.vrap.rmf.base.client.ResponseSerializer;
@@ -15,14 +15,15 @@ public class DatadogApiRootBuilderTest {
1515
public void addDatadogTelemetry() {
1616
ApiHttpClient client = ApiRootBuilder.of()
1717
.defaultClient(ServiceRegion.GCP_EUROPE_WEST1.getApiUrl())
18-
.withTelemetryMiddleware(new DatadogMiddleware(ApiClient.getDefaultApiClient()))
18+
.withTelemetryMiddleware(new DatadogMiddleware(new NonBlockingStatsDClientBuilder().build()))
1919
.buildClient();
2020
}
2121

2222
public void addSerializer() {
2323
ApiHttpClient client = ApiRootBuilder.of()
2424
.defaultClient(ServiceRegion.GCP_EUROPE_WEST1.getApiUrl())
25-
.withSerializer(new DatadogResponseSerializer(ResponseSerializer.of(), ApiClient.getDefaultApiClient()))
25+
.withSerializer(new DatadogResponseSerializer(ResponseSerializer.of(),
26+
new NonBlockingStatsDClientBuilder().build()))
2627
.buildClient();
2728
}
2829
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
HELP.md
2+
.gradle
3+
build/
4+
!gradle/wrapper/gradle-wrapper.jar
5+
!**/src/main/**/build/
6+
!**/src/test/**/build/
7+
**/application.properties
8+
9+
### STS ###
10+
.apt_generated
11+
.classpath
12+
.factorypath
13+
.project
14+
.settings
15+
.springBeans
16+
.sts4-cache
17+
bin/
18+
!**/src/main/**/bin/
19+
!**/src/test/**/bin/
20+
21+
### IntelliJ IDEA ###
22+
.idea
23+
*.iws
24+
*.iml
25+
*.ipr
26+
out/
27+
!**/src/main/**/out/
28+
!**/src/test/**/out/
29+
30+
### NetBeans ###
31+
/nbproject/private/
32+
/nbbuild/
33+
/dist/
34+
/nbdist/
35+
/.nb-gradle/
36+
37+
### VS Code ###
38+
.vscode/

0 commit comments

Comments
 (0)