Inject ObjectProvider into BeanPostProcessors to avoid premature initialization of OpenTelemetry (#3171)

This commit is contained in:
HaloFour 2021-06-03 17:14:17 -04:00 committed by GitHub
parent 7c3c8883a8
commit 7ef31a0169
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 132 additions and 20 deletions

View File

@ -7,6 +7,7 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.restte
import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.HttpClientsProperties; 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.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
@ -30,7 +31,7 @@ public class RestTemplateAutoConfiguration {
@Bean @Bean
public RestTemplateBeanPostProcessor otelRestTemplateBeanPostProcessor( public RestTemplateBeanPostProcessor otelRestTemplateBeanPostProcessor(
OpenTelemetry openTelemetry) { ObjectProvider<OpenTelemetry> openTelemetryProvider) {
return new RestTemplateBeanPostProcessor(openTelemetry); return new RestTemplateBeanPostProcessor(openTelemetryProvider);
} }
} }

View File

@ -8,15 +8,16 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.restte
import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.spring.httpclients.RestTemplateInterceptor; import io.opentelemetry.instrumentation.spring.httpclients.RestTemplateInterceptor;
import java.util.List; import java.util.List;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
final class RestTemplateBeanPostProcessor implements BeanPostProcessor { final class RestTemplateBeanPostProcessor implements BeanPostProcessor {
private final OpenTelemetry openTelemetry; private final ObjectProvider<OpenTelemetry> openTelemetryProvider;
RestTemplateBeanPostProcessor(OpenTelemetry openTelemetry) { RestTemplateBeanPostProcessor(ObjectProvider<OpenTelemetry> openTelemetryProvider) {
this.openTelemetry = openTelemetry; this.openTelemetryProvider = openTelemetryProvider;
} }
@Override @Override
@ -26,11 +27,15 @@ final class RestTemplateBeanPostProcessor implements BeanPostProcessor {
} }
RestTemplate restTemplate = (RestTemplate) bean; RestTemplate restTemplate = (RestTemplate) bean;
addRestTemplateInterceptorIfNotPresent(restTemplate); OpenTelemetry openTelemetry = openTelemetryProvider.getIfUnique();
if (openTelemetry != null) {
addRestTemplateInterceptorIfNotPresent(restTemplate, openTelemetry);
}
return restTemplate; return restTemplate;
} }
private void addRestTemplateInterceptorIfNotPresent(RestTemplate restTemplate) { private static void addRestTemplateInterceptorIfNotPresent(
RestTemplate restTemplate, OpenTelemetry openTelemetry) {
List<ClientHttpRequestInterceptor> restTemplateInterceptors = restTemplate.getInterceptors(); List<ClientHttpRequestInterceptor> restTemplateInterceptors = restTemplate.getInterceptors();
if (restTemplateInterceptors.stream() if (restTemplateInterceptors.stream()
.noneMatch(interceptor -> interceptor instanceof RestTemplateInterceptor)) { .noneMatch(interceptor -> interceptor instanceof RestTemplateInterceptor)) {

View File

@ -7,6 +7,7 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.webcli
import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.HttpClientsProperties; 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.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
@ -29,7 +30,8 @@ import org.springframework.web.reactive.function.client.WebClient;
public class WebClientAutoConfiguration { public class WebClientAutoConfiguration {
@Bean @Bean
public WebClientBeanPostProcessor otelWebClientBeanPostProcessor(OpenTelemetry openTelemetry) { public WebClientBeanPostProcessor otelWebClientBeanPostProcessor(
return new WebClientBeanPostProcessor(openTelemetry); ObjectProvider<OpenTelemetry> openTelemetryProvider) {
return new WebClientBeanPostProcessor(openTelemetryProvider);
} }
} }

View File

@ -9,6 +9,7 @@ import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.spring.webflux.client.WebClientTracingFilter; import io.opentelemetry.instrumentation.spring.webflux.client.WebClientTracingFilter;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient; 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 { final class WebClientBeanPostProcessor implements BeanPostProcessor {
private final OpenTelemetry openTelemetry; private final ObjectProvider<OpenTelemetry> openTelemetryProvider;
WebClientBeanPostProcessor(OpenTelemetry openTelemetry) { WebClientBeanPostProcessor(ObjectProvider<OpenTelemetry> openTelemetryProvider) {
this.openTelemetry = openTelemetry; this.openTelemetryProvider = openTelemetryProvider;
} }
@Override @Override
public Object postProcessAfterInitialization(Object bean, String beanName) { public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof WebClient) { if (bean instanceof WebClient) {
WebClient webClient = (WebClient) bean; WebClient webClient = (WebClient) bean;
return wrapBuilder(openTelemetry, webClient.mutate()).build(); return wrapBuilder(openTelemetryProvider, webClient.mutate()).build();
} else if (bean instanceof WebClient.Builder) { } else if (bean instanceof WebClient.Builder) {
WebClient.Builder webClientBuilder = (WebClient.Builder) bean; WebClient.Builder webClientBuilder = (WebClient.Builder) bean;
return wrapBuilder(openTelemetry, webClientBuilder); return wrapBuilder(openTelemetryProvider, webClientBuilder);
} }
return bean; return bean;
} }
private static WebClient.Builder wrapBuilder( private static WebClient.Builder wrapBuilder(
OpenTelemetry openTelemetry, WebClient.Builder webClientBuilder) { ObjectProvider<OpenTelemetry> openTelemetryProvider, WebClient.Builder webClientBuilder) {
OpenTelemetry openTelemetry = openTelemetryProvider.getIfUnique();
if (openTelemetry != null) {
return webClientBuilder.filters(webClientFilterFunctionConsumer(openTelemetry)); return webClientBuilder.filters(webClientFilterFunctionConsumer(openTelemetry));
} else {
return webClientBuilder;
}
} }
private static Consumer<List<ExchangeFilterFunction>> webClientFilterFunctionConsumer( private static Consumer<List<ExchangeFilterFunction>> webClientFilterFunctionConsumer(

View File

@ -6,20 +6,33 @@
package io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.resttemplate; package io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.resttemplate;
import static org.assertj.core.api.Assertions.assertThat; 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.api.OpenTelemetry;
import io.opentelemetry.instrumentation.spring.httpclients.RestTemplateInterceptor; import io.opentelemetry.instrumentation.spring.httpclients.RestTemplateInterceptor;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class RestTemplateBeanPostProcessorTest { class RestTemplateBeanPostProcessorTest {
RestTemplateBeanPostProcessor restTemplateBeanPostProcessor = @Mock ObjectProvider<OpenTelemetry> openTelemetryProvider;
new RestTemplateBeanPostProcessor(OpenTelemetry.noop());
RestTemplateBeanPostProcessor restTemplateBeanPostProcessor;
@BeforeEach
void setUp() {
restTemplateBeanPostProcessor = new RestTemplateBeanPostProcessor(openTelemetryProvider);
}
@Test @Test
@DisplayName("when processed bean is not of type RestTemplate should return object") @DisplayName("when processed bean is not of type RestTemplate should return object")
@ -28,20 +41,28 @@ class RestTemplateBeanPostProcessorTest {
restTemplateBeanPostProcessor.postProcessAfterInitialization( restTemplateBeanPostProcessor.postProcessAfterInitialization(
new Object(), "testObject")) new Object(), "testObject"))
.isExactlyInstanceOf(Object.class); .isExactlyInstanceOf(Object.class);
verifyNoInteractions(openTelemetryProvider);
} }
@Test @Test
@DisplayName("when processed bean is of type RestTemplate should return RestTemplate") @DisplayName("when processed bean is of type RestTemplate should return RestTemplate")
void returnsRestTemplate() { void returnsRestTemplate() {
when(openTelemetryProvider.getIfUnique()).thenReturn(OpenTelemetry.noop());
assertThat( assertThat(
restTemplateBeanPostProcessor.postProcessAfterInitialization( restTemplateBeanPostProcessor.postProcessAfterInitialization(
new RestTemplate(), "testRestTemplate")) new RestTemplate(), "testRestTemplate"))
.isInstanceOf(RestTemplate.class); .isInstanceOf(RestTemplate.class);
verify(openTelemetryProvider).getIfUnique();
} }
@Test @Test
@DisplayName("when processed bean is of type RestTemplate should add ONE RestTemplateInterceptor") @DisplayName("when processed bean is of type RestTemplate should add ONE RestTemplateInterceptor")
void addsRestTemplateInterceptor() { void addsRestTemplateInterceptor() {
when(openTelemetryProvider.getIfUnique()).thenReturn(OpenTelemetry.noop());
RestTemplate restTemplate = new RestTemplate(); RestTemplate restTemplate = new RestTemplate();
restTemplateBeanPostProcessor.postProcessAfterInitialization(restTemplate, "testRestTemplate"); restTemplateBeanPostProcessor.postProcessAfterInitialization(restTemplate, "testRestTemplate");
@ -53,5 +74,26 @@ class RestTemplateBeanPostProcessorTest {
.filter(rti -> rti instanceof RestTemplateInterceptor) .filter(rti -> rti instanceof RestTemplateInterceptor)
.count()) .count())
.isEqualTo(1); .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();
} }
} }

View File

@ -6,20 +6,33 @@
package io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.webclient; package io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.webclient;
import static org.assertj.core.api.Assertions.assertThat; 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.api.OpenTelemetry;
import io.opentelemetry.instrumentation.spring.webflux.client.WebClientTracingFilter; 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.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class WebClientBeanPostProcessorTest { class WebClientBeanPostProcessorTest {
WebClientBeanPostProcessor webClientBeanPostProcessor = @Mock ObjectProvider<OpenTelemetry> openTelemetryProvider;
new WebClientBeanPostProcessor(OpenTelemetry.noop());
WebClientBeanPostProcessor webClientBeanPostProcessor;
@BeforeEach
void setUp() {
webClientBeanPostProcessor = new WebClientBeanPostProcessor(openTelemetryProvider);
}
@Test @Test
@DisplayName( @DisplayName(
@ -29,29 +42,41 @@ class WebClientBeanPostProcessorTest {
assertThat( assertThat(
webClientBeanPostProcessor.postProcessAfterInitialization(new Object(), "testObject")) webClientBeanPostProcessor.postProcessAfterInitialization(new Object(), "testObject"))
.isExactlyInstanceOf(Object.class); .isExactlyInstanceOf(Object.class);
verifyNoInteractions(openTelemetryProvider);
} }
@Test @Test
@DisplayName("when processed bean is of type WebClient should return WebClient") @DisplayName("when processed bean is of type WebClient should return WebClient")
void returnsWebClient() { void returnsWebClient() {
when(openTelemetryProvider.getIfUnique()).thenReturn(OpenTelemetry.noop());
assertThat( assertThat(
webClientBeanPostProcessor.postProcessAfterInitialization( webClientBeanPostProcessor.postProcessAfterInitialization(
WebClient.create(), "testWebClient")) WebClient.create(), "testWebClient"))
.isInstanceOf(WebClient.class); .isInstanceOf(WebClient.class);
verify(openTelemetryProvider).getIfUnique();
} }
@Test @Test
@DisplayName("when processed bean is of type WebClientBuilder should return WebClientBuilder") @DisplayName("when processed bean is of type WebClientBuilder should return WebClientBuilder")
void returnsWebClientBuilder() { void returnsWebClientBuilder() {
when(openTelemetryProvider.getIfUnique()).thenReturn(OpenTelemetry.noop());
assertThat( assertThat(
webClientBeanPostProcessor.postProcessAfterInitialization( webClientBeanPostProcessor.postProcessAfterInitialization(
WebClient.builder(), "testWebClientBuilder")) WebClient.builder(), "testWebClientBuilder"))
.isInstanceOf(WebClient.Builder.class); .isInstanceOf(WebClient.Builder.class);
verify(openTelemetryProvider).getIfUnique();
} }
@Test @Test
@DisplayName("when processed bean is of type WebClient should add exchange filter to WebClient") @DisplayName("when processed bean is of type WebClient should add exchange filter to WebClient")
void addsExchangeFilterWebClient() { void addsExchangeFilterWebClient() {
when(openTelemetryProvider.getIfUnique()).thenReturn(OpenTelemetry.noop());
WebClient webClient = WebClient.create(); WebClient webClient = WebClient.create();
Object processedWebClient = Object processedWebClient =
webClientBeanPostProcessor.postProcessAfterInitialization(webClient, "testWebClient"); webClientBeanPostProcessor.postProcessAfterInitialization(webClient, "testWebClient");
@ -67,12 +92,40 @@ class WebClientBeanPostProcessorTest {
.count()) .count())
.isEqualTo(1); .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 @Test
@DisplayName( @DisplayName(
"when processed bean is of type WebClientBuilder should add ONE exchange filter to WebClientBuilder") "when processed bean is of type WebClientBuilder should add ONE exchange filter to WebClientBuilder")
void addsExchangeFilterWebClientBuilder() { void addsExchangeFilterWebClientBuilder() {
when(openTelemetryProvider.getIfUnique()).thenReturn(OpenTelemetry.noop());
WebClient.Builder webClientBuilder = WebClient.builder(); WebClient.Builder webClientBuilder = WebClient.builder();
webClientBeanPostProcessor.postProcessAfterInitialization( webClientBeanPostProcessor.postProcessAfterInitialization(
@ -88,5 +141,7 @@ class WebClientBeanPostProcessorTest {
functions.stream().filter(wctf -> wctf instanceof WebClientTracingFilter).count()) functions.stream().filter(wctf -> wctf instanceof WebClientTracingFilter).count())
.isEqualTo(1); .isEqualTo(1);
}); });
verify(openTelemetryProvider, times(3)).getIfUnique();
} }
} }