Skip to content

Commit

Permalink
Add dogstatsd support (#599)
Browse files Browse the repository at this point in the history
  • Loading branch information
lojzatran authored Apr 8, 2024
1 parent 5009ae8 commit b89607f
Show file tree
Hide file tree
Showing 76 changed files with 41,481 additions and 10 deletions.
6 changes: 1 addition & 5 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
a8ec45c8ea4ba559247b654d01b0d35b21a68865
33f3224cb40e3fa8c56ddb88962e3a4e9319685d
430a1a0a5dd4efe78e21526c37bec9dbce036401




d0129c1095216d5c830900c8a6223ef5d4274de1
d0129c1095216d5c830900c8a6223ef5d4274de1
12 changes: 12 additions & 0 deletions allowed-licenses.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@
{
"moduleLicense": null,
"moduleName": "org.jetbrains.kotlin:kotlin-stdlib-common"
},
{
"moduleLicense": "Eclipse Public License - v 2.0",
"moduleName": "com.github.jnr:jnr-posix"
},
{
"moduleLicense": "GNU General Public License Version 2",
"moduleName": "com.github.jnr:jnr-posix"
},
{
"moduleLicense": "GNU LESSER GENERAL PUBLIC LICENSE, Version 3",
"moduleName": "com.github.jnr:jnr-posix"
}
]
}
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ tasks.register("writeVersionToExamples") {
include(name: 'examples/spring-newrelic/build.gradle')
include(name: 'examples/spring-otel/build.gradle')
include(name: 'examples/spring-datadog/build.gradle')
include(name: 'examples/spring-datadog-statsd/build.gradle')
}
}
ant.replaceregexp(match: '<commercetools.version>.+</commercetools.version>', replace: "<commercetools.version>${globalVersion}</commercetools.version>", flags:'g', byline:true) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
dependencies {
api project(":rmf:rmf-java-base")
implementation "com.datadoghq:java-dogstatsd-client:4.3.0"
implementation "com.datadoghq:datadog-api-client:2.23.0"

testImplementation project(":commercetools:commercetools-sdk-java-api")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

/**
* <p>The DatadogTelemetry middleware can be used to report outgoing request to commercetools to Datadog.
* This middleware uses Datadog API to submit telemetry data.
* It can be registered as TelemetryMiddleware to the {@link io.vrap.rmf.base.client.ClientBuilder#withTelemetryMiddleware(TelemetryMiddleware) ClientBuilder}
* or the ApiRootBuilder.</p>
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
import io.vrap.rmf.base.client.ApiHttpResponse;
import io.vrap.rmf.base.client.ResponseSerializer;

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@

package com.commercetools.monitoring.datadog.statsd;

import static com.commercetools.monitoring.datadog.DatadogInfo.*;
import static java.lang.String.format;

import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;

import com.timgroup.statsd.StatsDClient;

import io.vrap.rmf.base.client.ApiHttpRequest;
import io.vrap.rmf.base.client.ApiHttpResponse;
import io.vrap.rmf.base.client.http.TelemetryMiddleware;

/**
* <p>The DatadogTelemetry middleware can be used to report outgoing request to commercetools to Datadog.
* This middleware uses Datadog StatsD protocol to submit telemetry data.
* It can be registered as TelemetryMiddleware to the {@link io.vrap.rmf.base.client.ClientBuilder#withTelemetryMiddleware(TelemetryMiddleware) ClientBuilder}
* or the ApiRootBuilder.</p>
*
* {@include.example example.DatadogApiRootBuilderTest#addDatadogTelemetry()}
*
* The middleware adds the following metrics to Datadog:
* <ul>
* <li>commercetools.client.duration: The duration of the request in milliseconds</li>
* <li>commercetools.client.total_requests: The total number of requests</li>
* <li>commercetools.client.error_requests: The total number of requests with a status code greater or equal to 400</li>
* </ul>
*
* <p>The metrics are enriched with the response status code, server address, port and request method.</p>
*
* <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>
*/
public class DatadogMiddleware implements TelemetryMiddleware {

private final StatsDClient statsDClient;

public DatadogMiddleware(final StatsDClient datadogStatsDClient) {
this.statsDClient = datadogStatsDClient;
}

@Override
public CompletableFuture<ApiHttpResponse<byte[]>> invoke(ApiHttpRequest request,
Function<ApiHttpRequest, CompletableFuture<ApiHttpResponse<byte[]>>> next) {
final Instant start = Instant.now();

return next.apply(request).thenApply(response -> {
final List<String> tags = new ArrayList<>(4);
tags.add(format("%s:%s", HTTP_RESPONSE_STATUS_CODE, response.getStatusCode()));
tags.add(format("%s:%s", HTTP_REQUEST_METHOD, request.getMethod().name()));
tags.add(format("%s:%s", SERVER_ADDRESS, request.getUri().getHost()));
if (request.getUri().getPort() > 0) {
tags.add(format("%s:%s", SERVER_PORT, request.getUri().getPort()));
}

this.statsDClient.recordHistogramValue(PREFIX + "." + CLIENT_DURATION,
Duration.between(start, Instant.now()).toMillis(), tags.toArray(new String[0]));

this.statsDClient.incrementCounter(PREFIX + "." + CLIENT_REQUEST_TOTAL, tags.toArray(new String[0]));
if (response.getStatusCode() >= 400) {
this.statsDClient.incrementCounter(PREFIX + "." + CLIENT_REQUEST_ERROR, tags.toArray(new String[0]));
}
return response;
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@

package com.commercetools.monitoring.datadog.statsd;

import static com.commercetools.monitoring.datadog.DatadogInfo.*;
import static java.lang.String.format;

import java.time.Duration;
import java.time.Instant;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.timgroup.statsd.StatsDClient;

import io.vrap.rmf.base.client.ApiHttpResponse;
import io.vrap.rmf.base.client.ResponseSerializer;

/**
* This serializer uses dogstatsd library to submit metrics to datadog.
* If you are not using statsd, use {@link com.commercetools.monitoring.datadog.DatadogResponseSerializer} to submit metrics to datadog with API.
*/
public class DatadogResponseSerializer implements ResponseSerializer {
private final ResponseSerializer serializer;

private final StatsDClient statsDClient;

public DatadogResponseSerializer(final ResponseSerializer serializer, final StatsDClient datadogStatsDClient) {
this.serializer = serializer;
this.statsDClient = datadogStatsDClient;
}

@Override
public <O> ApiHttpResponse<O> convertResponse(ApiHttpResponse<byte[]> response, Class<O> outputType) {
Instant start = Instant.now();
ApiHttpResponse<O> result = serializer.convertResponse(response, outputType);
long durationInMillis = Duration.between(start, Instant.now()).toMillis();
this.statsDClient.recordHistogramValue(PREFIX + "." + JSON_SERIALIZATION, durationInMillis,
format("%s:%s", RESPONSE_BODY_TYPE, outputType.getCanonicalName()));
return result;
}

@Override
public <O> ApiHttpResponse<O> convertResponse(ApiHttpResponse<byte[]> response, JavaType outputType) {
Instant start = Instant.now();
ApiHttpResponse<O> result = serializer.convertResponse(response, outputType);
long durationInMillis = Duration.between(start, Instant.now()).toMillis();
this.statsDClient.recordHistogramValue(PREFIX + "." + JSON_SERIALIZATION, durationInMillis,
format("%s:%s", RESPONSE_BODY_TYPE, outputType.toString()));
return result;
}

@Override
public <O> ApiHttpResponse<O> convertResponse(ApiHttpResponse<byte[]> response, TypeReference<O> outputType) {
Instant start = Instant.now();
ApiHttpResponse<O> result = serializer.convertResponse(response, outputType);
long durationInMillis = Duration.between(start, Instant.now()).toMillis();
this.statsDClient.recordHistogramValue(PREFIX + "." + JSON_SERIALIZATION, durationInMillis,
format("%s:%s", RESPONSE_BODY_TYPE, outputType.getType().getTypeName()));
return result;
}

@Override
public byte[] toJsonByteArray(Object value) throws JsonProcessingException {
Instant start = Instant.now();
byte[] result = serializer.toJsonByteArray(value);
long durationInMillis = Duration.between(start, Instant.now()).toMillis();
this.statsDClient.recordHistogramValue(PREFIX + "." + JSON_DESERIALIZATION, durationInMillis,
format("%s:%s", REQUEST_BODY_TYPE, value.getClass().getCanonicalName()));
return result;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

import com.commercetools.api.defaultconfig.ApiRootBuilder;
import com.commercetools.api.defaultconfig.ServiceRegion;
import com.commercetools.monitoring.datadog.DatadogMiddleware;
import com.commercetools.monitoring.datadog.DatadogResponseSerializer;
import com.datadog.api.client.ApiClient;
import com.commercetools.monitoring.datadog.statsd.DatadogMiddleware;
import com.commercetools.monitoring.datadog.statsd.DatadogResponseSerializer;
import com.timgroup.statsd.NonBlockingStatsDClientBuilder;

import io.vrap.rmf.base.client.ApiHttpClient;
import io.vrap.rmf.base.client.ResponseSerializer;
Expand All @@ -15,14 +15,15 @@ public class DatadogApiRootBuilderTest {
public void addDatadogTelemetry() {
ApiHttpClient client = ApiRootBuilder.of()
.defaultClient(ServiceRegion.GCP_EUROPE_WEST1.getApiUrl())
.withTelemetryMiddleware(new DatadogMiddleware(ApiClient.getDefaultApiClient()))
.withTelemetryMiddleware(new DatadogMiddleware(new NonBlockingStatsDClientBuilder().build()))
.buildClient();
}

public void addSerializer() {
ApiHttpClient client = ApiRootBuilder.of()
.defaultClient(ServiceRegion.GCP_EUROPE_WEST1.getApiUrl())
.withSerializer(new DatadogResponseSerializer(ResponseSerializer.of(), ApiClient.getDefaultApiClient()))
.withSerializer(new DatadogResponseSerializer(ResponseSerializer.of(),
new NonBlockingStatsDClientBuilder().build()))
.buildClient();
}
}
38 changes: 38 additions & 0 deletions examples/spring-datadog-statsd/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
**/application.properties

### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/

### VS Code ###
.vscode/
1 change: 1 addition & 0 deletions examples/spring-datadog-statsd/.java-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
17.0
31 changes: 31 additions & 0 deletions examples/spring-datadog-statsd/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Spring MVC example app

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).

## Requirements

- A Composable Commerce Project with a configured [API Client](https://docs.commercetools.com/tutorials/getting-started#creating-an-api-client).
Necessary scopes: `view_published_products`, `manage_orders`
- Your Project must have existing products containing variants with SKUs, and at least one customer, the storefront search endpoint must be active.
- If your Project is currently empty, you can install the [SUNRISE sample data](https://docs.commercetools.com/sdk/sunrise-data).

## Installation

1. Clone/Download the example folder.
2. Navigate to the path `spring-datadog-statsd/`.
3. Register the client credentials in environment variables:
`CTP_CLIENT_ID`, `CTP_CLIENT_SECRET`, `CTP_PROJECT_KEY`
4. Add Datadog API key to environment variable as `DD_API_KEY`. If necessary, add `DD_SITE` to environmental variable as well.

## Using the Spring MVC Example app

### Start the server

1. Open a new Terminal.
2. Run `./gradlew bootRun`.
3. The server starts.

### Navigate the application

1. Open a new browser window/tab
2. Navigate to [http://localhost:8080/p](http://localhost:8080/p) and a list of products should appear.
45 changes: 45 additions & 0 deletions examples/spring-datadog-statsd/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.0.4'
id 'io.spring.dependency-management' version '1.1.0'
}

group = 'com.commercetools.sdk.examples'
version = '0.0.1-SNAPSHOT'

java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}

repositories {
mavenLocal()
mavenCentral()
}

ext {
versions = [
commercetools: "17.7.0",
]
}

dependencies {
implementation "com.commercetools.sdk:commercetools-sdk-java-api:${versions.commercetools}"
implementation "com.commercetools.sdk:commercetools-apachehttp-client:${versions.commercetools}"
implementation "com.commercetools.sdk:commercetools-monitoring-datadog:${versions.commercetools}"
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
implementation 'com.datadoghq:java-dogstatsd-client:4.3.0'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
developmentOnly "org.springframework.boot:spring-boot-devtools"
}

tasks.named('test') {
useJUnitPlatform()
}

Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading

0 comments on commit b89607f

Please sign in to comment.