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

+1-5
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

+12
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

+1
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

+1
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

+1
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

+4
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

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+
}
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

+6-5
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
}
+38
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/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
17.0
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Spring MVC example app
2+
3+
Example to show how Java SDK can be used in a Spring Boot application with Datadog monitoring and dogstatsd protocol. This example uses [java-dogstatsd-client](https://github.com/DataDog/java-dogstatsd-client).
4+
5+
## Requirements
6+
7+
- A Composable Commerce Project with a configured [API Client](https://docs.commercetools.com/tutorials/getting-started#creating-an-api-client).
8+
Necessary scopes: `view_published_products`, `manage_orders`
9+
- Your Project must have existing products containing variants with SKUs, and at least one customer, the storefront search endpoint must be active.
10+
- If your Project is currently empty, you can install the [SUNRISE sample data](https://docs.commercetools.com/sdk/sunrise-data).
11+
12+
## Installation
13+
14+
1. Clone/Download the example folder.
15+
2. Navigate to the path `spring-datadog-statsd/`.
16+
3. Register the client credentials in environment variables:
17+
`CTP_CLIENT_ID`, `CTP_CLIENT_SECRET`, `CTP_PROJECT_KEY`
18+
4. Add Datadog API key to environment variable as `DD_API_KEY`. If necessary, add `DD_SITE` to environmental variable as well.
19+
20+
## Using the Spring MVC Example app
21+
22+
### Start the server
23+
24+
1. Open a new Terminal.
25+
2. Run `./gradlew bootRun`.
26+
3. The server starts.
27+
28+
### Navigate the application
29+
30+
1. Open a new browser window/tab
31+
2. Navigate to [http://localhost:8080/p](http://localhost:8080/p) and a list of products should appear.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
plugins {
2+
id 'java'
3+
id 'org.springframework.boot' version '3.0.4'
4+
id 'io.spring.dependency-management' version '1.1.0'
5+
}
6+
7+
group = 'com.commercetools.sdk.examples'
8+
version = '0.0.1-SNAPSHOT'
9+
10+
java {
11+
toolchain {
12+
languageVersion = JavaLanguageVersion.of(17)
13+
}
14+
}
15+
16+
repositories {
17+
mavenLocal()
18+
mavenCentral()
19+
}
20+
21+
ext {
22+
versions = [
23+
commercetools: "17.7.0",
24+
]
25+
}
26+
27+
dependencies {
28+
implementation "com.commercetools.sdk:commercetools-sdk-java-api:${versions.commercetools}"
29+
implementation "com.commercetools.sdk:commercetools-apachehttp-client:${versions.commercetools}"
30+
implementation "com.commercetools.sdk:commercetools-monitoring-datadog:${versions.commercetools}"
31+
implementation 'org.springframework.boot:spring-boot-starter-actuator'
32+
implementation 'org.springframework.boot:spring-boot-starter-security'
33+
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
34+
implementation 'org.springframework.boot:spring-boot-starter-web'
35+
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
36+
implementation 'com.datadoghq:java-dogstatsd-client:4.3.0'
37+
testImplementation 'org.springframework.boot:spring-boot-starter-test'
38+
testImplementation 'org.springframework.security:spring-security-test'
39+
developmentOnly "org.springframework.boot:spring-boot-devtools"
40+
}
41+
42+
tasks.named('test') {
43+
useJUnitPlatform()
44+
}
45+
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
distributionBase=GRADLE_USER_HOME
2+
distributionPath=wrapper/dists
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
4+
networkTimeout=10000
5+
validateDistributionUrl=true
6+
zipStoreBase=GRADLE_USER_HOME
7+
zipStorePath=wrapper/dists

0 commit comments

Comments
 (0)