diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/resttemplate/RestTemplateAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/resttemplate/RestTemplateAutoConfiguration.java index 3a43632aec..d6d1e79a10 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/resttemplate/RestTemplateAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/resttemplate/RestTemplateAutoConfiguration.java @@ -7,6 +7,7 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.restte import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.HttpClientsProperties; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -30,7 +31,7 @@ public class RestTemplateAutoConfiguration { @Bean public RestTemplateBeanPostProcessor otelRestTemplateBeanPostProcessor( - OpenTelemetry openTelemetry) { - return new RestTemplateBeanPostProcessor(openTelemetry); + ObjectProvider openTelemetryProvider) { + return new RestTemplateBeanPostProcessor(openTelemetryProvider); } } 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 7be50a5cd5..f3796e2f5b 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 @@ -8,15 +8,16 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.restte import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.spring.httpclients.RestTemplateInterceptor; import java.util.List; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.web.client.RestTemplate; final class RestTemplateBeanPostProcessor implements BeanPostProcessor { - private final OpenTelemetry openTelemetry; + private final ObjectProvider openTelemetryProvider; - RestTemplateBeanPostProcessor(OpenTelemetry openTelemetry) { - this.openTelemetry = openTelemetry; + RestTemplateBeanPostProcessor(ObjectProvider openTelemetryProvider) { + this.openTelemetryProvider = openTelemetryProvider; } @Override @@ -26,11 +27,15 @@ final class RestTemplateBeanPostProcessor implements BeanPostProcessor { } RestTemplate restTemplate = (RestTemplate) bean; - addRestTemplateInterceptorIfNotPresent(restTemplate); + OpenTelemetry openTelemetry = openTelemetryProvider.getIfUnique(); + if (openTelemetry != null) { + addRestTemplateInterceptorIfNotPresent(restTemplate, openTelemetry); + } return restTemplate; } - private void addRestTemplateInterceptorIfNotPresent(RestTemplate restTemplate) { + private static void addRestTemplateInterceptorIfNotPresent( + RestTemplate restTemplate, OpenTelemetry openTelemetry) { List restTemplateInterceptors = restTemplate.getInterceptors(); if (restTemplateInterceptors.stream() .noneMatch(interceptor -> interceptor instanceof RestTemplateInterceptor)) { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/webclient/WebClientAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/webclient/WebClientAutoConfiguration.java index eb0950733e..3497aae7a1 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/webclient/WebClientAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/webclient/WebClientAutoConfiguration.java @@ -7,6 +7,7 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.webcli import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.HttpClientsProperties; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -29,7 +30,8 @@ import org.springframework.web.reactive.function.client.WebClient; public class WebClientAutoConfiguration { @Bean - public WebClientBeanPostProcessor otelWebClientBeanPostProcessor(OpenTelemetry openTelemetry) { - return new WebClientBeanPostProcessor(openTelemetry); + public WebClientBeanPostProcessor otelWebClientBeanPostProcessor( + ObjectProvider openTelemetryProvider) { + return new WebClientBeanPostProcessor(openTelemetryProvider); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/webclient/WebClientBeanPostProcessor.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/webclient/WebClientBeanPostProcessor.java index 37a16da55f..b87cd52a00 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/webclient/WebClientBeanPostProcessor.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/webclient/WebClientBeanPostProcessor.java @@ -9,6 +9,7 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.spring.webflux.client.WebClientTracingFilter; import java.util.List; import java.util.function.Consumer; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.WebClient; @@ -20,27 +21,33 @@ import org.springframework.web.reactive.function.client.WebClient; */ final class WebClientBeanPostProcessor implements BeanPostProcessor { - private final OpenTelemetry openTelemetry; + private final ObjectProvider openTelemetryProvider; - WebClientBeanPostProcessor(OpenTelemetry openTelemetry) { - this.openTelemetry = openTelemetry; + WebClientBeanPostProcessor(ObjectProvider openTelemetryProvider) { + this.openTelemetryProvider = openTelemetryProvider; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) { if (bean instanceof WebClient) { WebClient webClient = (WebClient) bean; - return wrapBuilder(openTelemetry, webClient.mutate()).build(); + return wrapBuilder(openTelemetryProvider, webClient.mutate()).build(); } else if (bean instanceof WebClient.Builder) { WebClient.Builder webClientBuilder = (WebClient.Builder) bean; - return wrapBuilder(openTelemetry, webClientBuilder); + return wrapBuilder(openTelemetryProvider, webClientBuilder); } return bean; } private static WebClient.Builder wrapBuilder( - OpenTelemetry openTelemetry, WebClient.Builder webClientBuilder) { - return webClientBuilder.filters(webClientFilterFunctionConsumer(openTelemetry)); + ObjectProvider openTelemetryProvider, WebClient.Builder webClientBuilder) { + + OpenTelemetry openTelemetry = openTelemetryProvider.getIfUnique(); + if (openTelemetry != null) { + return webClientBuilder.filters(webClientFilterFunctionConsumer(openTelemetry)); + } else { + return webClientBuilder; + } } private static Consumer> webClientFilterFunctionConsumer( 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 e92d8a2e67..fb66546dee 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 @@ -6,20 +6,33 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.resttemplate; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +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; 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.web.client.RestTemplate; @ExtendWith(MockitoExtension.class) class RestTemplateBeanPostProcessorTest { - RestTemplateBeanPostProcessor restTemplateBeanPostProcessor = - new RestTemplateBeanPostProcessor(OpenTelemetry.noop()); + @Mock ObjectProvider openTelemetryProvider; + + RestTemplateBeanPostProcessor restTemplateBeanPostProcessor; + + @BeforeEach + void setUp() { + restTemplateBeanPostProcessor = new RestTemplateBeanPostProcessor(openTelemetryProvider); + } @Test @DisplayName("when processed bean is not of type RestTemplate should return object") @@ -28,20 +41,28 @@ class RestTemplateBeanPostProcessorTest { restTemplateBeanPostProcessor.postProcessAfterInitialization( new Object(), "testObject")) .isExactlyInstanceOf(Object.class); + + verifyNoInteractions(openTelemetryProvider); } @Test @DisplayName("when processed bean is of type RestTemplate should return RestTemplate") void returnsRestTemplate() { + when(openTelemetryProvider.getIfUnique()).thenReturn(OpenTelemetry.noop()); + assertThat( restTemplateBeanPostProcessor.postProcessAfterInitialization( new RestTemplate(), "testRestTemplate")) .isInstanceOf(RestTemplate.class); + + verify(openTelemetryProvider).getIfUnique(); } @Test @DisplayName("when processed bean is of type RestTemplate should add ONE RestTemplateInterceptor") void addsRestTemplateInterceptor() { + when(openTelemetryProvider.getIfUnique()).thenReturn(OpenTelemetry.noop()); + RestTemplate restTemplate = new RestTemplate(); restTemplateBeanPostProcessor.postProcessAfterInitialization(restTemplate, "testRestTemplate"); @@ -53,5 +74,26 @@ class RestTemplateBeanPostProcessorTest { .filter(rti -> rti instanceof RestTemplateInterceptor) .count()) .isEqualTo(1); + + verify(openTelemetryProvider, times(3)).getIfUnique(); + } + + @Test + @DisplayName("when OpenTelemetry is not available should NOT add RestTemplateInterceptor") + void doesNotAddRestTemplateInterceptorIfOpenTelemetryUnavailable() { + when(openTelemetryProvider.getIfUnique()).thenReturn(null); + RestTemplate restTemplate = new RestTemplate(); + + restTemplateBeanPostProcessor.postProcessAfterInitialization(restTemplate, "testRestTemplate"); + restTemplateBeanPostProcessor.postProcessAfterInitialization(restTemplate, "testRestTemplate"); + restTemplateBeanPostProcessor.postProcessAfterInitialization(restTemplate, "testRestTemplate"); + + assertThat( + restTemplate.getInterceptors().stream() + .filter(rti -> rti instanceof RestTemplateInterceptor) + .count()) + .isEqualTo(0); + + verify(openTelemetryProvider, times(3)).getIfUnique(); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/webclient/WebClientBeanPostProcessorTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/webclient/WebClientBeanPostProcessorTest.java index 1562611c62..2147f2443d 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/webclient/WebClientBeanPostProcessorTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/webclient/WebClientBeanPostProcessorTest.java @@ -6,20 +6,33 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.webclient; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.spring.webflux.client.WebClientTracingFilter; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; 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.web.reactive.function.client.WebClient; @ExtendWith(MockitoExtension.class) class WebClientBeanPostProcessorTest { - WebClientBeanPostProcessor webClientBeanPostProcessor = - new WebClientBeanPostProcessor(OpenTelemetry.noop()); + @Mock ObjectProvider openTelemetryProvider; + + WebClientBeanPostProcessor webClientBeanPostProcessor; + + @BeforeEach + void setUp() { + webClientBeanPostProcessor = new WebClientBeanPostProcessor(openTelemetryProvider); + } @Test @DisplayName( @@ -29,29 +42,41 @@ class WebClientBeanPostProcessorTest { assertThat( webClientBeanPostProcessor.postProcessAfterInitialization(new Object(), "testObject")) .isExactlyInstanceOf(Object.class); + + verifyNoInteractions(openTelemetryProvider); } @Test @DisplayName("when processed bean is of type WebClient should return WebClient") void returnsWebClient() { + when(openTelemetryProvider.getIfUnique()).thenReturn(OpenTelemetry.noop()); + assertThat( webClientBeanPostProcessor.postProcessAfterInitialization( WebClient.create(), "testWebClient")) .isInstanceOf(WebClient.class); + + verify(openTelemetryProvider).getIfUnique(); } @Test @DisplayName("when processed bean is of type WebClientBuilder should return WebClientBuilder") void returnsWebClientBuilder() { + when(openTelemetryProvider.getIfUnique()).thenReturn(OpenTelemetry.noop()); + assertThat( webClientBeanPostProcessor.postProcessAfterInitialization( WebClient.builder(), "testWebClientBuilder")) .isInstanceOf(WebClient.Builder.class); + + verify(openTelemetryProvider).getIfUnique(); } @Test @DisplayName("when processed bean is of type WebClient should add exchange filter to WebClient") void addsExchangeFilterWebClient() { + when(openTelemetryProvider.getIfUnique()).thenReturn(OpenTelemetry.noop()); + WebClient webClient = WebClient.create(); Object processedWebClient = webClientBeanPostProcessor.postProcessAfterInitialization(webClient, "testWebClient"); @@ -67,12 +92,40 @@ class WebClientBeanPostProcessorTest { .count()) .isEqualTo(1); }); + + verify(openTelemetryProvider).getIfUnique(); + } + + @Test + @DisplayName( + "when processed bean is of type WebClient and OpenTelemetry is not available should NOT add exchange filter to WebClient") + void doesNotAddExchangeFilterWebClientIfOpenTelemetryUnavailable() { + when(openTelemetryProvider.getIfUnique()).thenReturn(null); + + WebClient webClient = WebClient.create(); + Object processedWebClient = + webClientBeanPostProcessor.postProcessAfterInitialization(webClient, "testWebClient"); + + assertThat(processedWebClient).isInstanceOf(WebClient.class); + ((WebClient) processedWebClient) + .mutate() + .filters( + functions -> { + assertThat( + functions.stream() + .filter(wctf -> wctf instanceof WebClientTracingFilter) + .count()) + .isEqualTo(0); + }); + + verify(openTelemetryProvider).getIfUnique(); } @Test @DisplayName( "when processed bean is of type WebClientBuilder should add ONE exchange filter to WebClientBuilder") void addsExchangeFilterWebClientBuilder() { + when(openTelemetryProvider.getIfUnique()).thenReturn(OpenTelemetry.noop()); WebClient.Builder webClientBuilder = WebClient.builder(); webClientBeanPostProcessor.postProcessAfterInitialization( @@ -88,5 +141,7 @@ class WebClientBeanPostProcessorTest { functions.stream().filter(wctf -> wctf instanceof WebClientTracingFilter).count()) .isEqualTo(1); }); + + verify(openTelemetryProvider, times(3)).getIfUnique(); } }