Added a sender that works in a reactive environment; fixes gh-2106
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -65,7 +65,8 @@ public class TraceRedisAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingClass("brave.sampler.Sampler")
|
||||
TraceLettuceClientResourcesBuilderCustomizer otherTracersTraceLettuceClientResourcesBuilderCustomizer(Tracing tracing) {
|
||||
TraceLettuceClientResourcesBuilderCustomizer otherTracersTraceLettuceClientResourcesBuilderCustomizer(
|
||||
Tracing tracing) {
|
||||
return new TraceLettuceClientResourcesBuilderCustomizer(tracing);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -46,6 +46,16 @@
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webflux</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons</artifactId>
|
||||
@@ -181,11 +191,6 @@
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -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<Span> 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<byte[]> spans) {
|
||||
return encoding().listSizeInBytes(spans);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Call<Void> sendSpans(List<byte[]> 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<Void> {
|
||||
|
||||
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<Void> callback) {
|
||||
try {
|
||||
post(this.message);
|
||||
callback.onSuccess(null);
|
||||
}
|
||||
catch (RuntimeException | Error e) {
|
||||
callback.onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Call<Void> clone() {
|
||||
return new HttpPostCall(this.message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Span> encoder) {
|
||||
@@ -66,89 +41,14 @@ public class RestTemplateSender extends Sender {
|
||||
}
|
||||
|
||||
public RestTemplateSender(RestTemplate restTemplate, String baseUrl, String apiPath, BytesEncoder<Span> 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<byte[]> spans) {
|
||||
return encoding().listSizeInBytes(spans);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Call<Void> sendSpans(List<byte[]> 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<byte[]> requestEntity = new RequestEntity<>(json, httpHeaders, HttpMethod.POST,
|
||||
URI.create(this.url));
|
||||
this.restTemplate.exchange(requestEntity, String.class);
|
||||
httpHeaders.setContentType(mediaType);
|
||||
RequestEntity<byte[]> 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<Void> {
|
||||
|
||||
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<Void> callback) {
|
||||
try {
|
||||
post(this.message);
|
||||
callback.onSuccess(null);
|
||||
}
|
||||
catch (RuntimeException | Error e) {
|
||||
callback.onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Call<Void> clone() {
|
||||
return new HttpPostCall(this.message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Span> 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 + "}";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user