Inject ObjectProvider into BeanPostProcessors to avoid premature initialization of OpenTelemetry (#3171)
This commit is contained in:
parent
7c3c8883a8
commit
7ef31a0169
|
@ -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<OpenTelemetry> openTelemetryProvider) {
|
||||
return new RestTemplateBeanPostProcessor(openTelemetryProvider);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<OpenTelemetry> openTelemetryProvider;
|
||||
|
||||
RestTemplateBeanPostProcessor(OpenTelemetry openTelemetry) {
|
||||
this.openTelemetry = openTelemetry;
|
||||
RestTemplateBeanPostProcessor(ObjectProvider<OpenTelemetry> 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<ClientHttpRequestInterceptor> restTemplateInterceptors = restTemplate.getInterceptors();
|
||||
if (restTemplateInterceptors.stream()
|
||||
.noneMatch(interceptor -> interceptor instanceof RestTemplateInterceptor)) {
|
||||
|
|
|
@ -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<OpenTelemetry> openTelemetryProvider) {
|
||||
return new WebClientBeanPostProcessor(openTelemetryProvider);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<OpenTelemetry> openTelemetryProvider;
|
||||
|
||||
WebClientBeanPostProcessor(OpenTelemetry openTelemetry) {
|
||||
this.openTelemetry = openTelemetry;
|
||||
WebClientBeanPostProcessor(ObjectProvider<OpenTelemetry> 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) {
|
||||
ObjectProvider<OpenTelemetry> openTelemetryProvider, WebClient.Builder webClientBuilder) {
|
||||
|
||||
OpenTelemetry openTelemetry = openTelemetryProvider.getIfUnique();
|
||||
if (openTelemetry != null) {
|
||||
return webClientBuilder.filters(webClientFilterFunctionConsumer(openTelemetry));
|
||||
} else {
|
||||
return webClientBuilder;
|
||||
}
|
||||
}
|
||||
|
||||
private static Consumer<List<ExchangeFilterFunction>> webClientFilterFunctionConsumer(
|
||||
|
|
|
@ -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<OpenTelemetry> 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<OpenTelemetry> 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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue