diff --git a/pom.xml b/pom.xml
index 772572329..c3812f0b9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -58,6 +58,11 @@
brave-client
${brave.version}
+
+ com.github.kristofa
+ brave-impl
+ ${brave.version}
+
com.github.kristofa
brave-impl-spring
@@ -72,6 +77,12 @@
com.github.kristofa
brave-tracefilters
${brave.version}
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
com.github.kristofa
diff --git a/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleApplication.java b/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleApplication.java
index 14afd6a9d..ace29542f 100644
--- a/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleApplication.java
+++ b/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleApplication.java
@@ -1,14 +1,19 @@
package org.springframework.cloud.sleuth.sample;
+import java.util.Random;
+
+import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent;
+import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
-import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.client.RestTemplate;
/**
* @author Spencer Gibb
@@ -17,19 +22,43 @@ import org.springframework.web.bind.annotation.RestController;
@EnableAutoConfiguration
@RestController
@Slf4j
-public class SampleApplication {
+public class SampleApplication implements ApplicationListener {
public static final String CLIENT_NAME = "testApp";
- @Autowired
- Environment env;
+ @Autowired
+ private RestTemplate restTemplate;
+ private int port;
- @RequestMapping("/")
+ @SneakyThrows
+ @RequestMapping("/")
public String hi() {
- return "hi";
+ final Random random = new Random();
+ Thread.sleep(random.nextInt(1000));
+
+ String s = restTemplate.getForObject("http://localhost:" + port + "/hi2", String.class);
+ return "hi/"+s;
}
+ @SneakyThrows
+ @RequestMapping("/hi2")
+ public String hi2() {
+ final Random random = new Random();
+ Thread.sleep(random.nextInt(1000));
+ return "hi2";
+ }
+
public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args);
}
+
+ /*@Bean
+ public SpanCollector spanCollector() {
+ return new LoggingSpanCollectorImpl();
+ }*/
+
+ @Override
+ public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) {
+ port = event.getEmbeddedServletContainer().getPort();
+ }
}
diff --git a/spring-cloud-sleuth-sample/src/main/resources/application.yml b/spring-cloud-sleuth-sample/src/main/resources/application.yml
index 0547869c1..9dafd9d4e 100644
--- a/spring-cloud-sleuth-sample/src/main/resources/application.yml
+++ b/spring-cloud-sleuth-sample/src/main/resources/application.yml
@@ -1,5 +1,5 @@
server:
- port: 8080
+ port: 3380
spring:
application:
diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml
index 4715806dd..0c257652c 100644
--- a/spring-cloud-sleuth-zipkin/pom.xml
+++ b/spring-cloud-sleuth-zipkin/pom.xml
@@ -24,6 +24,27 @@
org.springframework.boot
spring-boot-starter-actuator
+ true
+
+
+ com.github.kristofa
+ brave-client
+
+
+ com.github.kristofa
+ brave-impl
+
+
+ com.github.kristofa
+ brave-impl-spring
+
+
+ com.github.kristofa
+ brave-tracefilters
+
+
+ com.github.kristofa
+ brave-zipkin-spancollector
org.projectlombok
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 dc9e7d965..f80ef8dd3 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
@@ -1,12 +1,86 @@
package org.springframework.cloud.sleuth.zipkin;
+import java.util.List;
+
+import com.github.kristofa.brave.zipkin.ZipkinSpanCollector;
+import org.springframework.beans.factory.annotation.Autowired;
+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;
+
+import com.github.kristofa.brave.AnnotationSubmitterConfig;
+import com.github.kristofa.brave.ClientTracer;
+import com.github.kristofa.brave.ClientTracerConfig;
+import com.github.kristofa.brave.EndPointSubmitterConfig;
+import com.github.kristofa.brave.FixedSampleRateTraceFilter;
+import com.github.kristofa.brave.LoggingSpanCollectorImpl;
+import com.github.kristofa.brave.ServerSpanThreadBinderConfig;
+import com.github.kristofa.brave.ServerTracerConfig;
+import com.github.kristofa.brave.SpanCollector;
+import com.github.kristofa.brave.TraceFilter;
+import com.github.kristofa.brave.TraceFilters;
+import com.github.kristofa.brave.client.ClientRequestInterceptor;
+import com.github.kristofa.brave.client.ClientResponseInterceptor;
+import com.github.kristofa.brave.client.spanfilter.SpanNameFilter;
+import com.google.common.base.Optional;
/**
* @author Spencer Gibb
*/
@Configuration
@EnableConfigurationProperties
+@ConditionalOnClass(ServerTracerConfig.class)
+@Import({ AnnotationSubmitterConfig.class, ClientTracerConfig.class,
+ EndPointSubmitterConfig.class, ServerSpanThreadBinderConfig.class,
+ ServerTracerConfig.class })
public class ZipkinAutoConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public SpanCollector spanCollector() {
+ return new ZipkinSpanCollector(zipkinProperties().getHost(),
+ zipkinProperties().getPort());
+ }
+
+ @Bean
+ public ZipkinProperties zipkinProperties() {
+ return new ZipkinProperties();
+ }
+
+ @Bean
+ public FixedSampleRateTraceFilter fixedSampleRateTraceFilter() {
+ return new FixedSampleRateTraceFilter(zipkinProperties().getFixedSampleRate());
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public TraceFilters traceFilters(List traceFilters) {
+ return new TraceFilters(traceFilters);
+ }
+
+ @Configuration
+ protected static class InterceptorConfig {
+
+ @Autowired
+ private ClientTracer clientTracer;
+
+ @Autowired(required = false)
+ private SpanNameFilter spanNameFilter;
+
+ @Bean
+ @ConditionalOnMissingBean
+ public ClientRequestInterceptor clientRequestInterceptor() {
+ return new ClientRequestInterceptor(clientTracer,
+ Optional.fromNullable(spanNameFilter));
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public ClientResponseInterceptor clientResponseInterceptor() {
+ return new ClientResponseInterceptor(clientTracer);
+ }
+ }
}
diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinHandlerInterceptor.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinHandlerInterceptor.java
new file mode 100644
index 000000000..585f5f203
--- /dev/null
+++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinHandlerInterceptor.java
@@ -0,0 +1,161 @@
+package org.springframework.cloud.sleuth.zipkin;
+
+import static com.github.kristofa.brave.BraveHttpHeaders.*;
+
+import com.github.kristofa.brave.EndPointSubmitter;
+import com.github.kristofa.brave.IdConversion;
+import com.github.kristofa.brave.ServerTracer;
+import lombok.Data;
+import lombok.extern.apachecommons.CommonsLog;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.server.ServletServerHttpRequest;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.util.UriComponents;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Spencer Gibb
+ */
+@CommonsLog
+public class ZipkinHandlerInterceptor implements HandlerInterceptor {
+
+ private final EndPointSubmitter endPointSubmitter;
+ private final ServerTracer serverTracer;
+
+ public ZipkinHandlerInterceptor(EndPointSubmitter endPointSubmitter, ServerTracer serverTracer) {
+ this.endPointSubmitter = endPointSubmitter;
+ this.serverTracer = serverTracer;
+ }
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+ submitEndpoint(request);
+
+ serverTracer.clearCurrentSpan();
+ final TraceData traceData = getTraceData(request);
+
+ if (Boolean.FALSE.equals(traceData.getShouldBeSampled())) {
+ serverTracer.setStateNoTracing();
+ log.debug("Received indication that we should NOT trace.");
+ } else {
+ final String spanName = getSpanName(request, traceData);
+ if (traceData.getTraceId() != null && traceData.getSpanId() != null) {
+
+ log.debug("Received span information as part of request.");
+ serverTracer.setStateCurrentTrace(traceData.getTraceId(), traceData.getSpanId(),
+ traceData.getParentSpanId(), spanName);
+ } else {
+ log.debug("Received no span state.");
+ serverTracer.setStateUnknown(spanName);
+ }
+ serverTracer.setServerReceived();
+ }
+
+ return true;
+ }
+
+ @Override
+ public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
+ // We can submit this in any case. When server state is not set or
+ // we should not trace this request nothing will happen.
+ log.debug("Sending server send.");
+ try {
+ serverTracer.setServerSend();
+ } finally {
+ serverTracer.clearCurrentSpan();
+ }
+ }
+
+ @Override
+ public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
+ }
+
+ private void submitEndpoint(HttpServletRequest servletRequest) {
+ if (!endPointSubmitter.endPointSubmitted()) {
+ final String localAddr = servletRequest.getLocalAddr();
+ final int localPort = servletRequest.getLocalPort();
+ final String contextPath = servletRequest.getContextPath();
+ log.debug("Setting endpoint: addr: "+localAddr+", port: "+localPort+", contextpath: "+ contextPath);
+ endPointSubmitter.submit(localAddr, localPort, contextPath);
+ }
+ }
+
+
+ private TraceData getTraceData(final HttpServletRequest request) {
+ ServletServerHttpRequest req = new ServletServerHttpRequest(request);
+ HttpHeaders headers = req.getHeaders();
+
+ TraceData traceData = new TraceData();
+
+ for (Map.Entry> headerEntry : headers.entrySet()) {
+ log.debug(headerEntry.getKey() +" = "+ headerEntry.getValue());
+ if (TraceId.getName().equalsIgnoreCase(headerEntry.getKey())) {
+ traceData.setTraceId(getFirstLong(headerEntry));
+ } else if (SpanId.getName().equalsIgnoreCase(headerEntry.getKey())) {
+ traceData.setSpanId(getFirstLong(headerEntry));
+ } else if (ParentSpanId.getName().equalsIgnoreCase(headerEntry.getKey())) {
+ traceData.setParentSpanId(getFirstLong(headerEntry));
+ } else if (Sampled.getName().equalsIgnoreCase(headerEntry.getKey())) {
+ traceData.setShouldBeSampled(getFirstBoolean(headerEntry));
+ } else if (SpanName.getName().equalsIgnoreCase(headerEntry.getKey())) {
+ traceData.setSpanName(getFirstString(headerEntry));
+ }
+ }
+ return traceData;
+ }
+
+ private String getSpanName(final HttpServletRequest request, final TraceData traceData) {
+ if (StringUtils.isNotBlank(traceData.getSpanName())) {
+ return traceData.getSpanName();
+ } else {
+ //TODO: what is the functional equivalent of resteasy request.getPreprocessedPath();
+ UriComponents components = UriComponentsBuilder.fromUriString(request.getRequestURL().toString()).build();
+ StringBuilder preprocessedPath = new StringBuilder();
+ for (String segment : components.getPathSegments()) {
+ preprocessedPath.append("/").append(segment);
+ }
+ if (preprocessedPath.length() == 0) {
+ preprocessedPath.append("/");
+ }
+ return preprocessedPath.toString();
+ }
+ }
+
+ private Long getFirstLong(final Map.Entry> headerEntry) {
+ final String value = getFirstString(headerEntry);
+ if (value == null) {
+ return null;
+ }
+ return IdConversion.convertToLong(value);
+
+ }
+
+ private Boolean getFirstBoolean(final Map.Entry> headerEntry) {
+ final String firstStringValueFor = getFirstString(headerEntry);
+ return firstStringValueFor == null ? null : Boolean.valueOf(firstStringValueFor);
+ }
+
+ private String getFirstString(final Map.Entry> headerEntry) {
+ final List values = headerEntry.getValue();
+ if (values != null && values.size() > 0) {
+ return headerEntry.getValue().get(0);
+ }
+ return null;
+ }
+
+ @Data
+ class TraceData {
+ private Long traceId;
+ private Long spanId;
+ private Long parentSpanId;
+ private Boolean shouldBeSampled;
+ private String spanName;
+ }
+}
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
new file mode 100644
index 000000000..ce170fd63
--- /dev/null
+++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinProperties.java
@@ -0,0 +1,16 @@
+package org.springframework.cloud.sleuth.zipkin;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * @author Spencer Gibb
+ */
+@ConfigurationProperties("spring.cloud.sleuth.zipkin")
+@Data
+public class ZipkinProperties {
+ // Sample rate = 1 means every request will get traced.
+ private int fixedSampleRate = 1;
+ private String host = "localhost";
+ private int port = 9410;
+}
diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinRestTemplateInterceptor.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinRestTemplateInterceptor.java
new file mode 100644
index 000000000..718feae85
--- /dev/null
+++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinRestTemplateInterceptor.java
@@ -0,0 +1,99 @@
+package org.springframework.cloud.sleuth.zipkin;
+
+import com.github.kristofa.brave.BraveHttpHeaders;
+import com.github.kristofa.brave.ClientRequestAdapter;
+import com.github.kristofa.brave.ClientResponseAdapter;
+import com.github.kristofa.brave.client.ClientRequestInterceptor;
+import com.github.kristofa.brave.client.ClientResponseInterceptor;
+import com.google.common.base.Optional;
+import lombok.SneakyThrows;
+import org.springframework.http.HttpRequest;
+import org.springframework.http.client.ClientHttpRequestExecution;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.http.client.ClientHttpResponse;
+
+import java.io.IOException;
+import java.net.URI;
+
+/**
+ * @author Spencer Gibb
+ */
+public class ZipkinRestTemplateInterceptor implements ClientHttpRequestInterceptor {
+
+ private final ClientRequestInterceptor clientRequestInterceptor;
+ private final ClientResponseInterceptor clientResponseInterceptor;
+
+ public ZipkinRestTemplateInterceptor(ClientRequestInterceptor clientRequestInterceptor, ClientResponseInterceptor clientResponseInterceptor) {
+ this.clientRequestInterceptor = clientRequestInterceptor;
+ //TODO: ClientResponseInterceptor assumes >= 300 is error
+ this.clientResponseInterceptor = clientResponseInterceptor;
+ }
+
+ @SneakyThrows
+ @Override
+ public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
+
+ clientRequestInterceptor.handle(new RequestAdapter(request), Optional.absent());
+
+ ClientHttpResponse response = null;
+ Exception exception = null;
+ try {
+ response = execution.execute(request, body);
+ } catch (final Exception e) {
+ exception = e;
+ }
+
+ clientResponseInterceptor.handle(new ResponseAdapter(response));
+ if(exception != null) {
+ throw exception;
+ }
+ return response;
+ }
+
+ class RequestAdapter implements ClientRequestAdapter {
+
+ HttpRequest request;
+
+ public RequestAdapter(HttpRequest request) {
+ this.request = request;
+ }
+
+ @Override
+ public URI getUri() {
+ return request.getURI();
+ }
+
+ @Override
+ public String getMethod() {
+ return request.getMethod().toString();
+ }
+
+ @Override
+ public Optional getSpanName() {
+ String spanNameHeader = request.getHeaders().getFirst(BraveHttpHeaders.SpanName.getName());
+ return Optional.fromNullable(spanNameHeader);
+ }
+
+ @Override
+ public void addHeader(String header, String value) {
+ request.getHeaders().add(header, value);
+ }
+ }
+
+ class ResponseAdapter implements ClientResponseAdapter {
+ ClientHttpResponse response;
+
+ public ResponseAdapter(ClientHttpResponse response) {
+ this.response = response;
+ }
+
+ @SneakyThrows
+ @Override
+ public int getStatusCode() {
+ if (response == null) {
+ return 0;
+ }
+ return response.getRawStatusCode();
+ }
+ }
+}
diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/web/ZipkinWebAutoConfiguration.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/web/ZipkinWebAutoConfiguration.java
new file mode 100644
index 000000000..efa048c0a
--- /dev/null
+++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/web/ZipkinWebAutoConfiguration.java
@@ -0,0 +1,86 @@
+package org.springframework.cloud.sleuth.zipkin.web;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.cloud.sleuth.zipkin.ZipkinAutoConfiguration;
+import org.springframework.cloud.sleuth.zipkin.ZipkinHandlerInterceptor;
+import org.springframework.cloud.sleuth.zipkin.ZipkinRestTemplateInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
+
+import com.github.kristofa.brave.EndPointSubmitter;
+import com.github.kristofa.brave.ServerTracer;
+import com.github.kristofa.brave.ServerTracerConfig;
+import com.github.kristofa.brave.client.ClientRequestInterceptor;
+import com.github.kristofa.brave.client.ClientResponseInterceptor;
+
+/**
+ * @author Spencer Gibb
+ */
+@Configuration
+@ConditionalOnClass(ServerTracerConfig.class)
+@ConditionalOnWebApplication
+@AutoConfigureAfter(ZipkinAutoConfiguration.class)
+public class ZipkinWebAutoConfiguration {
+
+ @Autowired
+ private EndPointSubmitter endPointSubmitter;
+
+ @Autowired
+ private ServerTracer serverTracer;
+
+ @Bean
+ public ZipkinHandlerInterceptor zipkinHandlerInterceptor() {
+ return new ZipkinHandlerInterceptor(endPointSubmitter, serverTracer);
+ }
+
+ @Bean
+ public WebMvcConfigurerAdapter webMvcConfigurerAdapter() {
+ return new ZipkinWebConfigurer(zipkinHandlerInterceptor());
+ }
+
+ @Configuration
+ protected static class RestTemplateConfig {
+
+ @Autowired
+ private ClientRequestInterceptor clientRequestInterceptor;
+
+ @Autowired
+ private ClientResponseInterceptor clientResponseInterceptor;
+
+ @Bean
+ @ConditionalOnMissingBean
+ public RestTemplate restTemplate() {
+ //TODO: howto add this to an existing restTemplate without circular dependencies
+ RestTemplate restTemplate = new RestTemplate();
+ restTemplate.getInterceptors().add(zipkinRestTemplateInterceptor());
+ return restTemplate;
+ }
+
+ @Bean
+ public ZipkinRestTemplateInterceptor zipkinRestTemplateInterceptor() {
+ return new ZipkinRestTemplateInterceptor(clientRequestInterceptor,
+ clientResponseInterceptor);
+ }
+
+ }
+
+ protected static class ZipkinWebConfigurer extends WebMvcConfigurerAdapter {
+ private ZipkinHandlerInterceptor interceptor;
+
+ public ZipkinWebConfigurer(ZipkinHandlerInterceptor interceptor) {
+ this.interceptor = interceptor;
+ }
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ registry.addInterceptor(interceptor).addPathPatterns("/**");
+ }
+ }
+}
diff --git a/spring-cloud-sleuth-zipkin/src/main/resources/META-INF/spring.factories b/spring-cloud-sleuth-zipkin/src/main/resources/META-INF/spring.factories
index 0fca7baa9..ef706534a 100644
--- a/spring-cloud-sleuth-zipkin/src/main/resources/META-INF/spring.factories
+++ b/spring-cloud-sleuth-zipkin/src/main/resources/META-INF/spring.factories
@@ -1,3 +1,4 @@
# Auto Configuration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-org.springframework.cloud.sleuth.zipkin.ZipkinAutoConfiguration
+org.springframework.cloud.sleuth.zipkin.ZipkinAutoConfiguration,\
+org.springframework.cloud.sleuth.zipkin.web.ZipkinWebAutoConfiguration