Separate Zipkin tracing auto-configuration

This commit is contained in:
Phillip Webb
2025-05-23 12:56:37 -07:00
parent 1359933dad
commit 7f92ed80d2
7 changed files with 130 additions and 184 deletions

View File

@@ -16,31 +16,31 @@
package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
import zipkin2.reporter.Encoding;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Builder;
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.SenderConfiguration;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import zipkin2.reporter.BytesMessageSender;
import zipkin2.reporter.Encoding;
import zipkin2.reporter.HttpEndpointSupplier;
import zipkin2.reporter.HttpEndpointSuppliers;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Zipkin.
* <p>
* It uses imports on {@link ZipkinConfigurations} to guarantee the correct configuration
* ordering.
*
* @author Moritz Halbritter
* @author Moritz Halbritter
* @author Stefan Bratanov
* @author Wick Dynex
* @since 3.0.0
*/
@AutoConfiguration(afterName = "org.springframework.boot.restclient.autoconfigure.RestTemplateAutoConfiguration")
@ConditionalOnClass(Encoding.class)
@Import({ SenderConfiguration.class, BraveConfiguration.class, OpenTelemetryConfiguration.class })
@EnableConfigurationProperties(ZipkinProperties.class)
public class ZipkinAutoConfiguration {
@@ -59,4 +59,21 @@ public class ZipkinAutoConfiguration {
};
}
@Bean
@ConditionalOnMissingBean(BytesMessageSender.class)
@ConditionalOnClass(HttpClient.class)
ZipkinHttpClientSender httpClientSender(ZipkinProperties properties, Encoding encoding,
ObjectProvider<ZipkinHttpClientBuilderCustomizer> customizers,
ObjectProvider<ZipkinConnectionDetails> connectionDetailsProvider,
ObjectProvider<HttpEndpointSupplier.Factory> endpointSupplierFactoryProvider) {
ZipkinConnectionDetails connectionDetails = connectionDetailsProvider
.getIfAvailable(() -> new PropertiesZipkinConnectionDetails(properties));
HttpEndpointSupplier.Factory endpointSupplierFactory = endpointSupplierFactoryProvider
.getIfAvailable(HttpEndpointSuppliers::constantFactory);
Builder httpClientBuilder = HttpClient.newBuilder().connectTimeout(properties.getConnectTimeout());
customizers.orderedStream().forEach((customizer) -> customizer.customize(httpClientBuilder));
return new ZipkinHttpClientSender(encoding, endpointSupplierFactory, connectionDetails.getSpanEndpoint(),
httpClientBuilder.build(), properties.getReadTimeout());
}
}

View File

@@ -16,9 +16,6 @@
package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Builder;
import brave.Tag;
import brave.Tags;
import brave.handler.MutableSpan;
@@ -27,59 +24,36 @@ import zipkin2.Span;
import zipkin2.reporter.BytesEncoder;
import zipkin2.reporter.BytesMessageSender;
import zipkin2.reporter.Encoding;
import zipkin2.reporter.HttpEndpointSupplier;
import zipkin2.reporter.HttpEndpointSuppliers;
import zipkin2.reporter.SpanBytesEncoder;
import zipkin2.reporter.brave.AsyncZipkinSpanHandler;
import zipkin2.reporter.brave.MutableSpanBytesEncoder;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing;
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinTracingAutoConfiguration.BraveConfiguration;
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinTracingAutoConfiguration.OpenTelemetryConfiguration;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* Configurations for Zipkin. Those are imported by {@link ZipkinAutoConfiguration}.
* {@link EnableAutoConfiguration Auto-configuration} for Zipkin tracing.
*
* @author Moritz Halbritter
* @author Stefan Bratanov
* @author Wick Dynex
* @author Phillip Webb
* @since 4.0.0
*/
class ZipkinConfigurations {
@Configuration(proxyBeanMethods = false)
@Import({ HttpClientSenderConfiguration.class })
static class SenderConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HttpClient.class)
@EnableConfigurationProperties(ZipkinProperties.class)
static class HttpClientSenderConfiguration {
@Bean
@ConditionalOnMissingBean(BytesMessageSender.class)
ZipkinHttpClientSender httpClientSender(ZipkinProperties properties, Encoding encoding,
ObjectProvider<ZipkinHttpClientBuilderCustomizer> customizers,
ObjectProvider<ZipkinConnectionDetails> connectionDetailsProvider,
ObjectProvider<HttpEndpointSupplier.Factory> endpointSupplierFactoryProvider) {
ZipkinConnectionDetails connectionDetails = connectionDetailsProvider
.getIfAvailable(() -> new PropertiesZipkinConnectionDetails(properties));
HttpEndpointSupplier.Factory endpointSupplierFactory = endpointSupplierFactoryProvider
.getIfAvailable(HttpEndpointSuppliers::constantFactory);
Builder httpClientBuilder = HttpClient.newBuilder().connectTimeout(properties.getConnectTimeout());
customizers.orderedStream().forEach((customizer) -> customizer.customize(httpClientBuilder));
return new ZipkinHttpClientSender(encoding, endpointSupplierFactory, connectionDetails.getSpanEndpoint(),
httpClientBuilder.build(), properties.getReadTimeout());
}
}
@ConditionalOnClass(Encoding.class)
@AutoConfiguration(afterName = "org.springframework.boot.restclient.autoconfigure.RestTemplateAutoConfiguration")
@Import({ BraveConfiguration.class, OpenTelemetryConfiguration.class })
public class ZipkinTracingAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(AsyncZipkinSpanHandler.class)

