From 448d68092c22bfcbfbb46d0e72cfa60e103630a1 Mon Sep 17 00:00:00 2001 From: zzhlogin Date: Wed, 11 Dec 2024 11:04:16 -0800 Subject: [PATCH] Support new semantice conventions. (#972) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit *Description of changes:* This PR adding support of new semantic conventions in latest version from otel upstream: `http.url` -> `url.full` `http.method` -> `http.request.method` `http.status_code` -> `http.response.status_code` `net.peer.name` -> `server.address` `net.peer.port` -> `server.port` Testing are done for both old upstream ve Screenshot 2024-12-10 at 3 42 53 PM rsion v1.32.1 and new upstream version v2.10.0. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. --- awsagentprovider/build.gradle.kts | 4 +- .../AwsMetricAttributeGenerator.java | 46 +++++++++-- .../providers/AwsSpanMetricsProcessor.java | 9 ++- .../providers/AwsSpanProcessingUtil.java | 21 +++-- .../AwsMetricAttributeGeneratorTest.java | 81 ++++++++++++++++++- 5 files changed, 142 insertions(+), 19 deletions(-) diff --git a/awsagentprovider/build.gradle.kts b/awsagentprovider/build.gradle.kts index 3d3b76a15b..44488d3cfa 100644 --- a/awsagentprovider/build.gradle.kts +++ b/awsagentprovider/build.gradle.kts @@ -24,8 +24,8 @@ base { dependencies { compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-extension-api") - compileOnly("io.opentelemetry.semconv:opentelemetry-semconv") - testImplementation("io.opentelemetry.semconv:opentelemetry-semconv") + compileOnly("io.opentelemetry.semconv:opentelemetry-semconv:1.28.0-alpha") + testImplementation("io.opentelemetry.semconv:opentelemetry-semconv:1.28.0-alpha") compileOnly("com.google.errorprone:error_prone_annotations:2.19.1") compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") compileOnly("org.slf4j:slf4j-api") diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGenerator.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGenerator.java index 5aa8980644..3b27816cc0 100644 --- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGenerator.java +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGenerator.java @@ -25,10 +25,14 @@ import static io.opentelemetry.semconv.SemanticAttributes.FAAS_TRIGGER; import static io.opentelemetry.semconv.SemanticAttributes.GRAPHQL_OPERATION_TYPE; import static io.opentelemetry.semconv.SemanticAttributes.HTTP_METHOD; +import static io.opentelemetry.semconv.SemanticAttributes.HTTP_REQUEST_METHOD; +import static io.opentelemetry.semconv.SemanticAttributes.HTTP_RESPONSE_STATUS_CODE; import static io.opentelemetry.semconv.SemanticAttributes.HTTP_STATUS_CODE; import static io.opentelemetry.semconv.SemanticAttributes.HTTP_URL; import static io.opentelemetry.semconv.SemanticAttributes.MESSAGING_OPERATION; import static io.opentelemetry.semconv.SemanticAttributes.MESSAGING_SYSTEM; +import static io.opentelemetry.semconv.SemanticAttributes.NETWORK_PEER_ADDRESS; +import static io.opentelemetry.semconv.SemanticAttributes.NETWORK_PEER_PORT; import static io.opentelemetry.semconv.SemanticAttributes.NET_PEER_NAME; import static io.opentelemetry.semconv.SemanticAttributes.NET_PEER_PORT; import static io.opentelemetry.semconv.SemanticAttributes.NET_SOCK_PEER_ADDR; @@ -40,6 +44,7 @@ import static io.opentelemetry.semconv.SemanticAttributes.SERVER_PORT; import static io.opentelemetry.semconv.SemanticAttributes.SERVER_SOCKET_ADDRESS; import static io.opentelemetry.semconv.SemanticAttributes.SERVER_SOCKET_PORT; +import static io.opentelemetry.semconv.SemanticAttributes.URL_FULL; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_AGENT_ID; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_BUCKET_NAME; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER; @@ -292,8 +297,11 @@ private static void setRemoteServiceAndOperation(SpanData span, AttributesBuilde */ private static String generateRemoteOperation(SpanData span) { String remoteOperation = UNKNOWN_REMOTE_OPERATION; - if (isKeyPresent(span, HTTP_URL)) { - String httpUrl = span.getAttributes().get(HTTP_URL); + if (isKeyPresent(span, URL_FULL) || isKeyPresent(span, HTTP_URL)) { + String httpUrl = + isKeyPresent(span, URL_FULL) + ? span.getAttributes().get(URL_FULL) + : span.getAttributes().get(HTTP_URL); try { URL url; if (httpUrl != null) { @@ -304,8 +312,11 @@ private static String generateRemoteOperation(SpanData span) { logger.log(Level.FINEST, "invalid http.url attribute: ", httpUrl); } } - if (isKeyPresent(span, HTTP_METHOD)) { - String httpMethod = span.getAttributes().get(HTTP_METHOD); + if (isKeyPresent(span, HTTP_REQUEST_METHOD) || isKeyPresent(span, HTTP_METHOD)) { + String httpMethod = + isKeyPresent(span, HTTP_REQUEST_METHOD) + ? span.getAttributes().get(HTTP_REQUEST_METHOD) + : span.getAttributes().get(HTTP_METHOD); remoteOperation = httpMethod + " " + remoteOperation; } if (remoteOperation.equals(UNKNOWN_REMOTE_OPERATION)) { @@ -316,20 +327,35 @@ private static String generateRemoteOperation(SpanData span) { private static String generateRemoteService(SpanData span) { String remoteService = UNKNOWN_REMOTE_SERVICE; - if (isKeyPresent(span, NET_PEER_NAME)) { + if (isKeyPresent(span, SERVER_ADDRESS)) { + remoteService = getRemoteService(span, SERVER_ADDRESS); + if (isKeyPresent(span, SERVER_PORT)) { + Long port = span.getAttributes().get(SERVER_PORT); + remoteService += ":" + port; + } + } else 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, NETWORK_PEER_ADDRESS)) { + remoteService = getRemoteService(span, NETWORK_PEER_ADDRESS); + if (isKeyPresent(span, NETWORK_PEER_PORT)) { + Long port = span.getAttributes().get(NETWORK_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; } - } else if (isKeyPresent(span, HTTP_URL)) { - String httpUrl = span.getAttributes().get(HTTP_URL); + } else if (isKeyPresent(span, URL_FULL) || isKeyPresent(span, HTTP_URL)) { + String httpUrl = + isKeyPresent(span, URL_FULL) + ? span.getAttributes().get(URL_FULL) + : span.getAttributes().get(HTTP_URL); try { URL url = new URL(httpUrl); if (!url.getHost().isEmpty()) { @@ -633,6 +659,12 @@ private static void setHttpStatus(SpanData span, AttributesBuilder builder) { return; } + if (isKeyPresent(span, HTTP_RESPONSE_STATUS_CODE)) { + Long statusCode = span.getAttributes().get(HTTP_RESPONSE_STATUS_CODE); + builder.put(HTTP_STATUS_CODE, statusCode); + return; + } + Long statusCode = getAwsStatusCode(span); if (statusCode != null) { builder.put(HTTP_STATUS_CODE, statusCode); diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanMetricsProcessor.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanMetricsProcessor.java index a2a7a017f9..af987908e1 100644 --- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanMetricsProcessor.java +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanMetricsProcessor.java @@ -15,7 +15,9 @@ package software.amazon.opentelemetry.javaagent.providers; +import static io.opentelemetry.semconv.SemanticAttributes.HTTP_RESPONSE_STATUS_CODE; import static io.opentelemetry.semconv.SemanticAttributes.HTTP_STATUS_CODE; +import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.isKeyPresent; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.DoubleHistogram; @@ -129,7 +131,12 @@ public boolean isEndRequired() { // possible except for the throttle // https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/exporter/awsxrayexporter/internal/translator/cause.go#L121-L160 private void recordErrorOrFault(SpanData spanData, Attributes attributes) { - Long httpStatusCode = spanData.getAttributes().get(HTTP_STATUS_CODE); + Long httpStatusCode = null; + if (isKeyPresent(spanData, HTTP_RESPONSE_STATUS_CODE)) { + httpStatusCode = spanData.getAttributes().get(HTTP_RESPONSE_STATUS_CODE); + } else if (isKeyPresent(spanData, HTTP_STATUS_CODE)) { + httpStatusCode = spanData.getAttributes().get(HTTP_STATUS_CODE); + } StatusCode statusCode = spanData.getStatus().getStatusCode(); if (httpStatusCode == null) { diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanProcessingUtil.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanProcessingUtil.java index 4a0b22858f..a855757075 100644 --- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanProcessingUtil.java +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanProcessingUtil.java @@ -19,10 +19,12 @@ import static io.opentelemetry.semconv.SemanticAttributes.DB_STATEMENT; import static io.opentelemetry.semconv.SemanticAttributes.DB_SYSTEM; import static io.opentelemetry.semconv.SemanticAttributes.HTTP_METHOD; +import static io.opentelemetry.semconv.SemanticAttributes.HTTP_REQUEST_METHOD; import static io.opentelemetry.semconv.SemanticAttributes.HTTP_TARGET; import static io.opentelemetry.semconv.SemanticAttributes.MESSAGING_OPERATION; import static io.opentelemetry.semconv.SemanticAttributes.MessagingOperationValues.PROCESS; import static io.opentelemetry.semconv.SemanticAttributes.RPC_SYSTEM; +import static io.opentelemetry.semconv.SemanticAttributes.URL_PATH; import static software.amazon.opentelemetry.javaagent.providers.AwsApplicationSignalsCustomizerProvider.AWS_LAMBDA_FUNCTION_NAME_CONFIG; import static software.amazon.opentelemetry.javaagent.providers.AwsApplicationSignalsCustomizerProvider.isLambdaEnvironment; import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_OPERATION; @@ -200,7 +202,10 @@ private static boolean isValidOperation(SpanData span, String operation) { if (operation == null || operation.equals(UNKNOWN_OPERATION)) { return false; } - if (isKeyPresent(span, HTTP_METHOD)) { + if (isKeyPresent(span, HTTP_REQUEST_METHOD)) { + String httpMethod = span.getAttributes().get(HTTP_REQUEST_METHOD); + return !operation.equals(httpMethod); + } else if (isKeyPresent(span, HTTP_METHOD)) { String httpMethod = span.getAttributes().get(HTTP_METHOD); return !operation.equals(httpMethod); } @@ -213,15 +218,21 @@ private static boolean isValidOperation(SpanData span, String operation) { */ private static String generateIngressOperation(SpanData span) { String operation = UNKNOWN_OPERATION; - if (isKeyPresent(span, HTTP_TARGET)) { - String httpTarget = span.getAttributes().get(HTTP_TARGET); + if (isKeyPresent(span, URL_PATH) || isKeyPresent(span, HTTP_TARGET)) { + String httpTarget = + isKeyPresent(span, URL_PATH) + ? span.getAttributes().get(URL_PATH) + : 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 (isKeyPresent(span, HTTP_REQUEST_METHOD) || isKeyPresent(span, HTTP_METHOD)) { + String httpMethod = + isKeyPresent(span, HTTP_REQUEST_METHOD) + ? span.getAttributes().get(HTTP_REQUEST_METHOD) + : span.getAttributes().get(HTTP_METHOD); if (httpMethod != null) { operation = httpMethod + " " + operation; } diff --git a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGeneratorTest.java b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGeneratorTest.java index 0598aa9ec5..8309ac31f1 100644 --- a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGeneratorTest.java +++ b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGeneratorTest.java @@ -374,23 +374,25 @@ public void testServerSpanWithNullSpanName() { 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); + // Validate the span with http.method. + mockAttribute(HTTP_METHOD, "GET"); validateAttributesProducedForNonLocalRootSpanOfKind(expectedAttributes, SpanKind.SERVER); mockAttribute(HTTP_METHOD, null); + // Validate the span with http.request.method. + mockAttribute(HTTP_REQUEST_METHOD, "GET"); + validateAttributesProducedForNonLocalRootSpanOfKind(expectedAttributes, SpanKind.SERVER); + mockAttribute(HTTP_REQUEST_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( @@ -400,9 +402,18 @@ public void testServerSpanWithSpanNameWithHttpTarget() { SERVICE_NAME_VALUE, AWS_LOCAL_OPERATION, "POST /payment"); + // Validate the span with http.method and http.target. + mockAttribute(HTTP_METHOD, "POST"); + mockAttribute(HTTP_TARGET, "/payment/123"); validateAttributesProducedForNonLocalRootSpanOfKind(expectedAttributes, SpanKind.SERVER); mockAttribute(HTTP_METHOD, null); mockAttribute(HTTP_TARGET, null); + // Validate the span with http.request.method and url.path. + mockAttribute(HTTP_REQUEST_METHOD, "POST"); + mockAttribute(URL_PATH, "/payment/123"); + validateAttributesProducedForNonLocalRootSpanOfKind(expectedAttributes, SpanKind.SERVER); + mockAttribute(HTTP_REQUEST_METHOD, null); + mockAttribute(URL_PATH, null); } @Test @@ -507,6 +518,11 @@ public void testRemoteAttributesCombinations() { validateExpectedRemoteAttributes("www.example.com", UNKNOWN_REMOTE_OPERATION); mockAttribute(NET_PEER_NAME, null); + // Validate behaviour of extracting Remote Service from service.address + mockAttribute(SERVER_ADDRESS, "www.example.com"); + validateExpectedRemoteAttributes("www.example.com", UNKNOWN_REMOTE_OPERATION); + mockAttribute(SERVER_ADDRESS, 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); @@ -514,11 +530,23 @@ public void testRemoteAttributesCombinations() { mockAttribute(NET_PEER_NAME, null); mockAttribute(NET_PEER_PORT, null); + // Validate behaviour of extracting Remote Service from service.address and service.port + mockAttribute(SERVER_ADDRESS, "192.168.0.0"); + mockAttribute(SERVER_PORT, 8081L); + validateExpectedRemoteAttributes("192.168.0.0:8081", UNKNOWN_REMOTE_OPERATION); + mockAttribute(SERVER_ADDRESS, null); + mockAttribute(SERVER_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 + mockAttribute(NETWORK_PEER_ADDRESS, "www.example.com"); + validateExpectedRemoteAttributes("www.example.com", UNKNOWN_REMOTE_OPERATION); + mockAttribute(NETWORK_PEER_ADDRESS, 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"); @@ -527,43 +555,86 @@ public void testRemoteAttributesCombinations() { mockAttribute(NET_SOCK_PEER_ADDR, null); mockAttribute(NET_SOCK_PEER_PORT, null); + // Validate behaviour of extracting Remote Service from net.peer.socket.addr and + // net.sock.peer.port + mockAttribute(NETWORK_PEER_ADDRESS, "192.168.0.0"); + mockAttribute(NETWORK_PEER_PORT, 8081L); + validateExpectedRemoteAttributes("192.168.0.0:8081", UNKNOWN_REMOTE_OPERATION); + mockAttribute(NETWORK_PEER_ADDRESS, null); + mockAttribute(NETWORK_PEER_PORT, null); + // Validate behavior of Remote Operation from HttpTarget - with 1st api part. Also validates // that RemoteService is extracted from HttpUrl. mockAttribute(HTTP_URL, "http://www.example.com/payment/123"); validateExpectedRemoteAttributes("www.example.com", "/payment"); mockAttribute(HTTP_URL, null); + // that RemoteService is extracted from url.full. + mockAttribute(URL_FULL, "http://www.example.com/payment/123"); + validateExpectedRemoteAttributes("www.example.com", "/payment"); + mockAttribute(URL_FULL, null); + // Validate behavior of Remote Operation from HttpTarget - with 1st api part. Also validates // that RemoteService is extracted from HttpUrl. mockAttribute(HTTP_URL, "http://www.example.com"); validateExpectedRemoteAttributes("www.example.com", "/"); mockAttribute(HTTP_URL, null); + // that RemoteService is extracted from url.full. + mockAttribute(URL_FULL, "http://www.example.com"); + validateExpectedRemoteAttributes("www.example.com", "/"); + mockAttribute(URL_FULL, null); + // Validate behavior of Remote Service from HttpUrl mockAttribute(HTTP_URL, "http://192.168.1.1:8000"); validateExpectedRemoteAttributes("192.168.1.1:8000", "/"); mockAttribute(HTTP_URL, null); + // Validate behavior of Remote Service from url.full + mockAttribute(URL_FULL, "http://192.168.1.1:8000"); + validateExpectedRemoteAttributes("192.168.1.1:8000", "/"); + mockAttribute(URL_FULL, null); + // Validate behavior of Remote Service from HttpUrl mockAttribute(HTTP_URL, "http://192.168.1.1"); validateExpectedRemoteAttributes("192.168.1.1", "/"); mockAttribute(HTTP_URL, null); + // Validate behavior of Remote Service from url.full + mockAttribute(URL_FULL, "http://192.168.1.1"); + validateExpectedRemoteAttributes("192.168.1.1", "/"); + mockAttribute(URL_FULL, null); + // Validate behavior of Remote Service from HttpUrl mockAttribute(HTTP_URL, ""); validateExpectedRemoteAttributes(UNKNOWN_REMOTE_SERVICE, UNKNOWN_REMOTE_OPERATION); mockAttribute(HTTP_URL, null); + // Validate behavior of Remote Service from url.full + mockAttribute(URL_FULL, ""); + validateExpectedRemoteAttributes(UNKNOWN_REMOTE_SERVICE, UNKNOWN_REMOTE_OPERATION); + mockAttribute(URL_FULL, null); + // Validate behavior of Remote Service from HttpUrl mockAttribute(HTTP_URL, null); validateExpectedRemoteAttributes(UNKNOWN_REMOTE_SERVICE, UNKNOWN_REMOTE_OPERATION); mockAttribute(HTTP_URL, null); + // Validate behavior of Remote Service from url.full + mockAttribute(URL_FULL, null); + validateExpectedRemoteAttributes(UNKNOWN_REMOTE_SERVICE, UNKNOWN_REMOTE_OPERATION); + mockAttribute(URL_FULL, 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 behavior of Remote Operation from url.full - invalid url, then remove it + mockAttribute(URL_FULL, "abc"); + validateExpectedRemoteAttributes(UNKNOWN_REMOTE_SERVICE, UNKNOWN_REMOTE_OPERATION); + mockAttribute(URL_FULL, null); + // Validate behaviour of Peer service attribute, then remove it. mockAttribute(PEER_SERVICE, "Peer service"); validateExpectedRemoteAttributes("Peer service", UNKNOWN_REMOTE_OPERATION); @@ -660,7 +731,9 @@ public void testPeerServiceDoesOverrideOtherRemoteServices() { validatePeerServiceDoesOverride(MESSAGING_SYSTEM); validatePeerServiceDoesOverride(GRAPHQL_OPERATION_TYPE); validatePeerServiceDoesOverride(NET_PEER_NAME); + validatePeerServiceDoesOverride(SERVER_ADDRESS); validatePeerServiceDoesOverride(NET_SOCK_PEER_ADDR); + validatePeerServiceDoesOverride(NETWORK_PEER_ADDRESS); // Actually testing that peer service overrides "UnknownRemoteService". validatePeerServiceDoesOverride(AttributeKey.stringKey("unknown.service.key")); }