diff --git a/README.adoc b/README.adoc index 4b30082a6..ab8207b34 100644 --- a/README.adoc +++ b/README.adoc @@ -322,6 +322,7 @@ Example of setting baggage on a span: ---- Span initialSpan = this.tracer.createSpan("span"); initialSpan.setBaggageItem("foo", "bar"); +initialSpan.setBaggageItem("UPPER_CASE", "someValue"); ---- ===== Baggage vs. Span Tags diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index f1175c263..bbf232e53 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -484,6 +484,26 @@ Stream based span reporting). spring.zipkin.locator.discovery.enabled: true ---- +== Sending spans to Zipkin + +By default if you add `spring-cloud-starter-zipkin` as a dependency to your project, +when the span is closed, it will be sent to Zipkin over HTTP. The communication +is asynchronous. You can configure the URL by setting the `spring.zipkin.baseUrl` +property as follows: + +[source,yaml] +---- +spring.zipkin.baseUrl: http://192.168.99.100:9411/ +---- + +If you want to find Zipkin via service discovery it's enough to pass the +Zipkin's service id inside the URL (example for `zipkinserver` service id) + +[source,yaml] +---- +spring.zipkin.baseUrl: http://zipkinserver/ +---- + == Span Data as Messages You can accumulate and send span data over diff --git a/pom.xml b/pom.xml index 1b22ce281..2b03f2271 100644 --- a/pom.xml +++ b/pom.xml @@ -226,12 +226,6 @@ 3.0.0 test - - com.github.tomakehurst - wiremock - 2.6.0 - test - pl.pragmatists JUnitParams diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 8b6883210..5e0b36e69 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -88,6 +88,11 @@ zipkin-junit test + + org.awaitility + awaitility + test + com.squareup.okhttp3 diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java index da83b58e6..ce8f33d2f 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java @@ -16,6 +16,7 @@ package org.springframework.cloud.sleuth.zipkin; +import java.net.URI; import java.util.ArrayList; import java.util.List; @@ -24,9 +25,11 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureBefore; 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.ConditionalOnProperty; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.commons.util.InetUtils; import org.springframework.cloud.sleuth.Sampler; @@ -64,6 +67,7 @@ import org.springframework.web.client.RestTemplate; public class ZipkinAutoConfiguration { @Autowired(required = false) List spanAdjusters = new ArrayList<>(); + @Autowired ZipkinUrlExtractor extractor; @Bean @ConditionalOnMissingBean @@ -71,10 +75,51 @@ public class ZipkinAutoConfiguration { ZipkinRestTemplateCustomizer zipkinRestTemplateCustomizer) { RestTemplate restTemplate = new RestTemplate(); zipkinRestTemplateCustomizer.customize(restTemplate); - return new HttpZipkinSpanReporter(restTemplate, zipkin.getBaseUrl(), zipkin.getFlushInterval(), + String zipkinUrl = this.extractor.zipkinUrl(zipkin); + return new HttpZipkinSpanReporter(restTemplate, zipkinUrl, zipkin.getFlushInterval(), spanMetricReporter); } + @Configuration + @ConditionalOnClass(DiscoveryClient.class) + static class DiscoveryClientZipkinUrlExtractorConfiguration { + + @Autowired(required = false) DiscoveryClient discoveryClient; + + @Bean + ZipkinUrlExtractor zipkinUrlExtractor() { + final DiscoveryClient discoveryClient = this.discoveryClient; + return new ZipkinUrlExtractor() { + @Override + public String zipkinUrl(ZipkinProperties zipkinProperties) { + if (discoveryClient != null) { + URI uri = URI.create(zipkinProperties.getBaseUrl()); + String host = uri.getHost(); + List instances = discoveryClient.getInstances(host); + if (!instances.isEmpty()) { + return instances.get(0).getUri().toString(); + } + } + return zipkinProperties.getBaseUrl(); + } + }; + } + } + + @Configuration + @ConditionalOnMissingClass("org.springframework.cloud.client.discovery.DiscoveryClient") + static class DefaultZipkinUrlExtractorConfiguration { + @Bean + ZipkinUrlExtractor zipkinUrlExtractor() { + return new ZipkinUrlExtractor() { + @Override + public String zipkinUrl(ZipkinProperties zipkinProperties) { + return zipkinProperties.getBaseUrl(); + } + }; + } + } + @Bean @ConditionalOnMissingBean public ZipkinRestTemplateCustomizer zipkinRestTemplateCustomizer(ZipkinProperties zipkinProperties) { @@ -156,3 +201,7 @@ public class ZipkinAutoConfiguration { } } + +interface ZipkinUrlExtractor { + String zipkinUrl(ZipkinProperties zipkinProperties); +} \ No newline at end of file diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinProperties.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinProperties.java index 1144d0d0c..037a9dc2f 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinProperties.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinProperties.java @@ -26,7 +26,10 @@ import org.springframework.boot.context.properties.ConfigurationProperties; */ @ConfigurationProperties("spring.zipkin") public class ZipkinProperties { - /** URL of the zipkin query server instance. */ + /** URL of the zipkin query server instance. You can also provide + * the service id of the Zipkin server if Zipkin's registered in + * service discovery (e.g. http://zipkinserver/) + */ private String baseUrl = "http://localhost:9411/"; private boolean enabled = true; private int flushInterval = 1; diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinDiscoveryClientTests.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinDiscoveryClientTests.java new file mode 100644 index 000000000..80027526c --- /dev/null +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinDiscoveryClientTests.java @@ -0,0 +1,118 @@ +package org.springframework.cloud.sleuth.zipkin; + +import static org.assertj.core.api.BDDAssertions.then; +import static org.springframework.cloud.sleuth.zipkin.ZipkinDiscoveryClientTests.ZIPKIN_RULE; + +import java.io.IOException; +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.assertj.core.api.BDDAssertions; +import org.awaitility.Awaitility; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.SpanReporter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.SocketUtils; + +import zipkin.junit.ZipkinRule; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = ZipkinDiscoveryClientTests.Config.class, + properties = "spring.zipkin.baseUrl=http://zipkin/") +public class ZipkinDiscoveryClientTests { + + @ClassRule public static ZipkinRule ZIPKIN_RULE = new ZipkinRule(); + + @Autowired SpanReporter spanReporter; + + @Test + public void shouldUseDiscoveryClientToFindZipkinUrlIfPresent() throws Exception { + Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).name("foo") + .build(); + + this.spanReporter.report(span); + + Awaitility.await().untilAsserted(() -> then(ZIPKIN_RULE.httpRequestCount()).isGreaterThan(0)); + } + + @Configuration + @EnableAutoConfiguration + static class Config { + @Bean + DiscoveryClient client() { + return new ZipkinDiscoveryClient(); + } + } +} + + +class ZipkinDiscoveryClient implements DiscoveryClient { + + @Override + public String description() { + return ""; + } + + @Override + public ServiceInstance getLocalServiceInstance() { + return null; + } + + @Override + public List getInstances(String s) { + if ("zipkin".equals(s)) { + return Collections.singletonList(new ServiceInstance() { + @Override + public String getServiceId() { + return "zipkin"; + } + + @Override + public String getHost() { + return "localhost"; + } + + @Override + public int getPort() { + return URI.create(ZIPKIN_RULE.httpUrl()).getPort(); + } + + @Override + public boolean isSecure() { + return false; + } + + @Override + public URI getUri() { + return URI.create(ZIPKIN_RULE.httpUrl()); + } + + @Override + public Map getMetadata() { + return null; + } + }); + } + return Collections.emptyList(); + } + + @Override + public List getServices() { + return Collections.singletonList("zipkin"); + } + +} \ No newline at end of file diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java index b5aecddaa..878cd5c8b 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListenerTests.java @@ -17,11 +17,16 @@ package org.springframework.cloud.sleuth.zipkin; import org.assertj.core.api.Condition; +import org.junit.Ignore; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; import zipkin.Constants; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import javax.annotation.PostConstruct; import org.junit.Test; @@ -56,7 +61,6 @@ import static org.junit.Assert.assertEquals; public class ZipkinSpanListenerTests { @Autowired Tracer tracer; - @Autowired ApplicationContext application; @Autowired TestConfiguration test; @Autowired ZipkinSpanListener spanListener; @Autowired ZipkinSpanReporter spanReporter; @@ -337,4 +341,4 @@ public class ZipkinSpanListenerTests { } -} +} \ No newline at end of file