View File

@@ -63,6 +63,7 @@ org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryTracingAutoC
org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpTracingAutoConfiguration
org.springframework.boot.actuate.autoconfigure.tracing.prometheus.PrometheusExemplarsAutoConfiguration
org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinAutoConfiguration
org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinTracingAutoConfiguration
org.springframework.boot.actuate.autoconfigure.web.exchanges.HttpExchangesAutoConfiguration
org.springframework.boot.actuate.autoconfigure.web.exchanges.HttpExchangesEndpointAutoConfiguration
org.springframework.boot.actuate.autoconfigure.web.mappings.MappingsEndpointAutoConfiguration
@@ -71,4 +72,4 @@ org.springframework.boot.actuate.autoconfigure.web.server.jetty.JettyServletMana
org.springframework.boot.actuate.autoconfigure.web.server.netty.NettyReactiveManagementContextAutoConfiguration
org.springframework.boot.actuate.autoconfigure.web.server.undertow.UndertowReactiveManagementContextAutoConfiguration
org.springframework.boot.actuate.autoconfigure.web.server.undertow.UndertowServletManagementContextAutoConfiguration
org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration
org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration

View File

@@ -16,8 +16,12 @@
package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
import java.net.http.HttpClient;
import org.junit.jupiter.api.Test;
import zipkin2.reporter.BytesMessageSender;
import zipkin2.reporter.Encoding;
import zipkin2.reporter.HttpEndpointSupplier;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
@@ -26,11 +30,13 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ZipkinAutoConfiguration}.
*
* @author Moritz Halbritter
* @author Wick Dynex
*/
class ZipkinAutoConfigurationTests {
@@ -39,8 +45,12 @@ class ZipkinAutoConfigurationTests {
@Test
void shouldSupplyBeans() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(Encoding.class)
.hasSingleBean(PropertiesZipkinConnectionDetails.class));
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(Encoding.class);
assertThat(context).hasSingleBean(PropertiesZipkinConnectionDetails.class);
assertThat(context).hasSingleBean(BytesMessageSender.class);
assertThat(context).hasSingleBean(ZipkinHttpClientSender.class);
});
}
@Test
@@ -50,13 +60,37 @@ class ZipkinAutoConfigurationTests {
}
@Test
void shouldBackOffOnCustomBeans() {
this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> {
void shouldNotProvideHttpClientSenderIfHttpClientIsNotAvailable() {
this.contextRunner.withClassLoader(new FilteredClassLoader(HttpClient.class))
.run((context) -> assertThat(context).doesNotHaveBean(ZipkinHttpClientSender.class));
}
@Test
void shouldBackOffOnCustomEncodingBeans() {
this.contextRunner.withUserConfiguration(CustomEncodingConfiguration.class).run((context) -> {
assertThat(context).hasBean("customEncoding");
assertThat(context).hasSingleBean(Encoding.class);
});
}
@Test
void shouldBackOffOnCustomSenderBeans() {
this.contextRunner.withUserConfiguration(CustomSenderConfiguration.class).run((context) -> {
assertThat(context).hasBean("customSender");
assertThat(context).hasSingleBean(BytesMessageSender.class);
});
}
@Test
void shouldUseCustomHttpEndpointSupplierFactory() {
this.contextRunner.withUserConfiguration(CustomHttpEndpointSupplierFactoryConfiguration.class)
.run((context) -> {
ZipkinHttpClientSender httpClientSender = context.getBean(ZipkinHttpClientSender.class);
assertThat(httpClientSender).extracting("endpointSupplier")
.isInstanceOf(CustomHttpEndpointSupplier.class);
});
}
@Test
void definesPropertiesBasedConnectionDetailsByDefault() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PropertiesZipkinConnectionDetails.class));
@@ -94,7 +128,7 @@ class ZipkinAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
private static final class CustomConfiguration {
private static final class CustomEncodingConfiguration {
@Bean
Encoding customEncoding() {
@@ -103,4 +137,52 @@ class ZipkinAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class CustomSenderConfiguration {
@Bean
BytesMessageSender customSender() {
return mock(BytesMessageSender.class);
}
}
@Configuration(proxyBeanMethods = false)
static class CustomHttpEndpointSupplierFactoryConfiguration {
@Bean
HttpEndpointSupplier.Factory httpEndpointSupplier() {
return new CustomHttpEndpointSupplierFactory();
}
}
static class CustomHttpEndpointSupplierFactory implements HttpEndpointSupplier.Factory {
@Override
public HttpEndpointSupplier create(String endpoint) {
return new CustomHttpEndpointSupplier(endpoint);
}
}
static class CustomHttpEndpointSupplier implements HttpEndpointSupplier {
private final String endpoint;
CustomHttpEndpointSupplier(String endpoint) {
this.endpoint = endpoint;
}
@Override
public String get() {
return this.endpoint;
}
@Override
public void close() {
}
}
}

View File

@@ -28,7 +28,7 @@ import zipkin2.reporter.BytesMessageSender;
import zipkin2.reporter.Encoding;
import zipkin2.reporter.brave.AsyncZipkinSpanHandler;
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.BraveConfiguration;
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinTracingAutoConfiguration.BraveConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;

View File

@@ -23,7 +23,7 @@ import zipkin2.reporter.BytesEncoder;
import zipkin2.reporter.BytesMessageSender;
import zipkin2.reporter.Encoding;
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.OpenTelemetryConfiguration;
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinTracingAutoConfiguration.OpenTelemetryConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;

View File

@@ -1,128 +0,0 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
import java.net.http.HttpClient;
import org.junit.jupiter.api.Test;
import zipkin2.reporter.BytesMessageSender;
import zipkin2.reporter.HttpEndpointSupplier;
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.HttpClientSenderConfiguration;
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.SenderConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link SenderConfiguration}.
*
* @author Moritz Halbritter
* @author Wick Dynex
*/
class ZipkinConfigurationsSenderConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(DefaultEncodingConfiguration.class, SenderConfiguration.class));
@Test
void shouldSupplyDefaultHttpClientSenderBean() {
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(BytesMessageSender.class);
assertThat(context).hasSingleBean(ZipkinHttpClientSender.class);
});
}
@Test
void shouldNotProvideHttpClientSenderIfHttpClientIsNotAvailable() {
this.contextRunner.withUserConfiguration(HttpClientSenderConfiguration.class)
.withClassLoader(new FilteredClassLoader(HttpClient.class))
.run((context) -> assertThat(context).doesNotHaveBean(ZipkinHttpClientSender.class));
}
@Test
void shouldBackOffOnCustomBeans() {
this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> {
assertThat(context).hasBean("customSender");
assertThat(context).hasSingleBean(BytesMessageSender.class);
});
}
@Test
void shouldUseCustomHttpEndpointSupplierFactory() {
this.contextRunner.withUserConfiguration(CustomHttpEndpointSupplierFactoryConfiguration.class)
.run((context) -> {
ZipkinHttpClientSender httpClientSender = context.getBean(ZipkinHttpClientSender.class);
assertThat(httpClientSender).extracting("endpointSupplier")
.isInstanceOf(CustomHttpEndpointSupplier.class);
});
}
@Configuration(proxyBeanMethods = false)
static class CustomConfiguration {
@Bean
BytesMessageSender customSender() {
return mock(BytesMessageSender.class);
}
}
@Configuration(proxyBeanMethods = false)
static class CustomHttpEndpointSupplierFactoryConfiguration {
@Bean
HttpEndpointSupplier.Factory httpEndpointSupplier() {
return new CustomHttpEndpointSupplierFactory();
}
}
static class CustomHttpEndpointSupplierFactory implements HttpEndpointSupplier.Factory {
@Override
public HttpEndpointSupplier create(String endpoint) {
return new CustomHttpEndpointSupplier(endpoint);
}
}
static class CustomHttpEndpointSupplier implements HttpEndpointSupplier {
private final String endpoint;
CustomHttpEndpointSupplier(String endpoint) {
this.endpoint = endpoint;
}
@Override
public String get() {
return this.endpoint;
}
@Override
public void close() {
}
}
}