Move zipkin.Span types in the OpenTelemetry auto-configuration

Brave can work without zipkin2 on the classpath, OpenTelemetry can't.
To not force users to have zipkin2 on the classpath, move it into the
OpenTelemetry auto-configuration.

See gh-39049
This commit is contained in:
Moritz Halbritter 2024-02-21 10:06:30 +01:00
parent 5e844dbbdd
commit ed4d13a8bf
4 changed files with 57 additions and 32 deletions

View File

@ -16,10 +16,7 @@
package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
import zipkin2.Span;
import zipkin2.reporter.BytesEncoder;
import zipkin2.reporter.Encoding; import zipkin2.reporter.Encoding;
import zipkin2.reporter.SpanBytesEncoder;
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.BraveConfiguration; import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.BraveConfiguration;
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.OpenTelemetryConfiguration; import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.OpenTelemetryConfiguration;
@ -63,10 +60,4 @@ public class ZipkinAutoConfiguration {
}; };
} }
@Bean
@ConditionalOnMissingBean(value = Span.class, parameterizedContainer = BytesEncoder.class)
BytesEncoder<Span> zipkinSpanEncoder(Encoding encoding) {
return SpanBytesEncoder.forEncoding(encoding);
}
} }

View File

@ -29,6 +29,7 @@ import zipkin2.reporter.BytesMessageSender;
import zipkin2.reporter.Encoding; import zipkin2.reporter.Encoding;
import zipkin2.reporter.HttpEndpointSupplier; import zipkin2.reporter.HttpEndpointSupplier;
import zipkin2.reporter.HttpEndpointSuppliers; import zipkin2.reporter.HttpEndpointSuppliers;
import zipkin2.reporter.SpanBytesEncoder;
import zipkin2.reporter.brave.AsyncZipkinSpanHandler; import zipkin2.reporter.brave.AsyncZipkinSpanHandler;
import zipkin2.reporter.brave.MutableSpanBytesEncoder; import zipkin2.reporter.brave.MutableSpanBytesEncoder;
import zipkin2.reporter.urlconnection.URLConnectionSender; import zipkin2.reporter.urlconnection.URLConnectionSender;
@ -177,7 +178,7 @@ class ZipkinConfigurations {
@Bean @Bean
@ConditionalOnMissingBean(value = MutableSpan.class, parameterizedContainer = BytesEncoder.class) @ConditionalOnMissingBean(value = MutableSpan.class, parameterizedContainer = BytesEncoder.class)
BytesEncoder<MutableSpan> braveSpanEncoder(Encoding encoding, BytesEncoder<MutableSpan> mutableSpanBytesEncoder(Encoding encoding,
ObjectProvider<Tag<Throwable>> throwableTagProvider) { ObjectProvider<Tag<Throwable>> throwableTagProvider) {
Tag<Throwable> throwableTag = throwableTagProvider.getIfAvailable(() -> Tags.ERROR); Tag<Throwable> throwableTag = throwableTagProvider.getIfAvailable(() -> Tags.ERROR);
return MutableSpanBytesEncoder.create(encoding, throwableTag); return MutableSpanBytesEncoder.create(encoding, throwableTag);
@ -188,22 +189,28 @@ class ZipkinConfigurations {
@ConditionalOnBean(BytesMessageSender.class) @ConditionalOnBean(BytesMessageSender.class)
@ConditionalOnEnabledTracing @ConditionalOnEnabledTracing
AsyncZipkinSpanHandler asyncZipkinSpanHandler(BytesMessageSender sender, AsyncZipkinSpanHandler asyncZipkinSpanHandler(BytesMessageSender sender,
BytesEncoder<MutableSpan> braveSpanEncoder) { BytesEncoder<MutableSpan> mutableSpanBytesEncoder) {
return AsyncZipkinSpanHandler.newBuilder(sender).build(braveSpanEncoder); return AsyncZipkinSpanHandler.newBuilder(sender).build(mutableSpanBytesEncoder);
} }
} }
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ZipkinSpanExporter.class) @ConditionalOnClass({ ZipkinSpanExporter.class, Span.class })
static class OpenTelemetryConfiguration { static class OpenTelemetryConfiguration {
@Bean
@ConditionalOnMissingBean(value = Span.class, parameterizedContainer = BytesEncoder.class)
BytesEncoder<Span> spanBytesEncoder(Encoding encoding) {
return SpanBytesEncoder.forEncoding(encoding);
}
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnBean(BytesMessageSender.class) @ConditionalOnBean(BytesMessageSender.class)
@ConditionalOnEnabledTracing @ConditionalOnEnabledTracing
ZipkinSpanExporter zipkinSpanExporter(BytesMessageSender sender, BytesEncoder<Span> zipkinSpanEncoder) { ZipkinSpanExporter zipkinSpanExporter(BytesMessageSender sender, BytesEncoder<Span> spanBytesEncoder) {
return ZipkinSpanExporter.builder().setSender(sender).setEncoder(zipkinSpanEncoder).build(); return ZipkinSpanExporter.builder().setSender(sender).setEncoder(spanBytesEncoder).build();
} }
} }

View File

@ -40,8 +40,7 @@ class ZipkinAutoConfigurationTests {
@Test @Test
void shouldSupplyBeans() { void shouldSupplyBeans() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(Encoding.class) this.contextRunner.run((context) -> assertThat(context).hasSingleBean(Encoding.class)
.hasSingleBean(PropertiesZipkinConnectionDetails.class) .hasSingleBean(PropertiesZipkinConnectionDetails.class));
.hasBean("zipkinSpanEncoder"));
} }
@Test @Test
@ -65,14 +64,8 @@ class ZipkinAutoConfigurationTests {
@Test @Test
void shouldUseCustomConnectionDetailsWhenDefined() { void shouldUseCustomConnectionDetailsWhenDefined() {
this.contextRunner.withBean(ZipkinConnectionDetails.class, () -> new ZipkinConnectionDetails() { this.contextRunner
.withBean(ZipkinConnectionDetails.class, () -> new FixedZipkinConnectionDetails("http://localhost"))
@Override
public String getSpanEndpoint() {
return "http://localhost";
}
})
.run((context) -> assertThat(context).hasSingleBean(ZipkinConnectionDetails.class) .run((context) -> assertThat(context).hasSingleBean(ZipkinConnectionDetails.class)
.doesNotHaveBean(PropertiesZipkinConnectionDetails.class)); .doesNotHaveBean(PropertiesZipkinConnectionDetails.class));
} }
@ -85,6 +78,21 @@ class ZipkinAutoConfigurationTests {
.run((context) -> assertThat(context).hasNotFailed()); .run((context) -> assertThat(context).hasNotFailed());
} }
private static final class FixedZipkinConnectionDetails implements ZipkinConnectionDetails {
private final String spanEndpoint;
private FixedZipkinConnectionDetails(String spanEndpoint) {
this.spanEndpoint = spanEndpoint;
}
@Override
public String getSpanEndpoint() {
return this.spanEndpoint;
}
}
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
private static final class CustomConfiguration { private static final class CustomConfiguration {

View File

@ -31,7 +31,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/** /**
* Tests for {@link OpenTelemetryConfiguration}. * Tests for {@link OpenTelemetryConfiguration}.
@ -48,24 +47,42 @@ class ZipkinConfigurationsOpenTelemetryConfigurationTests {
@Test @Test
void shouldSupplyBeans() { void shouldSupplyBeans() {
this.contextRunner.withUserConfiguration(SenderConfiguration.class) this.contextRunner.withUserConfiguration(SenderConfiguration.class, CustomEncoderConfiguration.class)
.withBean(BytesEncoder.class, () -> mock(BytesEncoder.class)) .run((context) -> {
.run((context) -> assertThat(context).hasSingleBean(ZipkinSpanExporter.class)); assertThat(context).hasSingleBean(ZipkinSpanExporter.class);
assertThat(context).hasBean("customSpanEncoder");
});
} }
@Test @Test
void shouldNotSupplyZipkinSpanExporterIfSenderIsMissing() { void shouldNotSupplyZipkinSpanExporterIfSenderIsMissing() {
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ZipkinSpanExporter.class)); this.contextRunner.run((context) -> {
assertThat(context).doesNotHaveBean(ZipkinSpanExporter.class);
assertThat(context).hasBean("spanBytesEncoder");
});
} }
@Test @Test
void shouldNotSupplyZipkinSpanExporterIfNotOnClasspath() { void shouldNotSupplyZipkinSpanExporterIfNotOnClasspath() {
this.contextRunner.withClassLoader(new FilteredClassLoader("io.opentelemetry.exporter.zipkin")) this.contextRunner.withClassLoader(new FilteredClassLoader("io.opentelemetry.exporter.zipkin"))
.withUserConfiguration(SenderConfiguration.class) .withUserConfiguration(SenderConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean(ZipkinSpanExporter.class)); .run((context) -> {
assertThat(context).doesNotHaveBean(ZipkinSpanExporter.class);
assertThat(context).doesNotHaveBean("spanBytesEncoder");
});
} }
@Test
void shouldBackOffIfZipkinIsNotOnClasspath() {
this.contextRunner.withClassLoader(new FilteredClassLoader("zipkin2.Span"))
.withUserConfiguration(SenderConfiguration.class)
.run((context) -> {
assertThat(context).doesNotHaveBean(ZipkinSpanExporter.class);
assertThat(context).doesNotHaveBean("spanBytesEncoder");
});
}
@Test @Test
void shouldBackOffOnCustomBeans() { void shouldBackOffOnCustomBeans() {
this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> { this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> {
@ -85,6 +102,7 @@ class ZipkinConfigurationsOpenTelemetryConfigurationTests {
this.contextRunner.withUserConfiguration(SenderConfiguration.class, CustomEncoderConfiguration.class) this.contextRunner.withUserConfiguration(SenderConfiguration.class, CustomEncoderConfiguration.class)
.run((context) -> { .run((context) -> {
assertThat(context).hasSingleBean(ZipkinSpanExporter.class); assertThat(context).hasSingleBean(ZipkinSpanExporter.class);
assertThat(context).hasBean("customSpanEncoder");
assertThat(context.getBean(ZipkinSpanExporter.class)).extracting("encoder") assertThat(context.getBean(ZipkinSpanExporter.class)).extracting("encoder")
.isInstanceOf(CustomSpanEncoder.class) .isInstanceOf(CustomSpanEncoder.class)
.extracting("encoding") .extracting("encoding")
@ -99,6 +117,7 @@ class ZipkinConfigurationsOpenTelemetryConfigurationTests {
CustomEncoderConfiguration.class) CustomEncoderConfiguration.class)
.run((context) -> { .run((context) -> {
assertThat(context).hasSingleBean(ZipkinSpanExporter.class); assertThat(context).hasSingleBean(ZipkinSpanExporter.class);
assertThat(context).hasBean("customSpanEncoder");
assertThat(context.getBean(ZipkinSpanExporter.class)).extracting("encoder") assertThat(context.getBean(ZipkinSpanExporter.class)).extracting("encoder")
.isInstanceOf(CustomSpanEncoder.class) .isInstanceOf(CustomSpanEncoder.class)
.extracting("encoding") .extracting("encoding")
@ -140,7 +159,7 @@ class ZipkinConfigurationsOpenTelemetryConfigurationTests {
private static final class CustomEncoderConfiguration { private static final class CustomEncoderConfiguration {
@Bean @Bean
BytesEncoder<Span> encoder(Encoding encoding) { BytesEncoder<Span> customSpanEncoder(Encoding encoding) {
return new CustomSpanEncoder(encoding); return new CustomSpanEncoder(encoding);
} }