Skip to content

Commit

Permalink
Add datadog monitoring module (#568)
Browse files Browse the repository at this point in the history
* Add datadog monitoring module

* Update licences

* Update licenses

* TASK: Updating license information

* Make static auth token example working

* spotless: Fix code style

* spotless: add commit to blame ignore revs file

* Update gradle files

* Update readme

* Move solution info to the right folder

---------

Co-authored-by: ct-sdks[bot] <153784748+ct-sdks[bot]@users.noreply.github.com>
  • Loading branch information
lojzatran and ct-sdks[bot] authored Feb 10, 2024
1 parent af68760 commit fad94ff
Show file tree
Hide file tree
Showing 16 changed files with 1,955 additions and 82 deletions.
1 change: 1 addition & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ a8ec45c8ea4ba559247b654d01b0d35b21a68865




1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ dependencies {
* `commercetools-sdk-compat-v1`: Compatibility layer for Java v1 SDK
* `commercetools-money`: Provider for JSR-354 money instances
* `commercetools-monitoring-newrelic`: Middleware to integrate NewRelic monitoring
* `commercetools-monitoring-datadog`: Middleware to integrate Datadog monitoring
* `commercetools-monitoring-opentelemetry`: Middleware to collect metrics using OpenTelemetry
* `commercetools-graphql-api`: type safe support for the commercetools GraphQL API

Expand Down
13 changes: 7 additions & 6 deletions allowed-licenses.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
{
"moduleLicense": "MIT License"
},
{
"moduleLicense": "PUBLIC DOMAIN"
},
{
"moduleLicense": "MIT-0"
},
Expand All @@ -26,17 +29,15 @@
"moduleName": "com.netflix.graphql.dgs:graphql-dgs-platform-dependencies"
},
{
"moduleLicense": "GNU GENERAL PUBLIC LICENSE, Version 2 + Classpath Exception",
"moduleName": "javax.annotation:javax.annotation-api"
},
{
"moduleLicense": "GNU GENERAL PUBLIC LICENSE, Version 2 + Classpath Exception",
"moduleName": "jakarta.annotation:jakarta.annotation-api"
"moduleLicense": "GNU GENERAL PUBLIC LICENSE, Version 2 + Classpath Exception"
},
{
"moduleLicense": "Eclipse Distribution License - v 1.0",
"moduleName": "com.sun.activation:jakarta.activation"
},
{
"moduleLicense": "Eclipse Distribution License - v 1.0"
},
{
"moduleLicense": "Eclipse Public License - v 2.0",
"moduleName": "com.sun.activation:jakarta.activation"
Expand Down
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ def documentationProjects= [
":commercetools:commercetools-money",
":commercetools:commercetools-monitoring-newrelic",
":commercetools:commercetools-monitoring-opentelemetry",
":commercetools:commercetools-monitoring-datadog",
":commercetools:commercetools-okhttp-client3",
":commercetools:commercetools-okhttp-client4",
":commercetools:commercetools-reactornetty-client",
Expand Down Expand Up @@ -355,6 +356,7 @@ tasks.register("writeVersionToExamples") {
include(name: 'examples/spring/build.gradle')
include(name: 'examples/spring-newrelic/build.gradle')
include(name: 'examples/spring-otel/build.gradle')
include(name: 'examples/spring-datadog/build.gradle')
}
}
ant.replaceregexp(match: '<commercetools.version>.+</commercetools.version>', replace: "<commercetools.version>${globalVersion}</commercetools.version>", flags:'g', byline:true) {
Expand Down
31 changes: 31 additions & 0 deletions commercetools/commercetools-monitoring-datadog/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
dependencies {
api project(":rmf:rmf-java-base")
implementation "com.datadoghq:datadog-api-client:2.20.0"

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

sourceSets {
main {
java {
srcDir 'build/generated/src/main/java'
}
}
}

tasks.register('versionTxt') {
doLast {
new File(projectDir, "$buildInfoPath/com/commercetools/monitoring/datadog/").mkdirs()
new File(projectDir, "$buildInfoPath/com/commercetools/monitoring/datadog/BuildInfo.java").text = """
package com.commercetools.monitoring.datadog;
public class BuildInfo {
public static final String VERSION = "$version";
}
"""
}
}

compileJava {
dependsOn versionTxt
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

package com.commercetools.monitoring.datadog;

public class DatadogInfo {
public static final String CLIENT_DURATION = "client.duration";
public static final String CLIENT_REQUEST_ERROR = "client.request.error";
public static final String CLIENT_REQUEST_TOTAL = "client.request.total";
public static final String HTTP_RESPONSE_STATUS_CODE = "http.response.status_code";
public static final String HTTP_REQUEST_METHOD = "http.request.method";
public static final String JSON_SERIALIZATION = "json.serialization";
public static final String JSON_DESERIALIZATION = "json.deserialization";
public static final String PREFIX = "commercetools";
public static final String REQUEST_BODY_TYPE = "request.body.type";
public static final String RESPONSE_BODY_TYPE = "response.body.type";
public static final String SERVER_ADDRESS = "server.address";
public static final String SERVER_PORT = "server.port";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@

package com.commercetools.monitoring.datadog;

import static com.commercetools.monitoring.datadog.DatadogUtils.*;

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

import com.datadog.api.client.ApiClient;
import com.datadog.api.client.ApiException;
import com.datadog.api.client.v2.api.MetricsApi;

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

public class DatadogMiddleware implements TelemetryMiddleware {

private final MetricsApi apiInstance;

public DatadogMiddleware(final ApiClient ddApiClient) {
this.apiInstance = new MetricsApi(ddApiClient);
}

@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 -> {
try {
submitClientDurationMetric(request, apiInstance, Duration.between(start, Instant.now()).toMillis(),
response);
submitTotalRequestsMetric(request, apiInstance, response);

if (response.getStatusCode() >= 400) {
submitErrorRequestsMetric(request, apiInstance, response);
}
}
catch (ApiException e) {
throw new RuntimeException(e);
}
return response;
});
}

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

package com.commercetools.monitoring.datadog;

import static com.commercetools.monitoring.datadog.DatadogUtils.submitJsonDeserializationMetric;
import static com.commercetools.monitoring.datadog.DatadogUtils.submitJsonSerializationMetric;

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

import com.datadog.api.client.ApiClient;
import com.datadog.api.client.v2.api.MetricsApi;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;

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

public class DatadogResponseSerializer implements ResponseSerializer {
private final ResponseSerializer serializer;

private final MetricsApi apiInstance;

public DatadogResponseSerializer(final ResponseSerializer serializer, final ApiClient ddApiClient) {
this.serializer = serializer;
this.apiInstance = new MetricsApi(ddApiClient);
}

@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();
submitJsonSerializationMetric(apiInstance, (double) durationInMillis, 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();
submitJsonSerializationMetric(apiInstance, (double) durationInMillis, 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();
submitJsonSerializationMetric(apiInstance, (double) durationInMillis, 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();
submitJsonDeserializationMetric(apiInstance, (double) durationInMillis, value.getClass().getCanonicalName());
return result;
}

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

package com.commercetools.monitoring.datadog;

import io.vrap.rmf.base.client.SolutionInfo;

public class DatadogSolutionInfo extends SolutionInfo {
public DatadogSolutionInfo() {
setName("commercetools-monitoring-datadog");
setVersion(BuildInfo.VERSION);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@

package com.commercetools.monitoring.datadog;

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

import java.time.OffsetDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import com.datadog.api.client.ApiException;
import com.datadog.api.client.v2.api.MetricsApi;
import com.datadog.api.client.v2.model.MetricIntakeType;
import com.datadog.api.client.v2.model.MetricPayload;
import com.datadog.api.client.v2.model.MetricPoint;
import com.datadog.api.client.v2.model.MetricSeries;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DatadogUtils {

private static final Logger logger = LoggerFactory.getLogger(DatadogMiddleware.class);

protected static void submitClientDurationMetric(final ApiHttpRequest request, final MetricsApi apiInstance,
final double durationInMillis, final ApiHttpResponse<byte[]> response) throws ApiException {
final String name = PREFIX + "." + CLIENT_DURATION;
final MetricIntakeType type = MetricIntakeType.UNSPECIFIED;
submitMetricWithHttpTags(name, durationInMillis, type, request, apiInstance, response);
}

protected static void submitErrorRequestsMetric(final ApiHttpRequest request, final MetricsApi apiInstance,
final ApiHttpResponse<byte[]> response) throws ApiException {
final String name = PREFIX + "." + CLIENT_REQUEST_ERROR;
final MetricIntakeType count = MetricIntakeType.COUNT;
submitMetricWithHttpTags(name, 1.0, count, request, apiInstance, response);
}

protected static void submitTotalRequestsMetric(final ApiHttpRequest request, final MetricsApi apiInstance,
final ApiHttpResponse<byte[]> response) throws ApiException {
final String name = PREFIX + "." + CLIENT_REQUEST_TOTAL;
final MetricIntakeType count = MetricIntakeType.COUNT;
submitMetricWithHttpTags(name, 1.0, count, request, apiInstance, response);
}

private static void submitMetricWithHttpTags(final String name, final double value, final MetricIntakeType type,
final ApiHttpRequest request, final MetricsApi apiInstance, final ApiHttpResponse<byte[]> response)
throws ApiException {
final List<String> tags = Arrays.asList(format("%s:%s", HTTP_RESPONSE_STATUS_CODE, response.getStatusCode()),
format("%s:%s", HTTP_REQUEST_METHOD, request.getMethod().name()),
format("%s:%s", SERVER_ADDRESS, request.getUri().getHost()));
if (request.getUri().getPort() > 0) {
tags.add(format("%s:%s", SERVER_PORT, request.getUri().getPort()));
}
submitMetric(apiInstance, name, value, type, tags);
}

protected static void submitJsonSerializationMetric(final MetricsApi apiInstance, final double durationInMillis,
final String responseBodyType) {
try {
submitMetric(apiInstance, PREFIX + "." + JSON_SERIALIZATION, durationInMillis, MetricIntakeType.UNSPECIFIED,
Arrays.asList(format("%s:%s", RESPONSE_BODY_TYPE, responseBodyType)));
}
catch (ApiException exception) {
logger.warn("Failed to submit commercetools json serialization metric", exception);
}
}

protected static void submitJsonDeserializationMetric(final MetricsApi apiInstance, final double durationInMillis,
final String requestBodyType) {
try {
submitMetric(apiInstance, PREFIX + "." + JSON_DESERIALIZATION, durationInMillis,
MetricIntakeType.UNSPECIFIED, Arrays.asList(format("%s:%s", REQUEST_BODY_TYPE, requestBodyType)));
}
catch (ApiException exception) {
logger.warn("Failed to submit commercetools json deserialization metric", exception);
}
}

private static void submitMetric(final MetricsApi apiInstance, final String name, final double value,
final MetricIntakeType type, final List<String> tags) throws ApiException {
MetricPayload totalMetric = new MetricPayload().series(Collections.singletonList(new MetricSeries().metric(name)
.type(type)
.points(Collections.singletonList(
new MetricPoint().timestamp(OffsetDateTime.now().toInstant().getEpochSecond()).value(value)))
.tags(tags)));
apiInstance.submitMetrics(totalMetric);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.commercetools.monitoring.datadog.DatadogSolutionInfo
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

package example;

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 io.vrap.rmf.base.client.ApiHttpClient;
import io.vrap.rmf.base.client.ResponseSerializer;

public class DatadogApiRootBuilderTest {

public void addOpenTelemetry() {
ApiHttpClient client = ApiRootBuilder.of()
.defaultClient(ServiceRegion.GCP_EUROPE_WEST1.getApiUrl())
.withTelemetryMiddleware(new DatadogMiddleware(ApiClient.getDefaultApiClient()))
.buildClient();
}

public void addSerializer() {
ApiHttpClient client = ApiRootBuilder.of()
.defaultClient(ServiceRegion.GCP_EUROPE_WEST1.getApiUrl())
.withSerializer(new DatadogResponseSerializer(ResponseSerializer.of(), ApiClient.getDefaultApiClient()))
.buildClient();
}
}
1 change: 1 addition & 0 deletions commercetools/internal-docs/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ dependencies {
implementation project(":commercetools:commercetools-sdk-compat-v1")
implementation project(":commercetools:commercetools-graphql-api")
implementation project(":commercetools:commercetools-monitoring-newrelic")
implementation project(":commercetools:commercetools-monitoring-datadog")

testImplementation ctsdkv1.client version ctsdkv1.version
implementation ctsdkv1.models version ctsdkv1.version
Expand Down
Loading

0 comments on commit fad94ff

Please sign in to comment.