diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-instrumentation-api.txt b/docs/apidiffs/current_vs_latest/opentelemetry-instrumentation-api.txt index df26146497bf..56b8389499d5 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-instrumentation-api.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-instrumentation-api.txt @@ -1,2 +1,6 @@ Comparing source compatibility of against -No changes. \ No newline at end of file +***! MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.instrumentation.api.semconv.http.HttpClientAttributesGetter (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + GENERIC TEMPLATES: === REQUEST:java.lang.Object, === RESPONSE:java.lang.Object + +++! NEW METHOD: PUBLIC(+) java.lang.String getHttpRoute(java.lang.Object) + +++ NEW ANNOTATION: javax.annotation.Nullable diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpClientAttributesExtractor.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpClientAttributesExtractor.java index 68b9b6e43fad..6670aeb9afca 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpClientAttributesExtractor.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpClientAttributesExtractor.java @@ -75,6 +75,11 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST String fullUrl = stripSensitiveData(getter.getUrlFull(request)); internalSet(attributes, SemanticAttributes.URL_FULL, fullUrl); + String httpRoute = getter.getHttpRoute(request); + if (httpRoute != null && !httpRoute.isEmpty()) { + internalSet(attributes, SemanticAttributes.HTTP_ROUTE, httpRoute); + } + int resendCount = resendCountIncrementer.applyAsInt(parentContext); if (resendCount > 0) { attributes.put(SemanticAttributes.HTTP_REQUEST_RESEND_COUNT, resendCount); diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpClientAttributesGetter.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpClientAttributesGetter.java index d81eb582f2a0..f7c12c8786e3 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpClientAttributesGetter.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpClientAttributesGetter.java @@ -32,6 +32,16 @@ public interface HttpClientAttributesGetter @Nullable String getUrlFull(REQUEST request); + /** + * Returns the path template in the format used by the respective client framework. + * + *

Examples: /foo/{bar} + */ + @Nullable + default String getHttpRoute(REQUEST request) { + return null; + } + /** {@inheritDoc} */ @Nullable @Override diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpMetricsAdvice.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpMetricsAdvice.java index c279ce02260d..68ca52cc2872 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpMetricsAdvice.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpMetricsAdvice.java @@ -26,6 +26,7 @@ static void applyClientDurationAdvice(DoubleHistogramBuilder builder) { ((ExtendedDoubleHistogramBuilder) builder) .setAttributesAdvice( asList( + SemanticAttributes.HTTP_ROUTE, SemanticAttributes.HTTP_REQUEST_METHOD, SemanticAttributes.HTTP_RESPONSE_STATUS_CODE, SemanticAttributes.ERROR_TYPE, diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/semconv/http/HttpClientAttributesExtractorTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/semconv/http/HttpClientAttributesExtractorTest.java index c3b4f88034ea..14ebce21e995 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/semconv/http/HttpClientAttributesExtractorTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/semconv/http/HttpClientAttributesExtractorTest.java @@ -42,6 +42,12 @@ public String getUrlFull(Map request) { return request.get("urlFull"); } + @Nullable + @Override + public String getHttpRoute(Map request) { + return request.get("httpRoute"); + } + @Override public String getHttpRequestMethod(Map request) { return request.get("method"); @@ -138,6 +144,7 @@ void normal() { Map request = new HashMap<>(); request.put("method", "POST"); request.put("urlFull", "http://github.com"); + request.put("httpRoute", "/api"); request.put("header.content-length", "10"); request.put("header.user-agent", "okhttp 3.x"); request.put("header.custom-request-header", "123,456"); @@ -170,6 +177,7 @@ void normal() { .containsOnly( entry(SemanticAttributes.HTTP_REQUEST_METHOD, "POST"), entry(SemanticAttributes.URL_FULL, "http://github.com"), + entry(SemanticAttributes.HTTP_ROUTE, "/api"), entry( AttributeKey.stringArrayKey("http.request.header.custom-request-header"), asList("123", "456")), diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/internal/WebClientHttpAttributesGetter.java b/instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/internal/WebClientHttpAttributesGetter.java index 80c75f68a61d..ea1345d7ce59 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/internal/WebClientHttpAttributesGetter.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/internal/WebClientHttpAttributesGetter.java @@ -9,9 +9,12 @@ import io.opentelemetry.instrumentation.api.semconv.http.HttpClientAttributesGetter; import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; import javax.annotation.Nullable; import org.springframework.web.reactive.function.client.ClientRequest; import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.WebClient; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -21,11 +24,27 @@ public enum WebClientHttpAttributesGetter implements HttpClientAttributesGetter { INSTANCE; + private static final String URI_TEMPLATE_ATTRIBUTE = WebClient.class.getName() + ".uriTemplate"; + private static final Pattern PATTERN_BEFORE_PATH = Pattern.compile("^https?://[^/]+/"); + @Override public String getUrlFull(ClientRequest request) { return request.url().toString(); } + @Nullable + @Override + public String getHttpRoute(ClientRequest clientRequest) { + Map attributes = clientRequest.attributes(); + Object value = attributes.get(URI_TEMPLATE_ATTRIBUTE); + if (value instanceof String) { + String uriTemplate = (String) value; + String path = PATTERN_BEFORE_PATH.matcher(uriTemplate).replaceFirst(""); + return path.startsWith("/") ? path : "/" + path; + } + return null; + } + @Override public String getHttpRequestMethod(ClientRequest request) { return request.method().name(); diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.3/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/client/AbstractSpringWebfluxClientInstrumentationTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.3/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/client/AbstractSpringWebfluxClientInstrumentationTest.java index 5ca5ebb133f9..d45736558453 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.3/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/client/AbstractSpringWebfluxClientInstrumentationTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.3/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/client/AbstractSpringWebfluxClientInstrumentationTest.java @@ -23,6 +23,7 @@ import java.lang.invoke.MethodType; import java.net.URI; import java.time.Duration; +import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -44,7 +45,7 @@ public WebClient.RequestBodySpec buildRequest( return webClient .method(HttpMethod.valueOf(method)) - .uri(uri) + .uri(uri.toString(), Collections.emptyMap()) .headers(h -> headers.forEach(h::add)); } @@ -182,6 +183,7 @@ void shouldEndSpanOnMonoTimeout() { .hasAttributesSatisfyingExactly( equalTo(SemanticAttributes.HTTP_REQUEST_METHOD, "GET"), equalTo(SemanticAttributes.URL_FULL, uri.toString()), + equalTo(SemanticAttributes.HTTP_ROUTE, uri.getPath()), equalTo(SemanticAttributes.SERVER_ADDRESS, "localhost"), equalTo(SemanticAttributes.SERVER_PORT, uri.getPort()), equalTo(SemanticAttributes.ERROR_TYPE, "cancelled")),