From cbd8bb29fd1382f95600f341b46dd5c59b51b8a2 Mon Sep 17 00:00:00 2001 From: Mateusz Rzeszutek Date: Fri, 30 Jul 2021 18:28:27 +0200 Subject: [PATCH] Spring web instrumenter 2 (#3731) * Refactor spring-web library instrumentation to Instrumenter API * errorprone * fix typo --- .../RestTemplateBeanPostProcessor.java | 13 ++- .../RestTemplateBeanPostProcessorTest.java | 10 +- .../spring/spring-web-3.1/library/README.md | 39 ++++--- .../httpclients/HttpHeadersInjectAdapter.java | 19 ---- .../httpclients/RestTemplateTracer.java | 64 ----------- .../spring/web/HttpRequestSetter.java | 16 +++ .../RestTemplateInterceptor.java | 22 ++-- .../web/SpringWebHttpAttributesExtractor.java | 103 ++++++++++++++++++ .../web/SpringWebNetAttributesExtractor.java | 38 +++++++ .../spring/web/SpringWebTracing.java | 47 ++++++++ .../spring/web/SpringWebTracingBuilder.java | 64 +++++++++++ ...vy => SpringWebInstrumentationTest.groovy} | 16 ++- .../SpringWebTracingTest.java} | 8 +- 13 files changed, 334 insertions(+), 125 deletions(-) delete mode 100644 instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/httpclients/HttpHeadersInjectAdapter.java delete mode 100644 instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/httpclients/RestTemplateTracer.java create mode 100644 instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/HttpRequestSetter.java rename instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/{httpclients => web}/RestTemplateInterceptor.java (53%) create mode 100644 instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/SpringWebHttpAttributesExtractor.java create mode 100644 instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/SpringWebNetAttributesExtractor.java create mode 100644 instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/SpringWebTracing.java create mode 100644 instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/SpringWebTracingBuilder.java rename instrumentation/spring/spring-web-3.1/library/src/test/groovy/{RestTemplateInstrumentationTest.groovy => SpringWebInstrumentationTest.groovy} (76%) rename instrumentation/spring/spring-web-3.1/library/src/test/java/io/opentelemetry/instrumentation/spring/{httpclients/RestTemplateInterceptorTest.java => web/SpringWebTracingTest.java} (84%) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/resttemplate/RestTemplateBeanPostProcessor.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/resttemplate/RestTemplateBeanPostProcessor.java index f3796e2f5b..bc58ecc9d7 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/resttemplate/RestTemplateBeanPostProcessor.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/resttemplate/RestTemplateBeanPostProcessor.java @@ -6,7 +6,7 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.resttemplate; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.spring.httpclients.RestTemplateInterceptor; +import io.opentelemetry.instrumentation.spring.web.SpringWebTracing; import java.util.List; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.config.BeanPostProcessor; @@ -29,17 +29,20 @@ final class RestTemplateBeanPostProcessor implements BeanPostProcessor { RestTemplate restTemplate = (RestTemplate) bean; OpenTelemetry openTelemetry = openTelemetryProvider.getIfUnique(); if (openTelemetry != null) { - addRestTemplateInterceptorIfNotPresent(restTemplate, openTelemetry); + ClientHttpRequestInterceptor interceptor = + SpringWebTracing.create(openTelemetry).newInterceptor(); + addRestTemplateInterceptorIfNotPresent(restTemplate, interceptor); } return restTemplate; } private static void addRestTemplateInterceptorIfNotPresent( - RestTemplate restTemplate, OpenTelemetry openTelemetry) { + RestTemplate restTemplate, ClientHttpRequestInterceptor instrumentationInterceptor) { List restTemplateInterceptors = restTemplate.getInterceptors(); if (restTemplateInterceptors.stream() - .noneMatch(interceptor -> interceptor instanceof RestTemplateInterceptor)) { - restTemplateInterceptors.add(0, new RestTemplateInterceptor(openTelemetry)); + .noneMatch( + interceptor -> interceptor.getClass() == instrumentationInterceptor.getClass())) { + restTemplateInterceptors.add(0, instrumentationInterceptor); } } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/resttemplate/RestTemplateBeanPostProcessorTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/resttemplate/RestTemplateBeanPostProcessorTest.java index fb66546dee..eb2144e1e3 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/resttemplate/RestTemplateBeanPostProcessorTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/resttemplate/RestTemplateBeanPostProcessorTest.java @@ -12,7 +12,6 @@ import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.spring.httpclients.RestTemplateInterceptor; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -20,6 +19,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.web.client.RestTemplate; @ExtendWith(MockitoExtension.class) @@ -71,7 +71,7 @@ class RestTemplateBeanPostProcessorTest { assertThat( restTemplate.getInterceptors().stream() - .filter(rti -> rti instanceof RestTemplateInterceptor) + .filter(RestTemplateBeanPostProcessorTest::isOtelInstrumentationInterceptor) .count()) .isEqualTo(1); @@ -90,10 +90,14 @@ class RestTemplateBeanPostProcessorTest { assertThat( restTemplate.getInterceptors().stream() - .filter(rti -> rti instanceof RestTemplateInterceptor) + .filter(RestTemplateBeanPostProcessorTest::isOtelInstrumentationInterceptor) .count()) .isEqualTo(0); verify(openTelemetryProvider, times(3)).getIfUnique(); } + + private static boolean isOtelInstrumentationInterceptor(ClientHttpRequestInterceptor rti) { + return rti.getClass().getName().startsWith("io.opentelemetry.instrumentation"); + } } diff --git a/instrumentation/spring/spring-web-3.1/library/README.md b/instrumentation/spring/spring-web-3.1/library/README.md index ff993d4179..bc2c9c7e9b 100644 --- a/instrumentation/spring/spring-web-3.1/library/README.md +++ b/instrumentation/spring/spring-web-3.1/library/README.md @@ -9,11 +9,14 @@ Provides OpenTelemetry instrumentation for Spring's RestTemplate. Replace `SPRING_VERSION` with the version of spring you're using. `Minimum version: 3.1` -Replace `OPENTELEMETRY_VERSION` with the latest stable [release](https://mvnrepository.com/artifact/io.opentelemetry). -`Minimum version: 0.17.0` +Replace `OPENTELEMETRY_VERSION` with the latest +stable [release](https://mvnrepository.com/artifact/io.opentelemetry). +`Minimum version: 1.4.0` For Maven add to your `pom.xml`: + ```xml + @@ -22,8 +25,8 @@ For Maven add to your `pom.xml`: OPENTELEMETRY_VERSION - - + + io.opentelemetry opentelemetry-exporters-logging OPENTELEMETRY_VERSION @@ -41,6 +44,7 @@ For Maven add to your `pom.xml`: ``` For Gradle add to your dependencies: + ```groovy implementation("io.opentelemetry.instrumentation:opentelemetry-spring-web-3.1:OPENTELEMETRY_VERSION") implementation("io.opentelemetry:opentelemetry-exporters-logging:OPENTELEMETRY_VERSION") @@ -51,16 +55,17 @@ implementation("org.springframework:spring-web:SPRING_VERSION") ### Features -#### RestTemplateInterceptor +#### Telemetry-producing `ClientHttpRequestInterceptor` implementation -RestTemplateInterceptor adds OpenTelemetry client spans to requests sent using RestTemplate by implementing the [ClientHttpRequestInterceptor](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/client/ClientHttpRequestInterceptor.html) -interface. An example is shown below: +`SpringWebTracing` allows creating a +custom [ClientHttpRequestInterceptor](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/client/ClientHttpRequestInterceptor.html) +that produces telemetry for HTTP requests sent using a `RestTemplate`. Example: ##### Usage ```java -import io.opentelemetry.instrumentation.spring.httpclients.RestTemplateInterceptor; +import io.opentelemetry.instrumentation.spring.web.SpringWebTracing; import io.opentelemetry.api.OpenTelemetry; import org.springframework.beans.factory.annotation.Autowired; @@ -72,18 +77,20 @@ import org.springframework.web.client.RestTemplate; @Configuration public class RestTemplateConfig { - @Bean - public RestTemplate restTemplate(OpenTelemetry openTelemetry) { + @Bean + public RestTemplate restTemplate(OpenTelemetry openTelemetry) { - RestTemplate restTemplate = new RestTemplate(); - RestTemplateInterceptor restTemplateInterceptor = new RestTemplateInterceptor(openTelemetry); - restTemplate.getInterceptors().add(restTemplateInterceptor); + RestTemplate restTemplate = new RestTemplate(); + SpringWebTracing springWebTracing = SpringWebTracing.create(openTelemetry); + restTemplate.getInterceptors().add(springWebTracing.newInterceptor()); - return restTemplate; - } + return restTemplate; + } } ``` ### Starter Guide -Check out the opentelemetry [quick start](https://github.com/open-telemetry/opentelemetry-java/blob/master/QUICKSTART.md) to learn more about OpenTelemetry instrumentation. +Check out the +OpenTelemetry [quick start](https://github.com/open-telemetry/opentelemetry-java/blob/master/QUICKSTART.md) +to learn more about OpenTelemetry instrumentation. diff --git a/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/httpclients/HttpHeadersInjectAdapter.java b/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/httpclients/HttpHeadersInjectAdapter.java deleted file mode 100644 index 557b9a957f..0000000000 --- a/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/httpclients/HttpHeadersInjectAdapter.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.spring.httpclients; - -import io.opentelemetry.context.propagation.TextMapSetter; -import org.springframework.http.HttpHeaders; - -class HttpHeadersInjectAdapter implements TextMapSetter { - - public static final HttpHeadersInjectAdapter SETTER = new HttpHeadersInjectAdapter(); - - @Override - public void set(HttpHeaders carrier, String key, String value) { - carrier.set(key, value); - } -} diff --git a/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/httpclients/RestTemplateTracer.java b/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/httpclients/RestTemplateTracer.java deleted file mode 100644 index c63902ecd1..0000000000 --- a/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/httpclients/RestTemplateTracer.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.spring.httpclients; - -import static io.opentelemetry.instrumentation.spring.httpclients.HttpHeadersInjectAdapter.SETTER; - -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.context.propagation.TextMapSetter; -import io.opentelemetry.instrumentation.api.tracer.HttpClientTracer; -import io.opentelemetry.instrumentation.api.tracer.net.NetPeerAttributes; -import java.io.IOException; -import java.net.URI; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpRequest; -import org.springframework.http.HttpStatus; -import org.springframework.http.client.ClientHttpResponse; - -class RestTemplateTracer extends HttpClientTracer { - RestTemplateTracer(OpenTelemetry openTelemetry) { - super(openTelemetry, new NetPeerAttributes()); - } - - @Override - protected String method(HttpRequest httpRequest) { - return httpRequest.getMethod().name(); - } - - @Override - protected URI url(HttpRequest request) { - return request.getURI(); - } - - @Override - protected Integer status(ClientHttpResponse response) { - try { - return response.getStatusCode().value(); - } catch (IOException e) { - return HttpStatus.INTERNAL_SERVER_ERROR.value(); - } - } - - @Override - protected String requestHeader(HttpRequest request, String name) { - return request.getHeaders().getFirst(name); - } - - @Override - protected String responseHeader(ClientHttpResponse response, String name) { - return response.getHeaders().getFirst(name); - } - - @Override - protected TextMapSetter getSetter() { - return SETTER; - } - - @Override - protected String getInstrumentationName() { - return "io.opentelemetry.spring-web-3.1"; - } -} diff --git a/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/HttpRequestSetter.java b/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/HttpRequestSetter.java new file mode 100644 index 0000000000..74c7ef32e8 --- /dev/null +++ b/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/HttpRequestSetter.java @@ -0,0 +1,16 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.web; + +import io.opentelemetry.context.propagation.TextMapSetter; +import org.springframework.http.HttpRequest; + +final class HttpRequestSetter implements TextMapSetter { + @Override + public void set(HttpRequest httpRequest, String key, String value) { + httpRequest.getHeaders().set(key, value); + } +} diff --git a/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/httpclients/RestTemplateInterceptor.java b/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/RestTemplateInterceptor.java similarity index 53% rename from instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/httpclients/RestTemplateInterceptor.java rename to instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/RestTemplateInterceptor.java index 731aad453c..41fff145f2 100644 --- a/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/httpclients/RestTemplateInterceptor.java +++ b/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/RestTemplateInterceptor.java @@ -3,42 +3,40 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.spring.httpclients; +package io.opentelemetry.instrumentation.spring.web; -import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import java.io.IOException; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; -/** Wraps RestTemplate requests in a span. Adds the current span context to request headers. */ -public final class RestTemplateInterceptor implements ClientHttpRequestInterceptor { +final class RestTemplateInterceptor implements ClientHttpRequestInterceptor { - private final RestTemplateTracer tracer; + private final Instrumenter instrumenter; - // TODO: create a SpringWebTracing class that follows the new library instrumentation pattern - public RestTemplateInterceptor(OpenTelemetry openTelemetry) { - this.tracer = new RestTemplateTracer(openTelemetry); + RestTemplateInterceptor(Instrumenter instrumenter) { + this.instrumenter = instrumenter; } @Override public ClientHttpResponse intercept( HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { Context parentContext = Context.current(); - if (!tracer.shouldStartSpan(parentContext)) { + if (!instrumenter.shouldStart(parentContext, request)) { return execution.execute(request, body); } - Context context = tracer.startSpan(parentContext, request, request.getHeaders()); + Context context = instrumenter.start(parentContext, request); try (Scope ignored = context.makeCurrent()) { ClientHttpResponse response = execution.execute(request, body); - tracer.end(context, response); + instrumenter.end(context, request, response, null); return response; } catch (Throwable t) { - tracer.endExceptionally(context, t); + instrumenter.end(context, request, null, t); throw t; } } diff --git a/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/SpringWebHttpAttributesExtractor.java b/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/SpringWebHttpAttributesExtractor.java new file mode 100644 index 0000000000..671b337a23 --- /dev/null +++ b/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/SpringWebHttpAttributesExtractor.java @@ -0,0 +1,103 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.web; + +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpAttributesExtractor; +import java.io.IOException; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatus; +import org.springframework.http.client.ClientHttpResponse; + +final class SpringWebHttpAttributesExtractor + extends HttpAttributesExtractor { + @Override + protected String method(HttpRequest httpRequest) { + return httpRequest.getMethod().name(); + } + + @Override + protected @Nullable String url(HttpRequest httpRequest) { + return httpRequest.getURI().toString(); + } + + @Override + protected @Nullable String target(HttpRequest httpRequest) { + return null; + } + + @Override + protected @Nullable String host(HttpRequest httpRequest) { + return httpRequest.getHeaders().getFirst("host"); + } + + @Override + protected @Nullable String route(HttpRequest httpRequest) { + return null; + } + + @Override + protected @Nullable String scheme(HttpRequest httpRequest) { + return httpRequest.getURI().getScheme(); + } + + @Override + protected @Nullable String userAgent(HttpRequest httpRequest) { + // using lowercase header name intentionally to ensure extraction is not case-sensitive + return httpRequest.getHeaders().getFirst("user-agent"); + } + + @Override + protected @Nullable Long requestContentLength( + HttpRequest httpRequest, @Nullable ClientHttpResponse clientHttpResponse) { + return null; + } + + @Override + protected @Nullable Long requestContentLengthUncompressed( + HttpRequest httpRequest, @Nullable ClientHttpResponse clientHttpResponse) { + return null; + } + + @Override + protected @Nullable String flavor( + HttpRequest httpRequest, @Nullable ClientHttpResponse clientHttpResponse) { + return null; + } + + @Override + protected @Nullable String serverName( + HttpRequest httpRequest, @Nullable ClientHttpResponse clientHttpResponse) { + return null; + } + + @Override + protected @Nullable String clientIp( + HttpRequest httpRequest, @Nullable ClientHttpResponse clientHttpResponse) { + return null; + } + + @Override + protected Integer statusCode(HttpRequest httpRequest, ClientHttpResponse clientHttpResponse) { + try { + return clientHttpResponse.getStatusCode().value(); + } catch (IOException e) { + return HttpStatus.INTERNAL_SERVER_ERROR.value(); + } + } + + @Override + protected @Nullable Long responseContentLength( + HttpRequest httpRequest, ClientHttpResponse clientHttpResponse) { + return null; + } + + @Override + protected @Nullable Long responseContentLengthUncompressed( + HttpRequest httpRequest, ClientHttpResponse clientHttpResponse) { + return null; + } +} diff --git a/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/SpringWebNetAttributesExtractor.java b/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/SpringWebNetAttributesExtractor.java new file mode 100644 index 0000000000..09141a106c --- /dev/null +++ b/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/SpringWebNetAttributesExtractor.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.web; + +import io.opentelemetry.instrumentation.api.instrumenter.net.NetAttributesExtractor; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpResponse; + +final class SpringWebNetAttributesExtractor + extends NetAttributesExtractor { + @Override + public String transport(HttpRequest httpRequest) { + return SemanticAttributes.NetTransportValues.IP_TCP; + } + + @Override + public @Nullable String peerName( + HttpRequest httpRequest, @Nullable ClientHttpResponse clientHttpResponse) { + return httpRequest.getURI().getHost(); + } + + @Override + public Integer peerPort( + HttpRequest httpRequest, @Nullable ClientHttpResponse clientHttpResponse) { + return httpRequest.getURI().getPort(); + } + + @Override + public @Nullable String peerIp( + HttpRequest httpRequest, @Nullable ClientHttpResponse clientHttpResponse) { + return null; + } +} diff --git a/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/SpringWebTracing.java b/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/SpringWebTracing.java new file mode 100644 index 0000000000..f850a94cf7 --- /dev/null +++ b/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/SpringWebTracing.java @@ -0,0 +1,47 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.web; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.web.client.RestTemplate; + +/** Entrypoint for tracing Spring {@link org.springframework.web.client.RestTemplate}. */ +public final class SpringWebTracing { + + /** Returns a new {@link SpringWebTracing} configured with the given {@link OpenTelemetry}. */ + public static SpringWebTracing create(OpenTelemetry openTelemetry) { + return newBuilder(openTelemetry).build(); + } + + /** + * Returns a new {@link SpringWebTracingBuilder} configured with the given {@link OpenTelemetry}. + */ + public static SpringWebTracingBuilder newBuilder(OpenTelemetry openTelemetry) { + return new SpringWebTracingBuilder(openTelemetry); + } + + private final Instrumenter instrumenter; + + SpringWebTracing(Instrumenter instrumenter) { + this.instrumenter = instrumenter; + } + + /** + * Returns a new {@link ClientHttpRequestInterceptor} that can be used with {@link + * RestTemplate#getInterceptors()}. For example: + * + *
{@code
+   * restTemplate.getInterceptors().add(SpringWebTracing.create(openTelemetry).newInterceptor());
+   * }
+ */ + public ClientHttpRequestInterceptor newInterceptor() { + return new RestTemplateInterceptor(instrumenter); + } +} diff --git a/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/SpringWebTracingBuilder.java b/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/SpringWebTracingBuilder.java new file mode 100644 index 0000000000..54389dc4d6 --- /dev/null +++ b/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/SpringWebTracingBuilder.java @@ -0,0 +1,64 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.web; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; +import java.util.ArrayList; +import java.util.List; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpResponse; + +/** A builder of {@link SpringWebTracing}. */ +public final class SpringWebTracingBuilder { + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.spring-web-3.1"; + + private final OpenTelemetry openTelemetry; + private final List> additionalExtractors = + new ArrayList<>(); + + SpringWebTracingBuilder(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + } + + /** + * Adds an additional {@link AttributesExtractor} to invoke to set attributes to instrumented + * items. + */ + public SpringWebTracingBuilder addAttributesExtractor( + AttributesExtractor attributesExtractor) { + additionalExtractors.add(attributesExtractor); + return this; + } + + /** + * Returns a new {@link SpringWebTracing} with the settings of this {@link + * SpringWebTracingBuilder}. + */ + public SpringWebTracing build() { + SpringWebHttpAttributesExtractor httpAttributesExtractor = + new SpringWebHttpAttributesExtractor(); + SpringWebNetAttributesExtractor netAttributesExtractor = new SpringWebNetAttributesExtractor(); + + Instrumenter instrumenter = + Instrumenter.newBuilder( + openTelemetry, + INSTRUMENTATION_NAME, + HttpSpanNameExtractor.create(httpAttributesExtractor)) + .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesExtractor)) + .addAttributesExtractor(httpAttributesExtractor) + .addAttributesExtractor(netAttributesExtractor) + .addAttributesExtractors(additionalExtractors) + .addRequestMetrics(HttpClientMetrics.get()) + .newClientInstrumenter(new HttpRequestSetter()); + + return new SpringWebTracing(instrumenter); + } +} diff --git a/instrumentation/spring/spring-web-3.1/library/src/test/groovy/RestTemplateInstrumentationTest.groovy b/instrumentation/spring/spring-web-3.1/library/src/test/groovy/SpringWebInstrumentationTest.groovy similarity index 76% rename from instrumentation/spring/spring-web-3.1/library/src/test/groovy/RestTemplateInstrumentationTest.groovy rename to instrumentation/spring/spring-web-3.1/library/src/test/groovy/SpringWebInstrumentationTest.groovy index 588619da31..3e4383bee6 100644 --- a/instrumentation/spring/spring-web-3.1/library/src/test/groovy/RestTemplateInstrumentationTest.groovy +++ b/instrumentation/spring/spring-web-3.1/library/src/test/groovy/SpringWebInstrumentationTest.groovy @@ -3,10 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -import io.opentelemetry.instrumentation.spring.httpclients.RestTemplateInterceptor +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.instrumentation.spring.web.SpringWebTracing import io.opentelemetry.instrumentation.test.LibraryTestTrait import io.opentelemetry.instrumentation.test.base.HttpClientTest import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes import org.springframework.http.HttpEntity import org.springframework.http.HttpHeaders import org.springframework.http.HttpMethod @@ -14,14 +16,14 @@ import org.springframework.web.client.ResourceAccessException import org.springframework.web.client.RestTemplate import spock.lang.Shared -class RestTemplateInstrumentationTest extends HttpClientTest> implements LibraryTestTrait { +class SpringWebInstrumentationTest extends HttpClientTest> implements LibraryTestTrait { @Shared RestTemplate restTemplate def setupSpec() { if (restTemplate == null) { restTemplate = new RestTemplate() - restTemplate.getInterceptors().add(new RestTemplateInterceptor(getOpenTelemetry())) + restTemplate.getInterceptors().add(SpringWebTracing.create(getOpenTelemetry()).newInterceptor()) } } @@ -66,4 +68,12 @@ class RestTemplateInstrumentationTest extends HttpClientTest> boolean testWithClientParent() { false } + + @Override + Set> httpAttributes(URI uri) { + def attributes = super.httpAttributes(uri) + attributes.remove(SemanticAttributes.HTTP_FLAVOR) + attributes.add(SemanticAttributes.HTTP_SCHEME) + attributes + } } diff --git a/instrumentation/spring/spring-web-3.1/library/src/test/java/io/opentelemetry/instrumentation/spring/httpclients/RestTemplateInterceptorTest.java b/instrumentation/spring/spring-web-3.1/library/src/test/java/io/opentelemetry/instrumentation/spring/web/SpringWebTracingTest.java similarity index 84% rename from instrumentation/spring/spring-web-3.1/library/src/test/java/io/opentelemetry/instrumentation/spring/httpclients/RestTemplateInterceptorTest.java rename to instrumentation/spring/spring-web-3.1/library/src/test/java/io/opentelemetry/instrumentation/spring/web/SpringWebTracingTest.java index ccec443168..662f36e927 100644 --- a/instrumentation/spring/spring-web-3.1/library/src/test/java/io/opentelemetry/instrumentation/spring/httpclients/RestTemplateInterceptorTest.java +++ b/instrumentation/spring/spring-web-3.1/library/src/test/java/io/opentelemetry/instrumentation/spring/web/SpringWebTracingTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.spring.httpclients; +package io.opentelemetry.instrumentation.spring.web; import static io.opentelemetry.sdk.testing.assertj.TracesAssert.assertThat; import static org.mockito.BDDMockito.then; @@ -17,9 +17,10 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; @ExtendWith(MockitoExtension.class) -class RestTemplateInterceptorTest { +class SpringWebTracingTest { @RegisterExtension static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); @@ -30,7 +31,8 @@ class RestTemplateInterceptorTest { @Test void shouldSkipWhenContextHasClientSpan() throws Exception { // given - RestTemplateInterceptor interceptor = new RestTemplateInterceptor(testing.getOpenTelemetry()); + ClientHttpRequestInterceptor interceptor = + SpringWebTracing.create(testing.getOpenTelemetry()).newInterceptor(); // when testing.runWithClientSpan(