diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index dc65e46d6ff..e059c6ac4cf 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -6,3 +6,4 @@ d0129c1095216d5c830900c8a6223ef5d4274de1 352051999507bd78542e177d67ce1548a0752691 bbe9f971763ca1b27687a6a51067a385a0d23b04 de95c481329aa8b821e6e71ac35c1b8bc67e3e86 +78c44064f4ec15091bde7a2dc590aa2b3a99341d diff --git a/commercetools/commercetools-monitoring-datadog/src/main/java/com/commercetools/monitoring/datadog/DatadogMiddleware.java b/commercetools/commercetools-monitoring-datadog/src/main/java/com/commercetools/monitoring/datadog/DatadogMiddleware.java index a32bb2d6db3..505d0ea9bda 100644 --- a/commercetools/commercetools-monitoring-datadog/src/main/java/com/commercetools/monitoring/datadog/DatadogMiddleware.java +++ b/commercetools/commercetools-monitoring-datadog/src/main/java/com/commercetools/monitoring/datadog/DatadogMiddleware.java @@ -2,11 +2,16 @@ package com.commercetools.monitoring.datadog; import static com.commercetools.monitoring.datadog.DatadogUtils.*; +import static java.lang.String.format; import java.time.Duration; import java.time.Instant; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.function.Function; +import java.util.stream.Collectors; import com.datadog.api.client.ApiClient; import com.datadog.api.client.ApiException; @@ -40,12 +45,26 @@ public class DatadogMiddleware implements TelemetryMiddleware { private final MetricsApi apiInstance; + private final Collection tags; + public DatadogMiddleware(final ApiClient ddApiClient) { - this.apiInstance = new MetricsApi(ddApiClient); + this(new MetricsApi(ddApiClient)); + } + + public DatadogMiddleware(final ApiClient ddApiClient, final Map tags) { + this(new MetricsApi(ddApiClient), tags); } public DatadogMiddleware(final MetricsApi apiInstance) { + this(apiInstance, Collections.emptyMap()); + } + + public DatadogMiddleware(final MetricsApi apiInstance, final Map tags) { this.apiInstance = apiInstance; + this.tags = tags.entrySet() + .stream() + .map(entry -> format("%s:%s", entry.getKey(), entry.getValue())) + .collect(Collectors.toList()); } @Override @@ -65,10 +84,10 @@ else if (throwable instanceof ApiHttpException && ((ApiHttpException) throwable) } try { submitClientDurationMetric(request, apiInstance, Duration.between(start, Instant.now()).toMillis(), - statusCode); - submitTotalRequestsMetric(request, apiInstance, statusCode); + statusCode, tags); + submitTotalRequestsMetric(request, apiInstance, statusCode, tags); if (statusCode >= 400 || throwable != null) { - submitErrorRequestsMetric(request, apiInstance, statusCode); + submitErrorRequestsMetric(request, apiInstance, statusCode, tags); } } catch (ApiException e) { diff --git a/commercetools/commercetools-monitoring-datadog/src/main/java/com/commercetools/monitoring/datadog/DatadogResponseSerializer.java b/commercetools/commercetools-monitoring-datadog/src/main/java/com/commercetools/monitoring/datadog/DatadogResponseSerializer.java index d3fd7a71a82..6aeed0ae207 100644 --- a/commercetools/commercetools-monitoring-datadog/src/main/java/com/commercetools/monitoring/datadog/DatadogResponseSerializer.java +++ b/commercetools/commercetools-monitoring-datadog/src/main/java/com/commercetools/monitoring/datadog/DatadogResponseSerializer.java @@ -3,9 +3,14 @@ import static com.commercetools.monitoring.datadog.DatadogUtils.submitJsonDeserializationMetric; import static com.commercetools.monitoring.datadog.DatadogUtils.submitJsonSerializationMetric; +import static java.lang.String.format; import java.time.Duration; import java.time.Instant; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; import com.datadog.api.client.ApiClient; import com.datadog.api.client.v2.api.MetricsApi; @@ -25,14 +30,29 @@ public class DatadogResponseSerializer implements ResponseSerializer { private final MetricsApi apiInstance; - public DatadogResponseSerializer(final ResponseSerializer serializer, final MetricsApi apiInstance) { + private final Collection tags; + + public DatadogResponseSerializer(final ResponseSerializer serializer, final MetricsApi apiInstance, + final Map tags) { this.serializer = serializer; this.apiInstance = apiInstance; + this.tags = tags.entrySet() + .stream() + .map(entry -> format("%s:%s", entry.getKey(), entry.getValue())) + .collect(Collectors.toList()); + } + + public DatadogResponseSerializer(final ResponseSerializer serializer, final MetricsApi apiInstance) { + this(serializer, apiInstance, Collections.emptyMap()); + } + + public DatadogResponseSerializer(final ResponseSerializer serializer, final ApiClient ddApiClient, + final Map tags) { + this(serializer, new MetricsApi(ddApiClient), tags); } public DatadogResponseSerializer(final ResponseSerializer serializer, final ApiClient ddApiClient) { - this.serializer = serializer; - this.apiInstance = new MetricsApi(ddApiClient); + this(serializer, new MetricsApi(ddApiClient), Collections.emptyMap()); } @Override @@ -40,7 +60,7 @@ public ApiHttpResponse convertResponse(ApiHttpResponse response, Instant start = Instant.now(); ApiHttpResponse result = serializer.convertResponse(response, outputType); double durationInMillis = Duration.between(start, Instant.now()).toNanos() / 1_000_000.0; - submitJsonDeserializationMetric(apiInstance, (double) durationInMillis, outputType.getCanonicalName()); + submitJsonDeserializationMetric(apiInstance, (double) durationInMillis, outputType.getCanonicalName(), tags); return result; } @@ -49,7 +69,7 @@ public ApiHttpResponse convertResponse(ApiHttpResponse response, Instant start = Instant.now(); ApiHttpResponse result = serializer.convertResponse(response, outputType); double durationInMillis = Duration.between(start, Instant.now()).toNanos() / 1_000_000.0; - submitJsonDeserializationMetric(apiInstance, durationInMillis, outputType.toString()); + submitJsonDeserializationMetric(apiInstance, durationInMillis, outputType.toString(), tags); return result; } @@ -58,7 +78,7 @@ public ApiHttpResponse convertResponse(ApiHttpResponse response, Instant start = Instant.now(); ApiHttpResponse result = serializer.convertResponse(response, outputType); double durationInMillis = Duration.between(start, Instant.now()).toNanos() / 1_000_000.0; - submitJsonDeserializationMetric(apiInstance, durationInMillis, outputType.getType().getTypeName()); + submitJsonDeserializationMetric(apiInstance, durationInMillis, outputType.getType().getTypeName(), tags); return result; } @@ -67,7 +87,7 @@ public byte[] toJsonByteArray(Object value) throws JsonProcessingException { Instant start = Instant.now(); byte[] result = serializer.toJsonByteArray(value); double durationInMillis = Duration.between(start, Instant.now()).toNanos() / 1_000_000.0; - submitJsonSerializationMetric(apiInstance, durationInMillis, value.getClass().getCanonicalName()); + submitJsonSerializationMetric(apiInstance, durationInMillis, value.getClass().getCanonicalName(), tags); return result; } diff --git a/commercetools/commercetools-monitoring-datadog/src/main/java/com/commercetools/monitoring/datadog/DatadogUtils.java b/commercetools/commercetools-monitoring-datadog/src/main/java/com/commercetools/monitoring/datadog/DatadogUtils.java index bdc2d105ce2..dc33e8b79aa 100644 --- a/commercetools/commercetools-monitoring-datadog/src/main/java/com/commercetools/monitoring/datadog/DatadogUtils.java +++ b/commercetools/commercetools-monitoring-datadog/src/main/java/com/commercetools/monitoring/datadog/DatadogUtils.java @@ -5,9 +5,7 @@ import static java.lang.String.format; import java.time.OffsetDateTime; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import java.util.*; import com.datadog.api.client.ApiException; import com.datadog.api.client.v2.api.MetricsApi; @@ -27,64 +25,68 @@ 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 response) throws ApiException { - submitClientDurationMetric(request, apiInstance, durationInMillis, response.getStatusCode()); + final double durationInMillis, final ApiHttpResponse response, final Collection tags) + throws ApiException { + submitClientDurationMetric(request, apiInstance, durationInMillis, response.getStatusCode(), tags); } protected static void submitClientDurationMetric(final ApiHttpRequest request, final MetricsApi apiInstance, - final double durationInMillis, final int statusCode) throws ApiException { + final double durationInMillis, final int statusCode, final Collection tags) throws ApiException { final String name = PREFIX + "." + CLIENT_DURATION; final MetricIntakeType type = MetricIntakeType.UNSPECIFIED; - submitMetricWithHttpTags(name, durationInMillis, type, "ms", request, apiInstance, statusCode); + submitMetricWithHttpTags(name, durationInMillis, type, "ms", request, apiInstance, statusCode, tags); } protected static void submitErrorRequestsMetric(final ApiHttpRequest request, final MetricsApi apiInstance, - final ApiHttpResponse response) throws ApiException { - submitErrorRequestsMetric(request, apiInstance, response.getStatusCode()); + final ApiHttpResponse response, final Collection tags) throws ApiException { + submitErrorRequestsMetric(request, apiInstance, response.getStatusCode(), tags); } protected static void submitErrorRequestsMetric(final ApiHttpRequest request, final MetricsApi apiInstance, - final int statusCode) throws ApiException { + final int statusCode, final Collection tags) throws ApiException { final String name = PREFIX + "." + CLIENT_REQUEST_ERROR; final MetricIntakeType count = MetricIntakeType.COUNT; - submitMetricWithHttpTags(name, 1.0, count, "count", request, apiInstance, statusCode); + submitMetricWithHttpTags(name, 1.0, count, "count", request, apiInstance, statusCode, tags); } protected static void submitTotalRequestsMetric(final ApiHttpRequest request, final MetricsApi apiInstance, - final ApiHttpResponse response) throws ApiException { - submitTotalRequestsMetric(request, apiInstance, response.getStatusCode()); + final ApiHttpResponse response, final Collection tags) throws ApiException { + submitTotalRequestsMetric(request, apiInstance, response.getStatusCode(), tags); } protected static void submitTotalRequestsMetric(final ApiHttpRequest request, final MetricsApi apiInstance, - final int statusCode) throws ApiException { + final int statusCode, final Collection tags) throws ApiException { final String name = PREFIX + "." + CLIENT_REQUEST_TOTAL; final MetricIntakeType count = MetricIntakeType.COUNT; - submitMetricWithHttpTags(name, 1.0, count, "count", request, apiInstance, statusCode); + submitMetricWithHttpTags(name, 1.0, count, "count", request, apiInstance, statusCode, tags); } private static void submitMetricWithHttpTags(final String name, final double value, final MetricIntakeType type, final String unit, final ApiHttpRequest request, final MetricsApi apiInstance, - final ApiHttpResponse response) throws ApiException { - submitMetricWithHttpTags(name, value, type, unit, request, apiInstance, response.getStatusCode()); + final ApiHttpResponse response, final Collection tags) throws ApiException { + submitMetricWithHttpTags(name, value, type, unit, request, apiInstance, response.getStatusCode(), tags); } private static void submitMetricWithHttpTags(final String name, final double value, final MetricIntakeType type, - final String unit, final ApiHttpRequest request, final MetricsApi apiInstance, final int statusCode) - throws ApiException { - final List tags = Arrays.asList(format("%s:%s", HTTP_RESPONSE_STATUS_CODE, statusCode), - format("%s:%s", HTTP_REQUEST_METHOD, request.getMethod().name()), - format("%s:%s", SERVER_ADDRESS, request.getUri().getHost())); + final String unit, final ApiHttpRequest request, final MetricsApi apiInstance, final int statusCode, + final Collection tags) throws ApiException { + final List currentTags = new ArrayList<>(tags); + currentTags.add(format("%s:%s", HTTP_RESPONSE_STATUS_CODE, statusCode)); + currentTags.add(format("%s:%s", HTTP_REQUEST_METHOD, request.getMethod().name())); + currentTags.add(format("%s:%s", SERVER_ADDRESS, request.getUri().getHost())); if (request.getUri().getPort() > 0) { - tags.add(format("%s:%s", SERVER_PORT, request.getUri().getPort())); + currentTags.add(format("%s:%s", SERVER_PORT, request.getUri().getPort())); } - submitMetric(apiInstance, name, value, type, unit, tags); + submitMetric(apiInstance, name, value, type, unit, currentTags); } protected static void submitJsonSerializationMetric(final MetricsApi apiInstance, final double durationInMillis, - final String responseBodyType) { + final String responseBodyType, final Collection tags) { try { + final List currentTags = new ArrayList<>(tags); + currentTags.add(format("%s:%s", RESPONSE_BODY_TYPE, responseBodyType)); submitMetric(apiInstance, PREFIX + "." + JSON_SERIALIZATION, durationInMillis, MetricIntakeType.UNSPECIFIED, - "ms", Arrays.asList(format("%s:%s", RESPONSE_BODY_TYPE, responseBodyType))); + "ms", currentTags); } catch (ApiException exception) { logger.warn("Failed to submit commercetools json serialization metric", exception); @@ -92,10 +94,13 @@ protected static void submitJsonSerializationMetric(final MetricsApi apiInstance } protected static void submitJsonDeserializationMetric(final MetricsApi apiInstance, final double durationInMillis, - final String requestBodyType) { + final String requestBodyType, final Collection tags) { try { + final List currentTags = new ArrayList<>(tags); + currentTags.add(format("%s:%s", REQUEST_BODY_TYPE, requestBodyType)); + submitMetric(apiInstance, PREFIX + "." + JSON_DESERIALIZATION, durationInMillis, - MetricIntakeType.UNSPECIFIED, "ms", Arrays.asList(format("%s:%s", REQUEST_BODY_TYPE, requestBodyType))); + MetricIntakeType.UNSPECIFIED, "ms", currentTags); } catch (ApiException exception) { logger.warn("Failed to submit commercetools json deserialization metric", exception); diff --git a/commercetools/commercetools-monitoring-datadog/src/main/java/com/commercetools/monitoring/datadog/statsd/DatadogMiddleware.java b/commercetools/commercetools-monitoring-datadog/src/main/java/com/commercetools/monitoring/datadog/statsd/DatadogMiddleware.java index d6142f196a1..9530865907d 100644 --- a/commercetools/commercetools-monitoring-datadog/src/main/java/com/commercetools/monitoring/datadog/statsd/DatadogMiddleware.java +++ b/commercetools/commercetools-monitoring-datadog/src/main/java/com/commercetools/monitoring/datadog/statsd/DatadogMiddleware.java @@ -6,10 +6,10 @@ import java.time.Duration; import java.time.Instant; -import java.util.ArrayList; -import java.util.List; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.function.Function; +import java.util.stream.Collectors; import com.timgroup.statsd.StatsDClient; @@ -41,8 +41,18 @@ public class DatadogMiddleware implements TelemetryMiddleware { private final StatsDClient statsDClient; + private final Collection tags; + public DatadogMiddleware(final StatsDClient datadogStatsDClient) { + this(datadogStatsDClient, Collections.emptyMap()); + } + + public DatadogMiddleware(final StatsDClient datadogStatsDClient, final Map tags) { this.statsDClient = datadogStatsDClient; + this.tags = tags.entrySet() + .stream() + .map(entry -> format("%s:%s", entry.getKey(), entry.getValue())) + .collect(Collectors.toList()); } @Override @@ -65,6 +75,7 @@ else if (throwable instanceof ApiHttpException && ((ApiHttpException) throwable) tags.add(format("%s:%s", HTTP_RESPONSE_STATUS_CODE, statusCode)); tags.add(format("%s:%s", HTTP_REQUEST_METHOD, request.getMethod().name())); tags.add(format("%s:%s", SERVER_ADDRESS, request.getUri().getHost())); + tags.addAll(this.tags); if (request.getUri().getPort() > 0) { tags.add(format("%s:%s", SERVER_PORT, request.getUri().getPort())); } diff --git a/commercetools/commercetools-monitoring-datadog/src/main/java/com/commercetools/monitoring/datadog/statsd/DatadogResponseSerializer.java b/commercetools/commercetools-monitoring-datadog/src/main/java/com/commercetools/monitoring/datadog/statsd/DatadogResponseSerializer.java index f9b19947df1..fef97c28e2c 100644 --- a/commercetools/commercetools-monitoring-datadog/src/main/java/com/commercetools/monitoring/datadog/statsd/DatadogResponseSerializer.java +++ b/commercetools/commercetools-monitoring-datadog/src/main/java/com/commercetools/monitoring/datadog/statsd/DatadogResponseSerializer.java @@ -6,6 +6,8 @@ import java.time.Duration; import java.time.Instant; +import java.util.*; +import java.util.stream.Collectors; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; @@ -24,9 +26,20 @@ public class DatadogResponseSerializer implements ResponseSerializer { private final StatsDClient statsDClient; + private final Collection tags; + public DatadogResponseSerializer(final ResponseSerializer serializer, final StatsDClient datadogStatsDClient) { + this(serializer, datadogStatsDClient, Collections.emptyMap()); + } + + public DatadogResponseSerializer(final ResponseSerializer serializer, final StatsDClient datadogStatsDClient, + final Map tags) { this.serializer = serializer; this.statsDClient = datadogStatsDClient; + this.tags = tags.entrySet() + .stream() + .map(entry -> format("%s:%s", entry.getKey(), entry.getValue())) + .collect(Collectors.toList()); } @Override @@ -34,8 +47,11 @@ public ApiHttpResponse convertResponse(ApiHttpResponse response, Instant start = Instant.now(); ApiHttpResponse result = serializer.convertResponse(response, outputType); double durationInMillis = Duration.between(start, Instant.now()).toNanos() / 1_000_000.0; + final Collection tags = new ArrayList<>(this.tags); + tags.add(format("%s:%s", RESPONSE_BODY_TYPE, outputType.getCanonicalName())); + this.statsDClient.recordHistogramValue(PREFIX + "." + JSON_DESERIALIZATION, durationInMillis, - format("%s:%s", RESPONSE_BODY_TYPE, outputType.getCanonicalName())); + tags.toArray(new String[0])); return result; } @@ -44,8 +60,11 @@ public ApiHttpResponse convertResponse(ApiHttpResponse response, Instant start = Instant.now(); ApiHttpResponse result = serializer.convertResponse(response, outputType); double durationInMillis = Duration.between(start, Instant.now()).toNanos() / 1_000_000.0; + final Collection tags = new ArrayList<>(this.tags); + tags.add(format("%s:%s", RESPONSE_BODY_TYPE, outputType.toString())); + this.statsDClient.recordHistogramValue(PREFIX + "." + JSON_DESERIALIZATION, durationInMillis, - format("%s:%s", RESPONSE_BODY_TYPE, outputType.toString())); + tags.toArray(new String[0])); return result; } @@ -54,8 +73,11 @@ public ApiHttpResponse convertResponse(ApiHttpResponse response, Instant start = Instant.now(); ApiHttpResponse result = serializer.convertResponse(response, outputType); double durationInMillis = Duration.between(start, Instant.now()).toNanos() / 1_000_000.0; + final Collection tags = new ArrayList<>(this.tags); + tags.add(format("%s:%s", RESPONSE_BODY_TYPE, outputType.getType().getTypeName())); + this.statsDClient.recordHistogramValue(PREFIX + "." + JSON_DESERIALIZATION, durationInMillis, - format("%s:%s", RESPONSE_BODY_TYPE, outputType.getType().getTypeName())); + tags.toArray(new String[0])); return result; } @@ -64,8 +86,11 @@ public byte[] toJsonByteArray(Object value) throws JsonProcessingException { Instant start = Instant.now(); byte[] result = serializer.toJsonByteArray(value); double durationInMillis = Duration.between(start, Instant.now()).toNanos() / 1_000_000.0; + final Collection tags = new ArrayList<>(this.tags); + tags.add(format("%s:%s", RESPONSE_BODY_TYPE, value.getClass().getCanonicalName())); + this.statsDClient.recordHistogramValue(PREFIX + "." + JSON_SERIALIZATION, durationInMillis, - format("%s:%s", REQUEST_BODY_TYPE, value.getClass().getCanonicalName())); + tags.toArray(new String[0])); return result; } diff --git a/commercetools/commercetools-monitoring-datadog/src/test/java/example/ResponseSerializerTest.java b/commercetools/commercetools-monitoring-datadog/src/test/java/example/ResponseSerializerTest.java index d2fb8b37914..114294fed64 100644 --- a/commercetools/commercetools-monitoring-datadog/src/test/java/example/ResponseSerializerTest.java +++ b/commercetools/commercetools-monitoring-datadog/src/test/java/example/ResponseSerializerTest.java @@ -1,6 +1,9 @@ package example; +import java.util.HashMap; +import java.util.Map; + import com.commercetools.api.models.common.Reference; import com.commercetools.api.models.product.ProductReference; import com.commercetools.monitoring.datadog.DatadogResponseSerializer; @@ -36,6 +39,32 @@ public void testSerialize() throws ApiException, JsonProcessingException { })); } + @Test + public void testSerializeWithAttributes() throws ApiException, JsonProcessingException { + MetricsApi metricsApi = Mockito.mock(MetricsApi.class); + Mockito.when(metricsApi.submitMetrics(Mockito.any())).thenReturn(null); + Map tags = new HashMap<>(); + tags.put("foo", "bar"); + + DatadogResponseSerializer serializer = new DatadogResponseSerializer(ResponseSerializer.of(), metricsApi, tags); + + Reference reference = ProductReference.builder().id("abc").build(); + + serializer.toJsonByteArray(reference); + + Mockito.verify(metricsApi).submitMetrics(Mockito.argThat(metricPayload -> { + Assertions.assertThat(metricPayload).isNotNull(); + Assertions.assertThat(metricPayload.getSeries().get(0).getMetric()) + .isEqualTo("commercetools.json.serialization"); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().size()).isEqualTo(2); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().get(0)).isEqualTo("foo:bar"); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().get(1)) + .isEqualTo("response.body.type:com.commercetools.api.models.product.ProductReferenceImpl"); + + return true; + })); + } + @Test public void testDeserialize() throws ApiException, JsonProcessingException { MetricsApi metricsApi = Mockito.mock(MetricsApi.class); @@ -55,4 +84,31 @@ public void testDeserialize() throws ApiException, JsonProcessingException { return true; })); } + + @Test + public void testDeserializeWithAttributes() throws ApiException, JsonProcessingException { + MetricsApi metricsApi = Mockito.mock(MetricsApi.class); + Mockito.when(metricsApi.submitMetrics(Mockito.any())).thenReturn(null); + Map tags = new HashMap<>(); + tags.put("foo", "bar"); + + DatadogResponseSerializer serializer = new DatadogResponseSerializer(ResponseSerializer.of(), metricsApi, tags); + + String responseBody = "{ \"typeId\": \"product\", \"id\": \"abc\" }"; + ApiHttpResponse response = new ApiHttpResponse<>(200, new ApiHttpHeaders(), responseBody.getBytes()); + + ApiHttpResponse reference = serializer.convertResponse(response, Reference.class); + + Assertions.assertThat(reference.getBody()).isInstanceOf(ProductReference.class); + Mockito.verify(metricsApi).submitMetrics(Mockito.argThat(metricPayload -> { + Assertions.assertThat(metricPayload).isNotNull(); + Assertions.assertThat(metricPayload.getSeries().get(0).getMetric()) + .isEqualTo("commercetools.json.deserialization"); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().size()).isEqualTo(2); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().get(0)).isEqualTo("foo:bar"); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().get(1)) + .isEqualTo("request.body.type:com.commercetools.api.models.common.Reference"); + return true; + })); + } } diff --git a/commercetools/commercetools-monitoring-datadog/src/test/java/example/TelemetryMiddlewareTest.java b/commercetools/commercetools-monitoring-datadog/src/test/java/example/TelemetryMiddlewareTest.java index 6d491de0c24..83e7e19f5e8 100644 --- a/commercetools/commercetools-monitoring-datadog/src/test/java/example/TelemetryMiddlewareTest.java +++ b/commercetools/commercetools-monitoring-datadog/src/test/java/example/TelemetryMiddlewareTest.java @@ -6,6 +6,8 @@ import java.net.URI; import java.net.URISyntaxException; import java.time.Duration; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.function.Function; @@ -48,6 +50,13 @@ public void testCounts(int statusCode, int count) throws ApiException { Assertions.assertThat(metricPayload.getSeries().get(0).getMetric()) .isIn("commercetools.client.duration", "commercetools.client.request.total", "commercetools.client.request.error"); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().size()).isEqualTo(3); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().get(0)) + .isEqualTo("http.response.status_code:" + statusCode); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().get(1)) + .isEqualTo("http.request.method:GET"); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().get(2)).isEqualTo("server.address:null"); + return true; })); } @@ -80,6 +89,91 @@ public void testHttpCounts(int statusCode, int count) throws URISyntaxException, Assertions.assertThat(metricPayload.getSeries().get(0).getMetric()) .isIn("commercetools.client.duration", "commercetools.client.request.total", "commercetools.client.request.error"); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().size()).isEqualTo(3); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().get(0)) + .isEqualTo("http.response.status_code:" + statusCode); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().get(1)) + .isEqualTo("http.request.method:GET"); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().get(2)).isEqualTo("server.address:null"); + return true; + })); + } + + @ParameterizedTest + @MethodSource("responses") + public void testCountsWithAttributes(int statusCode, int count) throws ApiException { + MetricsApi metricsApi = Mockito.mock(MetricsApi.class); + Mockito.when(metricsApi.submitMetrics(Mockito.any())).thenReturn(null); + Map tags = new HashMap<>(); + tags.put("foo", "bar"); + + DatadogMiddleware middleware = new DatadogMiddleware(metricsApi, tags); + + final ApiHttpRequest request = new ApiHttpRequest(ApiHttpMethod.GET, URI.create("/"), new ApiHttpHeaders(), + "".getBytes()); + + blockingWait( + middleware.invoke(request, + request1 -> CompletableFuture + .completedFuture(new ApiHttpResponse<>(statusCode, new ApiHttpHeaders(), "".getBytes()))), + Duration.ofSeconds(1)); + + Mockito.verify(metricsApi, Mockito.times(count)).submitMetrics(Mockito.argThat(metricPayload -> { + Assertions.assertThat(metricPayload).isNotNull(); + Assertions.assertThat(metricPayload.getSeries().get(0).getMetric()) + .isIn("commercetools.client.duration", "commercetools.client.request.total", + "commercetools.client.request.error"); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().size()).isEqualTo(4); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().get(0)).isEqualTo("foo:bar"); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().get(1)) + .isEqualTo("http.response.status_code:" + statusCode); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().get(2)) + .isEqualTo("http.request.method:GET"); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().get(3)).isEqualTo("server.address:null"); + + return true; + })); + } + + @ParameterizedTest + @MethodSource("responses") + public void testHttpCountsWithAttributes(int statusCode, int count) throws URISyntaxException, ApiException { + MetricsApi metricsApi = Mockito.mock(MetricsApi.class); + Mockito.when(metricsApi.submitMetrics(Mockito.any())).thenReturn(null); + Map tags = new HashMap<>(); + tags.put("foo", "bar"); + + DatadogMiddleware middleware = new DatadogMiddleware(metricsApi, tags); + + ApiHttpClient client = ClientBuilder + .of(new TestHttpClient(request -> CompletableFuture + .completedFuture(new ApiHttpResponse<>(statusCode, new ApiHttpHeaders(), "".getBytes())))) + .withApiBaseUrl(new URI("")) + .withTelemetryMiddleware(middleware) + .withErrorMiddleware() + .build(); + + final ApiHttpRequest request = new ApiHttpRequest(ApiHttpMethod.GET, URI.create("/"), new ApiHttpHeaders(), + "".getBytes()); + try { + blockingWait(client.execute(request), Duration.ofSeconds(1)); + } + catch (ApiHttpException ignored) { + } + + Mockito.verify(metricsApi, Mockito.times(count)).submitMetrics(Mockito.argThat(metricPayload -> { + Assertions.assertThat(metricPayload).isNotNull(); + Assertions.assertThat(metricPayload.getSeries().get(0).getMetric()) + .isIn("commercetools.client.duration", "commercetools.client.request.total", + "commercetools.client.request.error"); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().size()).isEqualTo(4); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().get(0)).isEqualTo("foo:bar"); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().get(1)) + .isEqualTo("http.response.status_code:" + statusCode); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().get(2)) + .isEqualTo("http.request.method:GET"); + Assertions.assertThat(metricPayload.getSeries().get(0).getTags().get(3)).isEqualTo("server.address:null"); + return true; })); } diff --git a/commercetools/commercetools-monitoring-newrelic/src/main/java/com/commercetools/monitoring/newrelic/NewRelicTelemetryMiddleware.java b/commercetools/commercetools-monitoring-newrelic/src/main/java/com/commercetools/monitoring/newrelic/NewRelicTelemetryMiddleware.java index 7d08b01cba0..2c90ecddfc5 100644 --- a/commercetools/commercetools-monitoring-newrelic/src/main/java/com/commercetools/monitoring/newrelic/NewRelicTelemetryMiddleware.java +++ b/commercetools/commercetools-monitoring-newrelic/src/main/java/com/commercetools/monitoring/newrelic/NewRelicTelemetryMiddleware.java @@ -5,7 +5,7 @@ import java.time.Duration; import java.time.Instant; -import java.util.Optional; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.function.Function; @@ -52,6 +52,17 @@ * See also the Spring MVC example application in the examples folder for further details. */ public class NewRelicTelemetryMiddleware implements TelemetryMiddleware { + + private final Map attributes; + + public NewRelicTelemetryMiddleware() { + this.attributes = Collections.emptyMap(); + } + + public NewRelicTelemetryMiddleware(final Map attributes) { + this.attributes = Collections.unmodifiableMap(attributes); + } + @Trace(async = true) @Override public CompletableFuture> invoke(ApiHttpRequest request, @@ -87,6 +98,7 @@ else if (throwable instanceof ApiHttpException && ((ApiHttpException) throwable) .noInboundHeaders() .status(statusCode, message) .build())); + segment.ifPresent(s -> s.addCustomAttributes(this.attributes)); segment.ifPresent(Segment::end); NewRelic.incrementCounter(PREFIX + CLIENT_REQUEST_TOTAL); diff --git a/commercetools/commercetools-monitoring-newrelic/src/main/java/com/commercetools/monitoring/newrelic/NewrelicResponseSerializer.java b/commercetools/commercetools-monitoring-newrelic/src/main/java/com/commercetools/monitoring/newrelic/NewrelicResponseSerializer.java index 73e23c787cc..009645c875b 100644 --- a/commercetools/commercetools-monitoring-newrelic/src/main/java/com/commercetools/monitoring/newrelic/NewrelicResponseSerializer.java +++ b/commercetools/commercetools-monitoring-newrelic/src/main/java/com/commercetools/monitoring/newrelic/NewrelicResponseSerializer.java @@ -5,6 +5,8 @@ import java.time.Duration; import java.time.Instant; +import java.util.Collections; +import java.util.Map; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; @@ -19,9 +21,16 @@ */ public class NewrelicResponseSerializer implements ResponseSerializer { private final ResponseSerializer serializer; + private final Map attributes; public NewrelicResponseSerializer(final ResponseSerializer serializer) { this.serializer = serializer; + this.attributes = Collections.emptyMap(); + } + + public NewrelicResponseSerializer(final ResponseSerializer serializer, final Map attributes) { + this.serializer = serializer; + this.attributes = Collections.unmodifiableMap(attributes); } @Override @@ -29,7 +38,7 @@ public ApiHttpResponse convertResponse(ApiHttpResponse response, Instant start = Instant.now(); ApiHttpResponse result = serializer.convertResponse(response, outputType); double durationInMillis = Duration.between(start, Instant.now()).toNanos() / 1_000_000.0; - ; + NewRelic.addCustomParameters(this.attributes); NewRelic.recordMetric(PREFIX + JSON_DESERIALIZATION, (float) durationInMillis); return result; } @@ -39,7 +48,7 @@ public ApiHttpResponse convertResponse(ApiHttpResponse response, Instant start = Instant.now(); ApiHttpResponse result = serializer.convertResponse(response, outputType); double durationInMillis = Duration.between(start, Instant.now()).toNanos() / 1_000_000.0; - ; + NewRelic.addCustomParameters(this.attributes); NewRelic.recordMetric(PREFIX + JSON_DESERIALIZATION, (float) durationInMillis); return result; } @@ -49,7 +58,7 @@ public ApiHttpResponse convertResponse(ApiHttpResponse response, Instant start = Instant.now(); ApiHttpResponse result = serializer.convertResponse(response, outputType); double durationInMillis = Duration.between(start, Instant.now()).toNanos() / 1_000_000.0; - ; + NewRelic.addCustomParameters(this.attributes); NewRelic.recordMetric(PREFIX + JSON_DESERIALIZATION, (float) durationInMillis); return result; } @@ -59,7 +68,7 @@ public byte[] toJsonByteArray(Object value) throws JsonProcessingException { Instant start = Instant.now(); byte[] result = serializer.toJsonByteArray(value); double durationInMillis = Duration.between(start, Instant.now()).toNanos() / 1_000_000.0; - ; + NewRelic.addCustomParameters(this.attributes); NewRelic.recordMetric(PREFIX + JSON_SERIALIZATION, (float) durationInMillis); return result; } diff --git a/commercetools/commercetools-monitoring-opentelemetry/src/main/java/com/commercetools/monitoring/opentelemetry/OpenTelemetryMiddleware.java b/commercetools/commercetools-monitoring-opentelemetry/src/main/java/com/commercetools/monitoring/opentelemetry/OpenTelemetryMiddleware.java index db3c929c3c4..2e88336e5ff 100644 --- a/commercetools/commercetools-monitoring-opentelemetry/src/main/java/com/commercetools/monitoring/opentelemetry/OpenTelemetryMiddleware.java +++ b/commercetools/commercetools-monitoring-opentelemetry/src/main/java/com/commercetools/monitoring/opentelemetry/OpenTelemetryMiddleware.java @@ -3,6 +3,8 @@ import java.time.Duration; import java.time.Instant; +import java.util.Collections; +import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.function.Function; @@ -41,6 +43,8 @@ public class OpenTelemetryMiddleware implements TelemetryMiddleware { private final LongCounter errorCounter; private final LongCounter requestCounter; + private final Attributes attributes; + public OpenTelemetryMiddleware(final OpenTelemetry otel) { this(otel, true, OpenTelemetryInfo.PREFIX); } @@ -50,6 +54,15 @@ public OpenTelemetryMiddleware(final OpenTelemetry otel, final boolean enableHis } public OpenTelemetryMiddleware(final OpenTelemetry otel, final boolean enableHistogram, final String prefix) { + this(otel, enableHistogram, prefix, Collections.emptyMap()); + } + + public OpenTelemetryMiddleware(final OpenTelemetry otel, final boolean enableHistogram, final String prefix, + final Map attributes) { + AttributesBuilder attrBuilder = Attributes.builder(); + attributes.forEach(attrBuilder::put); + this.attributes = attrBuilder.build(); + Meter meter = otel.meterBuilder(OpenTelemetryResponseSerializer.class.getPackage().getName()).build(); if (enableHistogram) { histogram = meter.histogramBuilder(prefix + "." + OpenTelemetryInfo.CLIENT_DURATION) @@ -79,7 +92,7 @@ else if (throwable instanceof ApiHttpException && ((ApiHttpException) throwable) else { statusCode = 0; } - AttributesBuilder builder = Attributes.builder() + AttributesBuilder builder = this.attributes.toBuilder() .put(OpenTelemetryInfo.HTTP_RESPONSE_STATUS_CODE, statusCode) .put(OpenTelemetryInfo.HTTP_REQUEST_METHOD, request.getMethod().name()) .put(OpenTelemetryInfo.SERVER_ADDRESS, request.getUri().getHost()); diff --git a/commercetools/commercetools-monitoring-opentelemetry/src/main/java/com/commercetools/monitoring/opentelemetry/OpenTelemetryResponseSerializer.java b/commercetools/commercetools-monitoring-opentelemetry/src/main/java/com/commercetools/monitoring/opentelemetry/OpenTelemetryResponseSerializer.java index 396cb5c7cd2..d131306dfc6 100644 --- a/commercetools/commercetools-monitoring-opentelemetry/src/main/java/com/commercetools/monitoring/opentelemetry/OpenTelemetryResponseSerializer.java +++ b/commercetools/commercetools-monitoring-opentelemetry/src/main/java/com/commercetools/monitoring/opentelemetry/OpenTelemetryResponseSerializer.java @@ -3,14 +3,16 @@ import java.time.Duration; import java.time.Instant; +import java.util.Collections; +import java.util.Map; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JavaType; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.metrics.DoubleHistogram; import io.opentelemetry.api.metrics.Meter; import io.vrap.rmf.base.client.ApiHttpResponse; @@ -32,12 +34,23 @@ public class OpenTelemetryResponseSerializer implements ResponseSerializer { private final DoubleHistogram serializerHistogram; private final DoubleHistogram deserializerHistogram; + private final Attributes attributes; + public OpenTelemetryResponseSerializer(final ResponseSerializer serializer, final OpenTelemetry otel) { this(serializer, otel, OpenTelemetryInfo.PREFIX); } public OpenTelemetryResponseSerializer(final ResponseSerializer serializer, final OpenTelemetry otel, final String prefix) { + this(serializer, otel, prefix, Collections.emptyMap()); + } + + public OpenTelemetryResponseSerializer(final ResponseSerializer serializer, final OpenTelemetry otel, + final String prefix, final Map attributes) { + AttributesBuilder attrBuilder = Attributes.builder(); + attributes.forEach(attrBuilder::put); + this.attributes = attrBuilder.build(); + this.serializer = serializer; Meter meter = otel.meterBuilder(OpenTelemetryResponseSerializer.class.getPackage().getName()).build(); serializerHistogram = meter.histogramBuilder(prefix + "." + OpenTelemetryInfo.JSON_SERIALIZATION) @@ -53,8 +66,9 @@ public OpenTelemetryResponseSerializer(final ResponseSerializer serializer, fina public ApiHttpResponse convertResponse(ApiHttpResponse response, Class outputType) { Instant start = Instant.now(); ApiHttpResponse result = serializer.convertResponse(response, outputType); - Attributes attributes = Attributes.of(AttributeKey.stringKey(OpenTelemetryInfo.RESPONSE_BODY_TYPE), - outputType.getCanonicalName()); + Attributes attributes = this.attributes.toBuilder() + .put(OpenTelemetryInfo.RESPONSE_BODY_TYPE, outputType.getCanonicalName()) + .build(); double durationMs = Duration.between(start, Instant.now()).toNanos() / 1_000_000.0; deserializerHistogram.record(durationMs, attributes); return result; @@ -64,8 +78,9 @@ public ApiHttpResponse convertResponse(ApiHttpResponse response, public ApiHttpResponse convertResponse(ApiHttpResponse response, JavaType outputType) { Instant start = Instant.now(); ApiHttpResponse result = serializer.convertResponse(response, outputType); - Attributes attributes = Attributes.of(AttributeKey.stringKey(OpenTelemetryInfo.RESPONSE_BODY_TYPE), - outputType.toString()); + Attributes attributes = this.attributes.toBuilder() + .put(OpenTelemetryInfo.RESPONSE_BODY_TYPE, outputType.toString()) + .build(); double duration = Duration.between(start, Instant.now()).toNanos() / 1_000_000.0; deserializerHistogram.record(duration, attributes); return result; @@ -75,8 +90,9 @@ public ApiHttpResponse convertResponse(ApiHttpResponse response, public ApiHttpResponse convertResponse(ApiHttpResponse response, TypeReference outputType) { Instant start = Instant.now(); ApiHttpResponse result = serializer.convertResponse(response, outputType); - Attributes attributes = Attributes.of(AttributeKey.stringKey(OpenTelemetryInfo.RESPONSE_BODY_TYPE), - outputType.getType().getTypeName()); + Attributes attributes = this.attributes.toBuilder() + .put(OpenTelemetryInfo.RESPONSE_BODY_TYPE, outputType.getType().getTypeName()) + .build(); double duration = Duration.between(start, Instant.now()).toNanos() / 1_000_000.0; deserializerHistogram.record(duration, attributes); return result; @@ -86,8 +102,9 @@ public ApiHttpResponse convertResponse(ApiHttpResponse response, public byte[] toJsonByteArray(Object value) throws JsonProcessingException { Instant start = Instant.now(); byte[] result = serializer.toJsonByteArray(value); - Attributes attributes = Attributes.of(AttributeKey.stringKey(OpenTelemetryInfo.REQUEST_BODY_TYPE), - value.getClass().getCanonicalName()); + Attributes attributes = this.attributes.toBuilder() + .put(OpenTelemetryInfo.REQUEST_BODY_TYPE, value.getClass().getCanonicalName()) + .build(); double duration = Duration.between(start, Instant.now()).toNanos() / 1_000_000.0; serializerHistogram.record(duration, attributes); return result;