From b0837a3e46641bcbfd8c8d396b10a9c02d30c1af Mon Sep 17 00:00:00 2001 From: Min Xia Date: Mon, 5 Jun 2023 16:25:45 -0700 Subject: [PATCH] Enhance AWS APM metrics mapping implementation to resolve a few unknown dimension use cases --- .../contrib/awsxray/AwsAttributeKeys.java | 7 + .../awsxray/AwsMetricAttributeGenerator.java | 213 +++++++++++++++--- .../AwsMetricAttributeGeneratorTest.java | 152 ++++++++++++- 3 files changed, 339 insertions(+), 33 deletions(-) diff --git a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsAttributeKeys.java b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsAttributeKeys.java index 595a133a4..5a6eb9a94 100644 --- a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsAttributeKeys.java +++ b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsAttributeKeys.java @@ -24,4 +24,11 @@ private AwsAttributeKeys() {} static final AttributeKey AWS_REMOTE_OPERATION = AttributeKey.stringKey("aws.remote.operation"); + + static final AttributeKey AWS_REMOTE_TARGET = AttributeKey.stringKey("aws.remote.target"); + + static final AttributeKey AWS_BUCKET_NAME = AttributeKey.stringKey("aws.bucket.name"); + static final AttributeKey AWS_QUEUE_NAME = AttributeKey.stringKey("aws.queue.name"); + static final AttributeKey AWS_STREAM_NAME = AttributeKey.stringKey("aws.stream.name"); + static final AttributeKey AWS_TABLE_NAME = AttributeKey.stringKey("aws.table.name"); } diff --git a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGenerator.java b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGenerator.java index b51274f0f..1c9ab4848 100644 --- a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGenerator.java +++ b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGenerator.java @@ -5,19 +5,31 @@ package io.opentelemetry.contrib.awsxray; +import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_BUCKET_NAME; import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_LOCAL_OPERATION; import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_LOCAL_SERVICE; +import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_QUEUE_NAME; import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_REMOTE_OPERATION; import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_REMOTE_SERVICE; +import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_REMOTE_TARGET; import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_SPAN_KIND; +import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_STREAM_NAME; +import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_TABLE_NAME; import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.SERVICE_NAME; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.DB_OPERATION; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.DB_SYSTEM; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.FAAS_INVOKED_NAME; -import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.FAAS_INVOKED_PROVIDER; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.FAAS_TRIGGER; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.GRAPHQL_OPERATION_TYPE; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_METHOD; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_TARGET; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_URL; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_OPERATION; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_SYSTEM; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NET_PEER_NAME; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NET_PEER_PORT; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NET_SOCK_PEER_ADDR; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NET_SOCK_PEER_PORT; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.PEER_SERVICE; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.RPC_METHOD; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.RPC_SERVICE; @@ -30,6 +42,9 @@ import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; @@ -71,6 +86,7 @@ public Attributes generateMetricAttributesFromSpan(SpanData span, Resource resou setService(resource, span, builder); setEgressOperation(span, builder); setRemoteServiceAndOperation(span, builder); + setRemoteTarget(span, builder); setSpanKind(span, builder); break; default: @@ -79,6 +95,30 @@ public Attributes generateMetricAttributesFromSpan(SpanData span, Resource resou return builder.build(); } + private static void setRemoteTarget(SpanData span, AttributesBuilder builder) { + Optional remoteTarget = getRemoteTarget(span); + remoteTarget.ifPresent(s -> builder.put(AWS_REMOTE_TARGET, s)); + } + + /** + * RemoteTarget attribute {@link AwsAttributeKeys#AWS_REMOTE_TARGET} is used to store the resource + * name of the remote invokes, such as S3 bucket name, mysql table name, etc. TODO: currently only + * support AWS resource name, will be extended to support the general remote targets, such as + * ActiveMQ name, etc. + */ + private static Optional getRemoteTarget(SpanData span) { + if (isKeyPresent(span, AWS_BUCKET_NAME)) { + return Optional.ofNullable(span.getAttributes().get(AWS_BUCKET_NAME)); + } else if (isKeyPresent(span, AWS_QUEUE_NAME)) { + return Optional.ofNullable(span.getAttributes().get(AWS_QUEUE_NAME)); + } else if (isKeyPresent(span, AWS_STREAM_NAME)) { + return Optional.ofNullable(span.getAttributes().get(AWS_STREAM_NAME)); + } else if (isKeyPresent(span, AWS_TABLE_NAME)) { + return Optional.ofNullable(span.getAttributes().get(AWS_TABLE_NAME)); + } + return Optional.empty(); + } + /** Service is always derived from {@link ResourceAttributes#SERVICE_NAME} */ private static void setService(Resource resource, SpanData span, AttributesBuilder builder) { String service = resource.getAttribute(SERVICE_NAME); @@ -94,14 +134,34 @@ private static void setService(Resource resource, SpanData span, AttributesBuild * name. */ private static void setIngressOperation(SpanData span, AttributesBuilder builder) { - String operation = span.getName(); - if (operation == null) { + String operation; + if (!isValidOperation(span)) { + operation = generateIngressOperation(span); + } else { + operation = span.getName(); + } + if (operation.equals(UNKNOWN_OPERATION)) { logUnknownAttribute(AWS_LOCAL_OPERATION, span); - operation = UNKNOWN_OPERATION; } builder.put(AWS_LOCAL_OPERATION, operation); } + /** + * When Span name is null, UnknownOperation or HttpMethod value, it will be treated as invalid + * local operation value that needs to be further processed + */ + private static boolean isValidOperation(SpanData span) { + String operation = span.getName(); + if (operation == null || operation.equals(UNKNOWN_OPERATION)) { + return false; + } + if (isKeyPresent(span, HTTP_METHOD)) { + String httpMethod = span.getAttributes().get(HTTP_METHOD); + return !operation.equals(httpMethod); + } + return true; + } + /** * Egress operation (i.e. operation for Client and Producer spans) is always derived from a * special span attribute, {@link AwsAttributeKeys#AWS_LOCAL_OPERATION}. This attribute is @@ -152,35 +212,130 @@ private static void setEgressOperation(SpanData span, AttributesBuilder builder) * and RPC attributes to use here, but this is a sufficient starting point. */ private static void setRemoteServiceAndOperation(SpanData span, AttributesBuilder builder) { + String remoteService; + String remoteOperation; if (isKeyPresent(span, AWS_REMOTE_SERVICE) || isKeyPresent(span, AWS_REMOTE_OPERATION)) { - setRemoteService(span, builder, AWS_REMOTE_SERVICE); - setRemoteOperation(span, builder, AWS_REMOTE_OPERATION); + remoteService = getRemoteService(span, AWS_REMOTE_SERVICE); + remoteOperation = getRemoteOperation(span, AWS_REMOTE_OPERATION); } else if (isKeyPresent(span, RPC_SERVICE) || isKeyPresent(span, RPC_METHOD)) { - setRemoteService(span, builder, RPC_SERVICE); - setRemoteOperation(span, builder, RPC_METHOD); + remoteService = getRemoteService(span, RPC_SERVICE); + remoteOperation = getRemoteOperation(span, RPC_METHOD); } else if (isKeyPresent(span, DB_SYSTEM) || isKeyPresent(span, DB_OPERATION)) { - setRemoteService(span, builder, DB_SYSTEM); - setRemoteOperation(span, builder, DB_OPERATION); - } else if (isKeyPresent(span, FAAS_INVOKED_PROVIDER) || isKeyPresent(span, FAAS_INVOKED_NAME)) { - setRemoteService(span, builder, FAAS_INVOKED_PROVIDER); - setRemoteOperation(span, builder, FAAS_INVOKED_NAME); + remoteService = getRemoteService(span, DB_SYSTEM); + remoteOperation = getRemoteOperation(span, DB_OPERATION); + } else if (isKeyPresent(span, FAAS_INVOKED_NAME) || isKeyPresent(span, FAAS_TRIGGER)) { + remoteService = getRemoteService(span, FAAS_INVOKED_NAME); + remoteOperation = getRemoteOperation(span, FAAS_TRIGGER); } else if (isKeyPresent(span, MESSAGING_SYSTEM) || isKeyPresent(span, MESSAGING_OPERATION)) { - setRemoteService(span, builder, MESSAGING_SYSTEM); - setRemoteOperation(span, builder, MESSAGING_OPERATION); + remoteService = getRemoteService(span, MESSAGING_SYSTEM); + remoteOperation = getRemoteOperation(span, MESSAGING_OPERATION); } else if (isKeyPresent(span, GRAPHQL_OPERATION_TYPE)) { - builder.put(AWS_REMOTE_SERVICE, GRAPHQL); - setRemoteOperation(span, builder, GRAPHQL_OPERATION_TYPE); + remoteService = GRAPHQL; + remoteOperation = getRemoteOperation(span, GRAPHQL_OPERATION_TYPE); } else { - logUnknownAttribute(AWS_REMOTE_SERVICE, span); - builder.put(AWS_REMOTE_SERVICE, UNKNOWN_REMOTE_SERVICE); - logUnknownAttribute(AWS_REMOTE_OPERATION, span); - builder.put(AWS_REMOTE_OPERATION, UNKNOWN_REMOTE_OPERATION); + // try to derive RemoteService and RemoteOperation from the other un-directive attributes + remoteService = generateRemoteService(span); + remoteOperation = generateRemoteOperation(span); } // Peer service takes priority as RemoteService over everything but AWS Remote. if (isKeyPresent(span, PEER_SERVICE) && !isKeyPresent(span, AWS_REMOTE_SERVICE)) { - setRemoteService(span, builder, PEER_SERVICE); + remoteService = getRemoteService(span, PEER_SERVICE); + } + + if (remoteService.equals(UNKNOWN_REMOTE_SERVICE)) { + logUnknownAttribute(AWS_REMOTE_SERVICE, span); + } + if (remoteOperation.equals(UNKNOWN_REMOTE_OPERATION)) { + logUnknownAttribute(AWS_REMOTE_OPERATION, span); + } + + builder.put(AWS_REMOTE_SERVICE, remoteService); + builder.put(AWS_REMOTE_OPERATION, remoteOperation); + } + + /** + * When span name is not meaningful(null, unknown or http_method value) as operation name for http + * use cases. Will try to extract the operation name from http target string + */ + private static String generateIngressOperation(SpanData span) { + String operation = UNKNOWN_OPERATION; + if (isKeyPresent(span, HTTP_TARGET)) { + String httpTarget = span.getAttributes().get(HTTP_TARGET); + // get the first part from API path string as operation value + // the more levels/parts we get from API path the higher chance for getting high cardinality + // data + if (httpTarget != null) { + operation = extractAPIPathValue(httpTarget); + } + if (isKeyPresent(span, HTTP_METHOD)) { + String httpMethod = span.getAttributes().get(HTTP_METHOD); + if (httpMethod != null) { + operation = httpMethod + " " + operation; + } + } } + return operation; + } + + /** + * When the remote call operation is undetermined for http use cases, will try to extract the + * remote operation name from http url string + */ + private static String generateRemoteOperation(SpanData span) { + String remoteOperation = UNKNOWN_REMOTE_OPERATION; + if (isKeyPresent(span, HTTP_URL)) { + String httpUrl = span.getAttributes().get(HTTP_URL); + try { + URL url; + if (httpUrl != null) { + url = new URL(httpUrl); + remoteOperation = extractAPIPathValue(url.getPath()); + } + } catch (MalformedURLException e) { + logger.log(Level.FINEST, "invalid http.url attribute: ", httpUrl); + } + } + if (isKeyPresent(span, HTTP_METHOD)) { + String httpMethod = span.getAttributes().get(HTTP_METHOD); + remoteOperation = httpMethod + " " + remoteOperation; + } + return remoteOperation; + } + + /** + * Extract the first part from API http target if it exists + * + * @param httpTarget http request target string value. Eg, /payment/1234 + * @return the first part from the http target. Eg, /payment + */ + private static String extractAPIPathValue(String httpTarget) { + if (httpTarget.isEmpty()) { + return "/"; + } + String[] paths = httpTarget.split("/"); + if (paths.length > 1) { + return "/" + paths[1]; + } + return "/"; + } + + private static String generateRemoteService(SpanData span) { + String remoteService = UNKNOWN_REMOTE_SERVICE; + if (isKeyPresent(span, NET_PEER_NAME)) { + remoteService = getRemoteService(span, NET_PEER_NAME); + if (isKeyPresent(span, NET_PEER_PORT)) { + Long port = span.getAttributes().get(NET_PEER_PORT); + remoteService += ":" + port; + } + } else if (isKeyPresent(span, NET_SOCK_PEER_ADDR)) { + remoteService = getRemoteService(span, NET_SOCK_PEER_ADDR); + if (isKeyPresent(span, NET_SOCK_PEER_PORT)) { + Long port = span.getAttributes().get(NET_SOCK_PEER_PORT); + remoteService += ":" + port; + } + } + return remoteService; } /** Span kind is needed for differentiating metrics in the EMF exporter */ @@ -189,28 +344,24 @@ private static void setSpanKind(SpanData span, AttributesBuilder builder) { builder.put(AWS_SPAN_KIND, spanKind); } - private static boolean isKeyPresent(SpanData span, AttributeKey key) { + private static boolean isKeyPresent(SpanData span, AttributeKey key) { return span.getAttributes().get(key) != null; } - private static void setRemoteService( - SpanData span, AttributesBuilder builder, AttributeKey remoteServiceKey) { + private static String getRemoteService(SpanData span, AttributeKey remoteServiceKey) { String remoteService = span.getAttributes().get(remoteServiceKey); if (remoteService == null) { - logUnknownAttribute(AWS_REMOTE_SERVICE, span); remoteService = UNKNOWN_REMOTE_SERVICE; } - builder.put(AWS_REMOTE_SERVICE, remoteService); + return remoteService; } - private static void setRemoteOperation( - SpanData span, AttributesBuilder builder, AttributeKey remoteOperationKey) { + private static String getRemoteOperation(SpanData span, AttributeKey remoteOperationKey) { String remoteOperation = span.getAttributes().get(remoteOperationKey); if (remoteOperation == null) { - logUnknownAttribute(AWS_REMOTE_OPERATION, span); remoteOperation = UNKNOWN_REMOTE_OPERATION; } - builder.put(AWS_REMOTE_OPERATION, remoteOperation); + return remoteOperation; } private static void logUnknownAttribute(AttributeKey attributeKey, SpanData span) { diff --git a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGeneratorTest.java b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGeneratorTest.java index c7d0a7c5a..b1df48daa 100644 --- a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGeneratorTest.java +++ b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGeneratorTest.java @@ -5,19 +5,32 @@ package io.opentelemetry.contrib.awsxray; +import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_BUCKET_NAME; import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_LOCAL_OPERATION; import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_LOCAL_SERVICE; +import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_QUEUE_NAME; import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_REMOTE_OPERATION; import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_REMOTE_SERVICE; +import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_REMOTE_TARGET; import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_SPAN_KIND; +import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_STREAM_NAME; +import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_TABLE_NAME; import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.SERVICE_NAME; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.DB_OPERATION; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.DB_SYSTEM; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.FAAS_INVOKED_NAME; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.FAAS_INVOKED_PROVIDER; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.FAAS_TRIGGER; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.GRAPHQL_OPERATION_TYPE; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_METHOD; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_TARGET; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_URL; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_OPERATION; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_SYSTEM; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NET_PEER_NAME; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NET_PEER_PORT; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NET_SOCK_PEER_ADDR; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NET_SOCK_PEER_PORT; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.PEER_SERVICE; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.RPC_METHOD; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.RPC_SERVICE; @@ -140,6 +153,54 @@ public void testServerSpanWithAttributes() { validateAttributesProducedForSpanOfKind(expectedAttributes, SpanKind.SERVER); } + @Test + public void testServerSpanWithNullSpanName() { + updateResourceWithServiceName(); + when(spanDataMock.getName()).thenReturn(null); + + Attributes expectedAttributes = + Attributes.of( + AWS_SPAN_KIND, SpanKind.SERVER.name(), + AWS_LOCAL_SERVICE, SERVICE_NAME_VALUE, + AWS_LOCAL_OPERATION, UNKNOWN_OPERATION); + validateAttributesProducedForSpanOfKind(expectedAttributes, SpanKind.SERVER); + } + + @Test + public void testServerSpanWithSpanNameAsHttpMethod() { + updateResourceWithServiceName(); + when(spanDataMock.getName()).thenReturn("GET"); + mockAttribute(HTTP_METHOD, "GET"); + + Attributes expectedAttributes = + Attributes.of( + AWS_SPAN_KIND, SpanKind.SERVER.name(), + AWS_LOCAL_SERVICE, SERVICE_NAME_VALUE, + AWS_LOCAL_OPERATION, UNKNOWN_OPERATION); + validateAttributesProducedForSpanOfKind(expectedAttributes, SpanKind.SERVER); + mockAttribute(HTTP_METHOD, null); + } + + @Test + public void testServerSpanWithSpanNameWithHttpTarget() { + updateResourceWithServiceName(); + when(spanDataMock.getName()).thenReturn("POST"); + mockAttribute(HTTP_METHOD, "POST"); + mockAttribute(HTTP_TARGET, "/payment/123"); + + Attributes expectedAttributes = + Attributes.of( + AWS_SPAN_KIND, + SpanKind.SERVER.name(), + AWS_LOCAL_SERVICE, + SERVICE_NAME_VALUE, + AWS_LOCAL_OPERATION, + "POST /payment"); + validateAttributesProducedForSpanOfKind(expectedAttributes, SpanKind.SERVER); + mockAttribute(HTTP_METHOD, null); + mockAttribute(HTTP_TARGET, null); + } + @Test public void testProducerSpanWithAttributes() { updateResourceWithServiceName(); @@ -210,7 +271,7 @@ public void testRemoteAttributesCombinations() { // Validate behaviour of various combinations of FAAS attributes, then remove them. validateAndRemoveRemoteAttributes( - FAAS_INVOKED_PROVIDER, "FAAS invoked provider", FAAS_INVOKED_NAME, "FAAS invoked name"); + FAAS_INVOKED_NAME, "FAAS invoked name", FAAS_TRIGGER, "FAAS trigger name"); // Validate behaviour of various combinations of Messaging attributes, then remove them. validateAndRemoveRemoteAttributes( @@ -221,6 +282,46 @@ public void testRemoteAttributesCombinations() { validateExpectedRemoteAttributes("graphql", "GraphQL operation type"); mockAttribute(GRAPHQL_OPERATION_TYPE, null); + // Validate behaviour of extracting Remote Service from net.peer.name + mockAttribute(NET_PEER_NAME, "www.example.com"); + validateExpectedRemoteAttributes("www.example.com", UNKNOWN_REMOTE_OPERATION); + mockAttribute(NET_PEER_NAME, null); + + // Validate behaviour of extracting Remote Service from net.peer.name and net.peer.port + mockAttribute(NET_PEER_NAME, "192.168.0.0"); + mockAttribute(NET_PEER_PORT, 8081L); + validateExpectedRemoteAttributes("192.168.0.0:8081", UNKNOWN_REMOTE_OPERATION); + mockAttribute(NET_PEER_NAME, null); + mockAttribute(NET_PEER_PORT, null); + + // Validate behaviour of extracting Remote Service from net.peer.socket.addr + mockAttribute(NET_SOCK_PEER_ADDR, "www.example.com"); + validateExpectedRemoteAttributes("www.example.com", UNKNOWN_REMOTE_OPERATION); + mockAttribute(NET_SOCK_PEER_ADDR, null); + + // Validate behaviour of extracting Remote Service from net.peer.socket.addr and + // net.sock.peer.port + mockAttribute(NET_SOCK_PEER_ADDR, "192.168.0.0"); + mockAttribute(NET_SOCK_PEER_PORT, 8081L); + validateExpectedRemoteAttributes("192.168.0.0:8081", UNKNOWN_REMOTE_OPERATION); + mockAttribute(NET_SOCK_PEER_ADDR, null); + mockAttribute(NET_SOCK_PEER_PORT, null); + + // Validate behavior of Remote Operation from HttpTarget - with 1st api part, then remove it + mockAttribute(HTTP_URL, "http://www.example.com/payment/123"); + validateExpectedRemoteAttributes(UNKNOWN_REMOTE_SERVICE, "/payment"); + mockAttribute(HTTP_URL, null); + + // Validate behavior of Remote Operation from HttpTarget - without 1st api part, then remove it + mockAttribute(HTTP_URL, "http://www.example.com"); + validateExpectedRemoteAttributes(UNKNOWN_REMOTE_SERVICE, "/"); + mockAttribute(HTTP_URL, null); + + // Validate behavior of Remote Operation from HttpTarget - invalid url, then remove it + mockAttribute(HTTP_URL, "abc"); + validateExpectedRemoteAttributes(UNKNOWN_REMOTE_SERVICE, UNKNOWN_REMOTE_OPERATION); + mockAttribute(HTTP_URL, null); + // Validate behaviour of Peer service attribute, then remove it. mockAttribute(PEER_SERVICE, "Peer service"); validateExpectedRemoteAttributes("Peer service", UNKNOWN_REMOTE_OPERATION); @@ -237,6 +338,8 @@ public void testPeerServiceDoesOverrideOtherRemoteServices() { validatePeerServiceDoesOverride(FAAS_INVOKED_PROVIDER); validatePeerServiceDoesOverride(MESSAGING_SYSTEM); validatePeerServiceDoesOverride(GRAPHQL_OPERATION_TYPE); + validatePeerServiceDoesOverride(NET_PEER_NAME); + validatePeerServiceDoesOverride(NET_SOCK_PEER_ADDR); // Actually testing that peer service overrides "UnknownRemoteService". validatePeerServiceDoesOverride(AttributeKey.stringKey("unknown.service.key")); } @@ -252,7 +355,30 @@ public void testPeerServiceDoesNotOverrideAwsRemoteService() { assertThat(actualAttributes.get(AWS_REMOTE_SERVICE)).isEqualTo("TestString"); } - private void mockAttribute(AttributeKey key, String value) { + @Test + public void testClientSpanWithRemoteTargetAttributes() { + // Validate behaviour of aws bucket name attribute, then remove it. + mockAttribute(AWS_BUCKET_NAME, "aws_s3_bucket_name"); + validateRemoteTargetAttributes(AWS_REMOTE_TARGET, "aws_s3_bucket_name"); + mockAttribute(AWS_BUCKET_NAME, null); + + // Validate behaviour of AWS_QUEUE_NAME attribute, then remove it. + mockAttribute(AWS_QUEUE_NAME, "aws_queue_name"); + validateRemoteTargetAttributes(AWS_REMOTE_TARGET, "aws_queue_name"); + mockAttribute(AWS_QUEUE_NAME, null); + + // Validate behaviour of AWS_STREAM_NAME attribute, then remove it. + mockAttribute(AWS_STREAM_NAME, "aws_stream_name"); + validateRemoteTargetAttributes(AWS_REMOTE_TARGET, "aws_stream_name"); + mockAttribute(AWS_STREAM_NAME, null); + + // Validate behaviour of AWS_TABLE_NAME attribute, then remove it. + mockAttribute(AWS_TABLE_NAME, "aws_table_name"); + validateRemoteTargetAttributes(AWS_REMOTE_TARGET, "aws_table_name"); + mockAttribute(AWS_TABLE_NAME, null); + } + + private void mockAttribute(AttributeKey key, T value) { when(attributesMock.get(key)).thenReturn(value); } @@ -316,4 +442,26 @@ private void validatePeerServiceDoesOverride(AttributeKey remoteServiceK mockAttribute(remoteServiceKey, null); mockAttribute(PEER_SERVICE, null); } + + private void validateRemoteTargetAttributes( + AttributeKey remoteTargetKey, String remoteTarget) { + // Client and Producer spans should generate the expected RemoteTarget attribute + when(spanDataMock.getKind()).thenReturn(SpanKind.CLIENT); + Attributes actualAttributes = + GENERATOR.generateMetricAttributesFromSpan(spanDataMock, resource); + assertThat(actualAttributes.get(remoteTargetKey)).isEqualTo(remoteTarget); + + when(spanDataMock.getKind()).thenReturn(SpanKind.PRODUCER); + actualAttributes = GENERATOR.generateMetricAttributesFromSpan(spanDataMock, resource); + assertThat(actualAttributes.get(remoteTargetKey)).isEqualTo(remoteTarget); + + // Server and Consumer span should not generate RemoteTarget attribute + when(spanDataMock.getKind()).thenReturn(SpanKind.SERVER); + actualAttributes = GENERATOR.generateMetricAttributesFromSpan(spanDataMock, resource); + assertThat(actualAttributes.get(remoteTargetKey)).isEqualTo(null); + + when(spanDataMock.getKind()).thenReturn(SpanKind.CONSUMER); + actualAttributes = GENERATOR.generateMetricAttributesFromSpan(spanDataMock, resource); + assertThat(actualAttributes.get(remoteTargetKey)).isEqualTo(null); + } }