diff --git a/docs/src/main/asciidoc/project-features.adoc b/docs/src/main/asciidoc/project-features.adoc
index e5e8da943..ba7bcc6f7 100644
--- a/docs/src/main/asciidoc/project-features.adoc
+++ b/docs/src/main/asciidoc/project-features.adoc
@@ -286,6 +286,8 @@ The following example shows setting the sender type for `web`:
spring.zipkin.sender.type: web
----
+If you're running a non-reactive application we will use a `RestTemplate` based span sender. Otherwise a `WebClient` based span sender will be chosen.
+
To customize the `RestTemplate` that sends spans to Zipkin via HTTP, you can register the `ZipkinRestTemplateCustomizer` bean.
[source,java,indent=0]
@@ -321,6 +323,17 @@ By default, api path will be set to `api/v2/spans` or `api/v1/spans` depending o
spring.zipkin.api-path: v2/path2
----
+In case of a reactive application, we're creating a simple `WebClient.Builder` instance. If you want to provide your own or reuse an existing one you need to create an instance of a `ZipkinWebClientBuilderProvider` bean.
+
+[source,java,indent=0]
+----
+ @Bean
+ ZipkinWebClientBuilderProvider myZipkinWebClientBuilderProvider() {
+ // create your own instance or inject one from the Spring Context
+ return () -> WebClient.builder();
+ }
+----
+
[[features-zipkin-custom-service-name]]
=== Custom service name
diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/redis/TraceRedisAutoConfiguration.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/redis/TraceRedisAutoConfiguration.java
index 3e798db1d..9fbe6be24 100644
--- a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/redis/TraceRedisAutoConfiguration.java
+++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/redis/TraceRedisAutoConfiguration.java
@@ -65,7 +65,8 @@ public class TraceRedisAutoConfiguration {
@Bean
@ConditionalOnMissingClass("brave.sampler.Sampler")
- TraceLettuceClientResourcesBuilderCustomizer otherTracersTraceLettuceClientResourcesBuilderCustomizer(Tracing tracing) {
+ TraceLettuceClientResourcesBuilderCustomizer otherTracersTraceLettuceClientResourcesBuilderCustomizer(
+ Tracing tracing) {
return new TraceLettuceClientResourcesBuilderCustomizer(tracing);
}
diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinRestTemplateSenderConfiguration.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinHttpSenderConfiguration.java
similarity index 60%
rename from spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinRestTemplateSenderConfiguration.java
rename to spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinHttpSenderConfiguration.java
index 57c2795e6..c49706cd7 100644
--- a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinRestTemplateSenderConfiguration.java
+++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinHttpSenderConfiguration.java
@@ -19,50 +19,81 @@ package org.springframework.cloud.sleuth.autoconfig.zipkin2;
import zipkin2.reporter.Sender;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.sleuth.zipkin2.CachingZipkinUrlExtractor;
import org.springframework.cloud.sleuth.zipkin2.LoadBalancerClientZipkinLoadBalancer;
import org.springframework.cloud.sleuth.zipkin2.RestTemplateSender;
import org.springframework.cloud.sleuth.zipkin2.StaticInstanceZipkinLoadBalancer;
+import org.springframework.cloud.sleuth.zipkin2.WebClientSender;
import org.springframework.cloud.sleuth.zipkin2.ZipkinLoadBalancer;
import org.springframework.cloud.sleuth.zipkin2.ZipkinProperties;
import org.springframework.cloud.sleuth.zipkin2.ZipkinRestTemplateCustomizer;
import org.springframework.cloud.sleuth.zipkin2.ZipkinRestTemplateProvider;
import org.springframework.cloud.sleuth.zipkin2.ZipkinRestTemplateWrapper;
import org.springframework.cloud.sleuth.zipkin2.ZipkinUrlExtractor;
+import org.springframework.cloud.sleuth.zipkin2.ZipkinWebClientBuilderProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
+import org.springframework.web.reactive.function.client.WebClient;
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = ZipkinAutoConfiguration.SENDER_BEAN_NAME)
@Conditional(ZipkinSenderCondition.class)
@EnableConfigurationProperties(ZipkinSenderProperties.class)
-class ZipkinRestTemplateSenderConfiguration {
+class ZipkinHttpSenderConfiguration {
+
+ @Configuration(proxyBeanMethods = false)
+ @Conditional(NonWebApplicationOrServletCondition.class)
+ static class ZipkinServletConfiguration {
+
+ @Bean(ZipkinAutoConfiguration.SENDER_BEAN_NAME)
+ Sender restTemplateSender(ZipkinProperties zipkin, ZipkinRestTemplateCustomizer zipkinRestTemplateCustomizer,
+ ZipkinRestTemplateProvider zipkinRestTemplateProvider) {
+ RestTemplate restTemplate = zipkinRestTemplateProvider.zipkinRestTemplate();
+ restTemplate = zipkinRestTemplateCustomizer.customizeTemplate(restTemplate);
+ return new RestTemplateSender(restTemplate, zipkin.getBaseUrl(), zipkin.getApiPath(), zipkin.getEncoder());
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ ZipkinRestTemplateProvider zipkinRestTemplateProvider(ZipkinProperties zipkin, ZipkinUrlExtractor extractor) {
+ return () -> new ZipkinRestTemplateWrapper(zipkin, extractor);
+ }
+
+ @Bean
+ ZipkinUrlExtractor defaultZipkinUrlExtractor(final ZipkinLoadBalancer zipkinLoadBalancer) {
+ return new CachingZipkinUrlExtractor(zipkinLoadBalancer);
+ }
- @Bean(ZipkinAutoConfiguration.SENDER_BEAN_NAME)
- Sender restTemplateSender(ZipkinProperties zipkin, ZipkinRestTemplateCustomizer zipkinRestTemplateCustomizer,
- ZipkinRestTemplateProvider zipkinRestTemplateProvider) {
- RestTemplate restTemplate = zipkinRestTemplateProvider.zipkinRestTemplate();
- restTemplate = zipkinRestTemplateCustomizer.customizeTemplate(restTemplate);
- return new RestTemplateSender(restTemplate, zipkin.getBaseUrl(), zipkin.getApiPath(), zipkin.getEncoder());
}
- @Bean
- @ConditionalOnMissingBean
- ZipkinRestTemplateProvider zipkinRestTemplateProvider(ZipkinProperties zipkin, ZipkinUrlExtractor extractor) {
- return () -> new ZipkinRestTemplateWrapper(zipkin, extractor);
- }
+ @Configuration(proxyBeanMethods = false)
+ @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
+ static class ZipkinReactiveConfiguration {
+
+ @Bean(ZipkinAutoConfiguration.SENDER_BEAN_NAME)
+ Sender webClientSender(ZipkinProperties zipkin, ZipkinWebClientBuilderProvider zipkinWebClientBuilderProvider) {
+ WebClient.Builder webClientBuilder = zipkinWebClientBuilderProvider.zipkinWebClientBuilder();
+ return new WebClientSender(webClientBuilder.build(), zipkin.getBaseUrl(), zipkin.getApiPath(),
+ zipkin.getEncoder());
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ ZipkinWebClientBuilderProvider defaultZipkinWebClientProvider() {
+ return WebClient::builder;
+ }
- @Bean
- ZipkinUrlExtractor defaultZipkinUrlExtractor(final ZipkinLoadBalancer zipkinLoadBalancer) {
- return new CachingZipkinUrlExtractor(zipkinLoadBalancer);
}
@Configuration(proxyBeanMethods = false)
@@ -114,4 +145,21 @@ class ZipkinRestTemplateSenderConfiguration {
}
+ static class NonWebApplicationOrServletCondition extends AnyNestedCondition {
+
+ private NonWebApplicationOrServletCondition() {
+ super(ConfigurationPhase.REGISTER_BEAN);
+ }
+
+ @ConditionalOnNotWebApplication
+ static class OnNonWeb {
+
+ }
+
+ @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
+ static class OnServlet {
+
+ }
+
+ }
}
diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinSenderConfigurationImportSelector.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinSenderConfigurationImportSelector.java
index 31cca6154..274ff5fff 100644
--- a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinSenderConfigurationImportSelector.java
+++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinSenderConfigurationImportSelector.java
@@ -39,7 +39,7 @@ class ZipkinSenderConfigurationImportSelector implements ImportSelector {
mappings.put("activemq", ZipkinActiveMqSenderConfiguration.class.getName());
mappings.put("rabbit", ZipkinRabbitSenderConfiguration.class.getName());
mappings.put("kafka", ZipkinKafkaSenderConfiguration.class.getName());
- mappings.put("web", ZipkinRestTemplateSenderConfiguration.class.getName());
+ mappings.put("web", ZipkinHttpSenderConfiguration.class.getName());
MAPPINGS = Collections.unmodifiableMap(mappings);
}
diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinHttpSenderConfigurationReactiveTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinHttpSenderConfigurationReactiveTests.java
new file mode 100644
index 000000000..306de3dca
--- /dev/null
+++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinHttpSenderConfigurationReactiveTests.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2013-2021 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.cloud.sleuth.autoconfig.zipkin2;
+
+import org.junit.jupiter.api.Test;
+import zipkin2.reporter.Sender;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.WebApplicationType;
+import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
+import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
+import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
+import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
+import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
+import org.springframework.cloud.gateway.config.GatewayAutoConfiguration;
+import org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration;
+import org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration;
+import org.springframework.cloud.sleuth.autoconfig.TraceNoOpAutoConfiguration;
+import org.springframework.cloud.sleuth.zipkin2.WebClientSender;
+import org.springframework.cloud.sleuth.zipkin2.ZipkinProperties;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+
+import static org.assertj.core.api.BDDAssertions.then;
+
+/**
+ * @author Marcin Grzejszczak
+ */
+class ZipkinHttpSenderConfigurationReactiveTests {
+
+ @Test
+ void should_work_when_using_web_client_without_the_web_environment() {
+ SpringApplication springApplication = new SpringApplication(Config.class);
+ springApplication.setWebApplicationType(WebApplicationType.REACTIVE);
+
+ try (ConfigurableApplicationContext context = springApplication.run("--spring.sleuth.noop.enabled=true")) {
+ then(context.getBean(Sender.class)).isInstanceOf(WebClientSender.class);
+ }
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ @EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class,
+ GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class,
+ MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class,
+ R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class, CassandraAutoConfiguration.class })
+ public static class Config {
+
+ }
+
+}
diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinRestTemplateSenderConfigurationTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinHttpSenderConfigurationTests.java
similarity index 77%
rename from spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinRestTemplateSenderConfigurationTests.java
rename to spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinHttpSenderConfigurationTests.java
index 336fd2ca9..bb06c66e9 100644
--- a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinRestTemplateSenderConfigurationTests.java
+++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinHttpSenderConfigurationTests.java
@@ -18,8 +18,10 @@ package org.springframework.cloud.sleuth.autoconfig.zipkin2;
import org.assertj.core.api.BDDAssertions;
import org.junit.jupiter.api.Test;
+import zipkin2.reporter.Sender;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.cloud.sleuth.zipkin2.WebClientSender;
import org.springframework.cloud.sleuth.zipkin2.ZipkinProperties;
import org.springframework.cloud.sleuth.zipkin2.ZipkinRestTemplateCustomizer;
import org.springframework.cloud.sleuth.zipkin2.ZipkinRestTemplateProvider;
@@ -29,12 +31,12 @@ import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
-class ZipkinRestTemplateSenderConfigurationTests {
+class ZipkinHttpSenderConfigurationTests {
@Test
void should_override_the_default_rest_template() {
ApplicationContextRunner runner = new ApplicationContextRunner().withUserConfiguration(Config.class,
- ZipkinRestTemplateSenderConfiguration.class, ZipkinProperties.class);
+ ZipkinHttpSenderConfiguration.class, ZipkinProperties.class);
runner.run(context -> {
Config config = context.getBean(Config.class);
@@ -42,6 +44,18 @@ class ZipkinRestTemplateSenderConfigurationTests {
});
}
+ @Test
+ void should_use_web_client_when_reactive_type() {
+ ApplicationContextRunner runner = new ApplicationContextRunner().withUserConfiguration(
+ ZipkinHttpSenderConfiguration.class, ZipkinProperties.class)
+ .withPropertyValues("spring.main.web-application-type=REACTIVE");
+
+ runner.run(context -> {
+ Sender sender = context.getBean(Sender.class);
+ assertThat(sender).isInstanceOf(WebClientSender.class);
+ });
+ }
+
@Configuration(proxyBeanMethods = false)
static class Config {
diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml
index 59e26e344..a8efeb2b9 100644
--- a/spring-cloud-sleuth-zipkin/pom.xml
+++ b/spring-cloud-sleuth-zipkin/pom.xml
@@ -46,6 +46,16 @@
org.springframework
spring-web
+
+ org.springframework
+ spring-webflux
+ true
+
+
+ io.projectreactor
+ reactor-core
+ true
+
org.springframework.cloud
spring-cloud-commons
@@ -181,11 +191,6 @@
aspectjweaver
test
-
- io.projectreactor
- reactor-core
- test
-
diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/HttpSender.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/HttpSender.java
new file mode 100644
index 000000000..4b9c8ce41
--- /dev/null
+++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/HttpSender.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2013-2021 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.cloud.sleuth.zipkin2;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+
+import zipkin2.Call;
+import zipkin2.Callback;
+import zipkin2.CheckResult;
+import zipkin2.Span;
+import zipkin2.codec.BytesEncoder;
+import zipkin2.codec.Encoding;
+import zipkin2.reporter.BytesMessageEncoder;
+import zipkin2.reporter.Sender;
+
+import org.springframework.http.MediaType;
+
+import static zipkin2.codec.SpanBytesEncoder.JSON_V2;
+
+/**
+ * {@link Sender} that uses an HTTP client to send spans to Zipkin.
+ *
+ * @since 3.1.0
+ */
+abstract class HttpSender extends Sender {
+
+ final ZipkinHttpClientSender sender;
+
+ final String url;
+
+ final Encoding encoding;
+
+ final MediaType mediaType;
+
+ final BytesMessageEncoder messageEncoder;
+
+ /**
+ * close is typically called from a different thread.
+ */
+ transient boolean closeCalled;
+
+ HttpSender(ZipkinHttpClientSender sender, String baseUrl, String apiPath, BytesEncoder encoder) {
+ this.sender = sender;
+ this.encoding = encoder.encoding();
+ if (encoder.equals(JSON_V2)) {
+ this.mediaType = MediaType.APPLICATION_JSON;
+ this.url = buildUrlWithCustomPathIfNecessary(baseUrl, apiPath,
+ baseUrl + (baseUrl.endsWith("/") ? "" : "/") + "api/v2/spans");
+ }
+ else if (this.encoding == Encoding.PROTO3) {
+ this.mediaType = MediaType.parseMediaType("application/x-protobuf");
+ this.url = buildUrlWithCustomPathIfNecessary(baseUrl, apiPath,
+ baseUrl + (baseUrl.endsWith("/") ? "" : "/") + "api/v2/spans");
+ }
+ else if (this.encoding == Encoding.JSON) {
+ this.mediaType = MediaType.APPLICATION_JSON;
+ this.url = buildUrlWithCustomPathIfNecessary(baseUrl, apiPath,
+ baseUrl + (baseUrl.endsWith("/") ? "" : "/") + "api/v1/spans");
+ }
+ else {
+ throw new UnsupportedOperationException("Unsupported encoding: " + this.encoding.name());
+ }
+ this.messageEncoder = BytesMessageEncoder.forEncoding(this.encoding);
+ }
+
+ private String buildUrlWithCustomPathIfNecessary(final String baseUrl, final String customApiPath,
+ final String defaultUrl) {
+ if (Objects.nonNull(customApiPath)) {
+ return baseUrl
+ + (baseUrl.endsWith("/") || customApiPath.startsWith("/") || customApiPath.isEmpty() ? "" : "/")
+ + customApiPath;
+ }
+ return defaultUrl;
+ }
+
+ @Override
+ public Encoding encoding() {
+ return this.encoding;
+ }
+
+ @Override
+ public int messageMaxBytes() {
+ // This will drop a span larger than 5MiB. Note: values like 512KiB benchmark
+ // better.
+ return 5 * 1024 * 1024;
+ }
+
+ @Override
+ public int messageSizeInBytes(List spans) {
+ return encoding().listSizeInBytes(spans);
+ }
+
+ @Override
+ public Call sendSpans(List encodedSpans) {
+ if (this.closeCalled) {
+ throw new IllegalStateException("close");
+ }
+ return new HttpPostCall(this.messageEncoder.encode(encodedSpans));
+ }
+
+ /**
+ * Sends an empty json message to the configured endpoint.
+ */
+ @Override
+ public CheckResult check() {
+ try {
+ post(new byte[] { '[', ']' });
+ return CheckResult.OK;
+ }
+ catch (Exception e) {
+ return CheckResult.failed(e);
+ }
+ }
+
+ @Override
+ public void close() {
+ this.closeCalled = true;
+ }
+
+ void post(byte[] json) {
+ this.sender.call(this.url, this.mediaType, json);
+ }
+
+ class HttpPostCall extends Call.Base {
+
+ private final byte[] message;
+
+ HttpPostCall(byte[] message) {
+ this.message = message;
+ }
+
+ @Override
+ protected Void doExecute() throws IOException {
+ post(this.message);
+ return null;
+ }
+
+ @Override
+ protected void doEnqueue(Callback callback) {
+ try {
+ post(this.message);
+ callback.onSuccess(null);
+ }
+ catch (RuntimeException | Error e) {
+ callback.onError(e);
+ }
+ }
+
+ @Override
+ public Call clone() {
+ return new HttpPostCall(this.message);
+ }
+
+ }
+
+}
diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/RestTemplateSender.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/RestTemplateSender.java
index 72db2db33..67e70a042 100644
--- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/RestTemplateSender.java
+++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/RestTemplateSender.java
@@ -16,18 +16,10 @@
package org.springframework.cloud.sleuth.zipkin2;
-import java.io.IOException;
import java.net.URI;
-import java.util.List;
-import java.util.Objects;
-import zipkin2.Call;
-import zipkin2.Callback;
-import zipkin2.CheckResult;
import zipkin2.Span;
import zipkin2.codec.BytesEncoder;
-import zipkin2.codec.Encoding;
-import zipkin2.reporter.BytesMessageEncoder;
import zipkin2.reporter.Sender;
import org.springframework.http.HttpHeaders;
@@ -36,29 +28,12 @@ import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.web.client.RestTemplate;
-import static zipkin2.codec.SpanBytesEncoder.JSON_V2;
-
/**
* {@link Sender} that uses {@link RestTemplate} to send spans to Zipkin.
*
* @since 3.0.0
*/
-public class RestTemplateSender extends Sender {
-
- final RestTemplate restTemplate;
-
- final String url;
-
- final Encoding encoding;
-
- final MediaType mediaType;
-
- final BytesMessageEncoder messageEncoder;
-
- /**
- * close is typically called from a different thread.
- */
- transient boolean closeCalled;
+public class RestTemplateSender extends HttpSender {
@Deprecated
public RestTemplateSender(RestTemplate restTemplate, String baseUrl, BytesEncoder encoder) {
@@ -66,89 +41,14 @@ public class RestTemplateSender extends Sender {
}
public RestTemplateSender(RestTemplate restTemplate, String baseUrl, String apiPath, BytesEncoder encoder) {
- this.restTemplate = restTemplate;
- this.encoding = encoder.encoding();
- if (encoder.equals(JSON_V2)) {
- this.mediaType = MediaType.APPLICATION_JSON;
- this.url = buildUrlWithCustomPathIfNecessary(baseUrl, apiPath,
- baseUrl + (baseUrl.endsWith("/") ? "" : "/") + "api/v2/spans");
- }
- else if (this.encoding == Encoding.PROTO3) {
- this.mediaType = MediaType.parseMediaType("application/x-protobuf");
- this.url = buildUrlWithCustomPathIfNecessary(baseUrl, apiPath,
- baseUrl + (baseUrl.endsWith("/") ? "" : "/") + "api/v2/spans");
- }
- else if (this.encoding == Encoding.JSON) {
- this.mediaType = MediaType.APPLICATION_JSON;
- this.url = buildUrlWithCustomPathIfNecessary(baseUrl, apiPath,
- baseUrl + (baseUrl.endsWith("/") ? "" : "/") + "api/v1/spans");
- }
- else {
- throw new UnsupportedOperationException("Unsupported encoding: " + this.encoding.name());
- }
- this.messageEncoder = BytesMessageEncoder.forEncoding(this.encoding);
+ super((url, mediaType, bytes) -> post(url, mediaType, bytes, restTemplate), baseUrl, apiPath, encoder);
}
- private String buildUrlWithCustomPathIfNecessary(final String baseUrl, final String customApiPath,
- final String defaultUrl) {
- if (Objects.nonNull(customApiPath)) {
- return baseUrl
- + (baseUrl.endsWith("/") || customApiPath.startsWith("/") || customApiPath.isEmpty() ? "" : "/")
- + customApiPath;
- }
- return defaultUrl;
- }
-
- @Override
- public Encoding encoding() {
- return this.encoding;
- }
-
- @Override
- public int messageMaxBytes() {
- // This will drop a span larger than 5MiB. Note: values like 512KiB benchmark
- // better.
- return 5 * 1024 * 1024;
- }
-
- @Override
- public int messageSizeInBytes(List spans) {
- return encoding().listSizeInBytes(spans);
- }
-
- @Override
- public Call sendSpans(List encodedSpans) {
- if (this.closeCalled) {
- throw new IllegalStateException("close");
- }
- return new HttpPostCall(this.messageEncoder.encode(encodedSpans));
- }
-
- /**
- * Sends an empty json message to the configured endpoint.
- */
- @Override
- public CheckResult check() {
- try {
- post(new byte[] { '[', ']' });
- return CheckResult.OK;
- }
- catch (Exception e) {
- return CheckResult.failed(e);
- }
- }
-
- @Override
- public void close() {
- this.closeCalled = true;
- }
-
- void post(byte[] json) {
+ private static void post(String url, MediaType mediaType, byte[] json, RestTemplate restTemplate) {
HttpHeaders httpHeaders = new HttpHeaders();
- httpHeaders.setContentType(this.mediaType);
- RequestEntity requestEntity = new RequestEntity<>(json, httpHeaders, HttpMethod.POST,
- URI.create(this.url));
- this.restTemplate.exchange(requestEntity, String.class);
+ httpHeaders.setContentType(mediaType);
+ RequestEntity requestEntity = new RequestEntity<>(json, httpHeaders, HttpMethod.POST, URI.create(url));
+ restTemplate.exchange(requestEntity, String.class);
}
@Override
@@ -156,36 +56,4 @@ public class RestTemplateSender extends Sender {
return "RestTemplateSender{" + url + "}";
}
- class HttpPostCall extends Call.Base {
-
- private final byte[] message;
-
- HttpPostCall(byte[] message) {
- this.message = message;
- }
-
- @Override
- protected Void doExecute() throws IOException {
- post(this.message);
- return null;
- }
-
- @Override
- protected void doEnqueue(Callback callback) {
- try {
- post(this.message);
- callback.onSuccess(null);
- }
- catch (RuntimeException | Error e) {
- callback.onError(e);
- }
- }
-
- @Override
- public Call clone() {
- return new HttpPostCall(this.message);
- }
-
- }
-
}
diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/WebClientSender.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/WebClientSender.java
new file mode 100644
index 000000000..bad08821c
--- /dev/null
+++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/WebClientSender.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013-2021 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.cloud.sleuth.zipkin2;
+
+import java.net.URI;
+
+import zipkin2.Span;
+import zipkin2.codec.BytesEncoder;
+import zipkin2.reporter.Sender;
+
+import org.springframework.http.MediaType;
+import org.springframework.web.reactive.function.client.WebClient;
+
+/**
+ * {@link Sender} that uses {@link WebClient} to send spans to Zipkin.
+ *
+ * @since 3.1.0
+ */
+public class WebClientSender extends HttpSender {
+
+ public WebClientSender(WebClient webClient, String baseUrl, String apiPath, BytesEncoder encoder) {
+ super((url, mediaType, bytes) -> post(url, mediaType, bytes, webClient), baseUrl, apiPath, encoder);
+ }
+
+ private static void post(String url, MediaType mediaType, byte[] json, WebClient webClient) {
+ webClient.post().uri(URI.create(url)).accept(mediaType).bodyValue(json).retrieve().toBodilessEntity()
+ .subscribe();
+ }
+
+ @Override
+ public String toString() {
+ return "WebClientSender{" + url + "}";
+ }
+
+}
diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/ZipkinHttpClientSender.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/ZipkinHttpClientSender.java
new file mode 100644
index 000000000..b4549d1b3
--- /dev/null
+++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/ZipkinHttpClientSender.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013-2021 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.cloud.sleuth.zipkin2;
+
+import org.springframework.http.MediaType;
+
+/**
+ * Sends spans to Zipkin via an HTTP Client.
+ *
+ * @author Marcin Grzejszczak
+ * @since 3.1.1
+ */
+interface ZipkinHttpClientSender {
+
+ /**
+ * Sends spans to Zipkin via an HTTP Client.
+ * @param url Zipkin URL
+ * @param mediaType HTTP message media type
+ * @param payload payload to send
+ */
+ void call(String url, MediaType mediaType, byte[] payload);
+
+}
diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/ZipkinWebClientBuilderProvider.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/ZipkinWebClientBuilderProvider.java
new file mode 100644
index 000000000..98717710e
--- /dev/null
+++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/ZipkinWebClientBuilderProvider.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013-2021 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.cloud.sleuth.zipkin2;
+
+import org.springframework.web.reactive.function.client.WebClient;
+
+/**
+ * A provider for a {@link WebClient.Builder} used to send spans to Zipkin.
+ *
+ * @author Marcin Grzejszczak
+ * @since 3.1.1
+ */
+public interface ZipkinWebClientBuilderProvider {
+
+ WebClient.Builder zipkinWebClientBuilder();
+
+}