Add noop tracer bean if tracing is disabled
The new functionality doesn't expose any public API and works the same as WebTestClientContextCustomizer does. Closes gh-32907
This commit is contained in:
@@ -59,6 +59,7 @@ dependencies {
|
||||
optional("org.apache.tomcat.embed:tomcat-embed-core")
|
||||
optional("org.mongodb:mongodb-driver-reactivestreams")
|
||||
optional("org.mongodb:mongodb-driver-sync")
|
||||
optional("io.micrometer:micrometer-tracing")
|
||||
|
||||
testImplementation(project(":spring-boot-project:spring-boot-actuator"))
|
||||
testImplementation(project(":spring-boot-project:spring-boot-actuator-autoconfigure"))
|
||||
|
||||
@@ -19,17 +19,36 @@ package org.springframework.boot.test.autoconfigure.actuate.observability;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.micrometer.tracing.Tracer;
|
||||
|
||||
import org.springframework.aot.AotDetector;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.boot.test.util.TestPropertyValues;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.test.context.ContextConfigurationAttributes;
|
||||
import org.springframework.test.context.ContextCustomizer;
|
||||
import org.springframework.test.context.ContextCustomizerFactory;
|
||||
import org.springframework.test.context.MergedContextConfiguration;
|
||||
import org.springframework.test.context.TestContextAnnotationUtils;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* {@link ContextCustomizerFactory} that globally disables metrics export and tracing
|
||||
* unless {@link AutoConfigureObservability} is set on the test class.
|
||||
* <p>
|
||||
* Registers {@link Tracer#NOOP} if tracing is disabled, micrometer-tracing is on the
|
||||
* classpath, and the user hasn't supplied their own {@link Tracer}.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @author Moritz Halbritter
|
||||
@@ -44,11 +63,10 @@ class ObservabilityContextCustomizerFactory implements ContextCustomizerFactory
|
||||
if (annotation == null) {
|
||||
return new DisableObservabilityContextCustomizer(true, true);
|
||||
}
|
||||
|
||||
return new DisableObservabilityContextCustomizer(!annotation.metrics(), !annotation.tracing());
|
||||
}
|
||||
|
||||
static class DisableObservabilityContextCustomizer implements ContextCustomizer {
|
||||
private static class DisableObservabilityContextCustomizer implements ContextCustomizer {
|
||||
|
||||
private final boolean disableMetrics;
|
||||
|
||||
@@ -68,9 +86,29 @@ class ObservabilityContextCustomizerFactory implements ContextCustomizerFactory
|
||||
}
|
||||
if (this.disableTracing) {
|
||||
TestPropertyValues.of("management.tracing.enabled=false").applyTo(context);
|
||||
registerNoopTracer(context);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerNoopTracer(ConfigurableApplicationContext context) {
|
||||
if (AotDetector.useGeneratedArtifacts()) {
|
||||
return;
|
||||
}
|
||||
if (!ClassUtils.isPresent("io.micrometer.tracing.Tracer", context.getClassLoader())) {
|
||||
return;
|
||||
}
|
||||
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
|
||||
if (beanFactory instanceof BeanDefinitionRegistry registry) {
|
||||
registerNoopTracer(registry);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerNoopTracer(BeanDefinitionRegistry registry) {
|
||||
RootBeanDefinition definition = new RootBeanDefinition(NoopTracerRegistrar.class);
|
||||
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
registry.registerBeanDefinition(NoopTracerRegistrar.class.getName(), definition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
@@ -90,4 +128,40 @@ class ObservabilityContextCustomizerFactory implements ContextCustomizerFactory
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link BeanDefinitionRegistryPostProcessor} that runs after the
|
||||
* {@link ConfigurationClassPostProcessor} and adds a {@link Tracer} bean definition
|
||||
* when a {@link Tracer} hasn't already been registered.
|
||||
*/
|
||||
private static class NoopTracerRegistrar implements BeanDefinitionRegistryPostProcessor, Ordered, BeanFactoryAware {
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
|
||||
if (AotDetector.useGeneratedArtifacts()) {
|
||||
return;
|
||||
}
|
||||
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors((ListableBeanFactory) this.beanFactory,
|
||||
Tracer.class, false, false).length == 0) {
|
||||
registry.registerBeanDefinition("noopTracer", new RootBeanDefinition(Tracer.class, () -> Tracer.NOOP));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,9 +18,17 @@ package org.springframework.boot.test.autoconfigure.actuate.observability;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import io.micrometer.tracing.Tracer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.boot.context.annotation.UserConfigurations;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.test.context.ContextCustomizer;
|
||||
|
||||
@@ -39,53 +47,113 @@ class ObservabilityContextCustomizerFactoryTests {
|
||||
|
||||
@Test
|
||||
void shouldDisableBothWhenNotAnnotated() {
|
||||
ContextCustomizer customizer = this.factory.createContextCustomizer(NoAnnotation.class,
|
||||
Collections.emptyList());
|
||||
assertThat(customizer).isNotNull();
|
||||
ContextCustomizer customizer = createContextCustomizer(NoAnnotation.class);
|
||||
ConfigurableApplicationContext context = new GenericApplicationContext();
|
||||
customizer.customizeContext(context, null);
|
||||
applyCustomizerToContext(customizer, context);
|
||||
assertThatMetricsAreDisabled(context);
|
||||
assertThatTracingIsDisabled(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDisableOnlyTracing() {
|
||||
ContextCustomizer customizer = this.factory.createContextCustomizer(OnlyMetrics.class, Collections.emptyList());
|
||||
assertThat(customizer).isNotNull();
|
||||
ContextCustomizer customizer = createContextCustomizer(OnlyMetrics.class);
|
||||
ConfigurableApplicationContext context = new GenericApplicationContext();
|
||||
customizer.customizeContext(context, null);
|
||||
applyCustomizerToContext(customizer, context);
|
||||
assertThatMetricsAreEnabled(context);
|
||||
assertThatTracingIsDisabled(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDisableOnlyMetrics() {
|
||||
ContextCustomizer customizer = this.factory.createContextCustomizer(OnlyTracing.class, Collections.emptyList());
|
||||
assertThat(customizer).isNotNull();
|
||||
ContextCustomizer customizer = createContextCustomizer(OnlyTracing.class);
|
||||
ConfigurableApplicationContext context = new GenericApplicationContext();
|
||||
customizer.customizeContext(context, null);
|
||||
applyCustomizerToContext(customizer, context);
|
||||
assertThatMetricsAreDisabled(context);
|
||||
assertThatTracingIsEnabled(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldEnableBothWhenAnnotated() {
|
||||
ContextCustomizer customizer = this.factory.createContextCustomizer(WithAnnotation.class,
|
||||
Collections.emptyList());
|
||||
assertThat(customizer).isNotNull();
|
||||
ContextCustomizer customizer = createContextCustomizer(WithAnnotation.class);
|
||||
ConfigurableApplicationContext context = new GenericApplicationContext();
|
||||
customizer.customizeContext(context, null);
|
||||
applyCustomizerToContext(customizer, context);
|
||||
assertThatMetricsAreEnabled(context);
|
||||
assertThatTracingIsEnabled(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterNoopTracerIfTracingIsDisabled() {
|
||||
ContextCustomizer customizer = createContextCustomizer(NoAnnotation.class);
|
||||
ConfigurableApplicationContext context = new GenericApplicationContext();
|
||||
applyCustomizerToContext(customizer, context);
|
||||
context.refresh();
|
||||
Tracer tracer = context.getBean(Tracer.class);
|
||||
assertThat(tracer).isNotNull();
|
||||
assertThat(tracer.nextSpan().isNoop()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotRegisterNoopTracerIfTracingIsEnabled() {
|
||||
ContextCustomizer customizer = createContextCustomizer(WithAnnotation.class);
|
||||
ConfigurableApplicationContext context = new GenericApplicationContext();
|
||||
applyCustomizerToContext(customizer, context);
|
||||
context.refresh();
|
||||
assertThat(context.getBeanProvider(Tracer.class).getIfAvailable()).as("Tracer bean").isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotRegisterNoopTracerIfMicrometerTracingIsNotPresent() throws Exception {
|
||||
try (FilteredClassLoader filteredClassLoader = new FilteredClassLoader("io.micrometer.tracing")) {
|
||||
ContextCustomizer customizer = createContextCustomizer(NoAnnotation.class);
|
||||
new ApplicationContextRunner().withClassLoader(filteredClassLoader)
|
||||
.withInitializer(applyCustomizer(customizer)).run((context) -> {
|
||||
assertThat(context).doesNotHaveBean(Tracer.class);
|
||||
assertThatMetricsAreDisabled(context);
|
||||
assertThatTracingIsDisabled(context);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBackOffOnCustomTracer() {
|
||||
ContextCustomizer customizer = createContextCustomizer(NoAnnotation.class);
|
||||
new ApplicationContextRunner().withConfiguration(UserConfigurations.of(CustomTracer.class))
|
||||
.withInitializer(applyCustomizer(customizer)).run((context) -> {
|
||||
assertThat(context).hasSingleBean(Tracer.class);
|
||||
assertThat(context).hasBean("customTracer");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotRunIfAotIsEnabled() {
|
||||
ContextCustomizer customizer = createContextCustomizer(NoAnnotation.class);
|
||||
new ApplicationContextRunner().withSystemProperties("spring.aot.enabled:true")
|
||||
.withInitializer(applyCustomizer(customizer))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(Tracer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hashCodeAndEquals() {
|
||||
ContextCustomizer customizer1 = this.factory.createContextCustomizer(OnlyMetrics.class, null);
|
||||
ContextCustomizer customizer2 = this.factory.createContextCustomizer(OnlyTracing.class, null);
|
||||
ContextCustomizer customizer1 = createContextCustomizer(OnlyMetrics.class);
|
||||
ContextCustomizer customizer2 = createContextCustomizer(OnlyTracing.class);
|
||||
assertThat(customizer1).isNotEqualTo(customizer2);
|
||||
}
|
||||
|
||||
private void applyCustomizerToContext(ContextCustomizer customizer, ConfigurableApplicationContext context) {
|
||||
customizer.customizeContext(context, null);
|
||||
}
|
||||
|
||||
private ContextCustomizer createContextCustomizer(Class<?> testClass) {
|
||||
ContextCustomizer contextCustomizer = this.factory.createContextCustomizer(testClass, Collections.emptyList());
|
||||
assertThat(contextCustomizer).as("contextCustomizer").isNotNull();
|
||||
return contextCustomizer;
|
||||
}
|
||||
|
||||
private ApplicationContextInitializer<ConfigurableApplicationContext> applyCustomizer(
|
||||
ContextCustomizer customizer) {
|
||||
return (applicationContext) -> customizer.customizeContext(applicationContext, null);
|
||||
}
|
||||
|
||||
private void assertThatTracingIsDisabled(ConfigurableApplicationContext context) {
|
||||
assertThat(context.getEnvironment().getProperty("management.tracing.enabled")).isEqualTo("false");
|
||||
}
|
||||
@@ -124,4 +192,14 @@ class ObservabilityContextCustomizerFactoryTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class CustomTracer {
|
||||
|
||||
@Bean
|
||||
Tracer customTracer() {
|
||||
return Mockito.mock(Tracer.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -80,6 +80,14 @@
|
||||
<disallow pkg="org.springframework.boot.context" />
|
||||
</subpackage>
|
||||
|
||||
<subpackage name="test">
|
||||
<subpackage name="autoconfigure">
|
||||
<subpackage name="actuate">
|
||||
<allow pkg="io.micrometer" />
|
||||
</subpackage>
|
||||
</subpackage>
|
||||
</subpackage>
|
||||
|
||||
<!-- Web related concerns -->
|
||||
<subpackage name="web">
|
||||
|
||||
|
||||
Reference in New Issue
Block a user