diff --git a/docs/src/main/asciidoc/README.adoc b/docs/src/main/asciidoc/README.adoc index 39895eaf4..390ea5ea1 100644 --- a/docs/src/main/asciidoc/README.adoc +++ b/docs/src/main/asciidoc/README.adoc @@ -10,15 +10,10 @@ include::intro.adoc[] == Running the sample -1. Download and build zipkin -2. Install redis and run `redis-server` -3. `cd zipkin` -4. `bin/collector redis` from [here](https://github.com/twitter/zipkin/blob/master/doc/redis.md) -5. `bin/query redis` -6. `bin/web` -7. run sample application -8. hit `http://localhost:3380` -9. goto `http://localhost:8080` for zipkin web +1. Run [Zipkin](https://github.com/openzipkin/zipkin), e.g. via docker compose (there's a `docker-compose.yml` in [Spring Cloud Sleuth](https://github.com/spring-cloud-incubator/spring-cloud-sleuth), or in [Docker Zipkin](https://github.com/openzipkin/docker-zipkin) +7. Run sample application +8. Hit `http://localhost:3380` +9. Goto `http://localhost:8082` for zipkin web == Building diff --git a/docs/src/main/asciidoc/intro.adoc b/docs/src/main/asciidoc/intro.adoc index 0fe0cdd50..af89eba08 100644 --- a/docs/src/main/asciidoc/intro.adoc +++ b/docs/src/main/asciidoc/intro.adoc @@ -1,5 +1,3 @@ -TODO: intro Spring Cloud Sleuth - === Terminology Spring Cloud Sleuth borrows http://research.google.com/pubs/pub36356.html[Dapper's] terminology. diff --git a/docs/src/main/asciidoc/spring-cloud-consul.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc similarity index 100% rename from docs/src/main/asciidoc/spring-cloud-consul.adoc rename to docs/src/main/asciidoc/spring-cloud-sleuth.adoc diff --git a/pom.xml b/pom.xml index a9823192e..2c4555043 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.0.2.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index e1ca6b9d9..60d3efd03 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -66,7 +66,7 @@ org.projectlombok lombok - provided + true org.springframework.boot diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/MilliSpan.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/MilliSpan.java index 858060afb..3a361e350 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/MilliSpan.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/MilliSpan.java @@ -1,5 +1,7 @@ package org.springframework.cloud.sleuth; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -22,10 +24,10 @@ public class MilliSpan implements Span { @Singular private List parents; private String spanId; - private Map kVAnnotations; + private Map kVAnnotations = new LinkedHashMap<>(); private String processId; @Singular - private List timelineAnnotations; + private List timelineAnnotations = new ArrayList<>(); @Override public synchronized void stop() { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceScope.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceScope.java index 695f913b0..61374d30c 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceScope.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceScope.java @@ -5,8 +5,10 @@ import java.io.Closeable; import lombok.SneakyThrows; import lombok.Value; import lombok.experimental.NonFinal; +import lombok.extern.apachecommons.CommonsLog; import org.springframework.cloud.sleuth.event.SpanStoppedEvent; +import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.ApplicationEventPublisher; /** @@ -14,6 +16,7 @@ import org.springframework.context.ApplicationEventPublisher; */ @Value @NonFinal +@CommonsLog public class TraceScope implements Closeable { private final ApplicationEventPublisher publisher; @@ -45,41 +48,41 @@ public class TraceScope implements Closeable { * @return the same Span object */ public Span detach() { - if (detached) { - Utils.error("Tried to detach trace span " + span + " but " + + if (this.detached) { + ExceptionUtils.error("Tried to detach trace span " + this.span + " but " + "it has already been detached."); } - detached = true; + this.detached = true; Span cur = TraceContextHolder.getCurrentSpan(); - if (cur != span) { - Utils.error("Tried to detach trace span " + span + " but " + + if (cur != this.span) { + ExceptionUtils.error("Tried to detach trace span " + this.span + " but " + "it is not the current span for the " + Thread.currentThread().getName() + " thread. You have " + "probably forgotten to close or detach " + cur); } else { - TraceContextHolder.setCurrentSpan(savedSpan); + TraceContextHolder.setCurrentSpan(this.savedSpan); } - return span; + return this.span; } @Override @SneakyThrows public void close() { - if (detached) { + if (this.detached) { return; } - detached = true; + this.detached = true; Span cur = TraceContextHolder.getCurrentSpan(); - if (cur != span) { - Utils.error("Tried to close trace span " + span + " but " + + if (cur != this.span) { + ExceptionUtils.error("Tried to close trace span " + this.span + " but " + "it is not the current span for the " + Thread.currentThread().getName() + " thread. You have " + "probably forgotten to close or detach " + cur); } else { - span.stop(); - this.publisher.publishEvent(new SpanStoppedEvent(this, span)); - TraceContextHolder.setCurrentSpan(savedSpan); + this.span.stop(); + this.publisher.publishEvent(new SpanStoppedEvent(this, this.span)); + TraceContextHolder.setCurrentSpan(this.savedSpan); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java similarity index 63% rename from spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceAutoConfiguration.java rename to spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java index 84ff1e3b6..cc8782977 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java @@ -1,7 +1,12 @@ -package org.springframework.cloud.sleuth; +package org.springframework.cloud.sleuth.autoconfig; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.sleuth.IdGenerator; +import org.springframework.cloud.sleuth.RandomUuidGenerator; +import org.springframework.cloud.sleuth.Sampler; +import org.springframework.cloud.sleuth.Trace; import org.springframework.cloud.sleuth.sampler.IsTracingSampler; +import org.springframework.cloud.sleuth.trace.DefaultTrace; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -20,13 +25,13 @@ public class TraceAutoConfiguration { @Bean @ConditionalOnMissingBean - public Sampler defaultSampler() { + public Sampler defaultSampler() { return new IsTracingSampler(); } @Bean @ConditionalOnMissingBean - public Trace trace(Sampler sampler, IdGenerator idGenerator, + public Trace trace(Sampler sampler, IdGenerator idGenerator, ApplicationEventPublisher publisher) { return new DefaultTrace(sampler, idGenerator, publisher); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/ArrayListSpanAccumulator.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/event/ArrayListSpanAccumulator.java similarity index 79% rename from spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/ArrayListSpanAccumulator.java rename to spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/event/ArrayListSpanAccumulator.java index 543ac1d04..1bb3d7f7e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/ArrayListSpanAccumulator.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/event/ArrayListSpanAccumulator.java @@ -1,10 +1,10 @@ -package org.springframework.cloud.sleuth; +package org.springframework.cloud.sleuth.event; import java.util.ArrayList; import lombok.Value; -import org.springframework.cloud.sleuth.event.SpanStoppedEvent; +import org.springframework.cloud.sleuth.Span; import org.springframework.context.ApplicationListener; /** diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/event/SpanStartedEvent.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/event/SpanStartedEvent.java index 49e5a7cdb..8a0f9314b 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/event/SpanStartedEvent.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/event/SpanStartedEvent.java @@ -1,6 +1,8 @@ package org.springframework.cloud.sleuth.event; +import lombok.EqualsAndHashCode; import lombok.Value; + import org.springframework.cloud.sleuth.Span; import org.springframework.context.ApplicationEvent; @@ -8,6 +10,8 @@ import org.springframework.context.ApplicationEvent; * @author Spencer Gibb */ @Value +@EqualsAndHashCode(callSuper=false) +@SuppressWarnings("serial") public class SpanStartedEvent extends ApplicationEvent { private final Span span; diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/event/SpanStoppedEvent.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/event/SpanStoppedEvent.java index 5272bfc92..1b45c066e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/event/SpanStoppedEvent.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/event/SpanStoppedEvent.java @@ -1,6 +1,8 @@ package org.springframework.cloud.sleuth.event; +import lombok.EqualsAndHashCode; import lombok.Value; + import org.springframework.cloud.sleuth.Span; import org.springframework.context.ApplicationEvent; @@ -8,6 +10,8 @@ import org.springframework.context.ApplicationEvent; * @author Spencer Gibb */ @Value +@EqualsAndHashCode(callSuper=false) +@SuppressWarnings("serial") public class SpanStoppedEvent extends ApplicationEvent { private final Span span; diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/TraceRunnable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/TraceRunnable.java index 259360f6f..4a46cb32d 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/TraceRunnable.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/TraceRunnable.java @@ -1,5 +1,6 @@ package org.springframework.cloud.sleuth.instrument; +import lombok.EqualsAndHashCode; import lombok.Value; import org.springframework.cloud.sleuth.Span; @@ -10,6 +11,7 @@ import org.springframework.cloud.sleuth.TraceScope; * @author Spencer Gibb */ @Value +@EqualsAndHashCode(callSuper=false) public class TraceRunnable extends TraceDelegate implements Runnable { public TraceRunnable(Trace trace, Runnable delagate) { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java index b6dee3321..f43913d87 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java @@ -30,7 +30,10 @@ import javax.servlet.http.HttpServletResponse; import org.springframework.cloud.sleuth.Trace; import org.springframework.cloud.sleuth.TraceInfo; import org.springframework.cloud.sleuth.TraceScope; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.UrlPathHelper; /** * Filter that takes the value of the {@link Trace#SPAN_ID_NAME} and @@ -44,13 +47,15 @@ import org.springframework.web.filter.OncePerRequestFilter; * @author Marcin Grzejszczak, 4financeIT * @author Spencer Gibb */ +@Order(Ordered.HIGHEST_PRECEDENCE + 5) public class TraceFilter extends OncePerRequestFilter { public static final Pattern DEFAULT_SKIP_PATTERN = Pattern - .compile("/api-docs.*|/autoconfig|/configprops|/dump|/info|/metrics.*|/mappings|/trace|/swagger.*|.*\\.png|.*\\.css|.*\\.js|.*\\.html"); + .compile("/api-docs.*|/autoconfig|/configprops|/dump|/info|/metrics.*|/mappings|/trace|/swagger.*|.*\\.png|.*\\.css|.*\\.js|.*\\.html|/favicon.ico"); private final Trace trace; private final Pattern skipPattern; + private UrlPathHelper urlPathHelper = new UrlPathHelper(); public TraceFilter(Trace trace) { this.trace = trace; @@ -65,26 +70,27 @@ public class TraceFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { + throws ServletException, IOException { String uri = hasText(request.getRequestURI()) ? request.getRequestURI() : ""; - boolean skip = skipPattern.matcher(uri).matches(); + boolean skip = this.skipPattern.matcher(uri).matches(); TraceScope traceScope = null; if (!skip) { String spanId = getHeader(request, response, SPAN_ID_NAME); String traceId = getHeader(request, response, TRACE_ID_NAME); + String name = this.urlPathHelper.getPathWithinApplication(request); if (hasText(spanId) && hasText(traceId)) { TraceInfo traceInfo = new TraceInfo(traceId, spanId); // TODO: trace description? - traceScope = trace.startSpan("traceFilter", traceInfo); + traceScope = this.trace.startSpan(name, traceInfo); // Send new span id back addToResponseIfNotPresent(response, SPAN_ID_NAME, traceScope.getSpan() .getSpanId()); } else { - traceScope = trace.startSpan("traceFilter"); + traceScope = this.trace.startSpan(name); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java index 7ffe04d2b..7a06bc4e3 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java @@ -67,12 +67,12 @@ public class TraceWebAspect { @Around("anyControllerOrRestControllerWithPublicAsyncMethod()") @SuppressWarnings("unchecked") public Object wrapWithCorrelationId(ProceedingJoinPoint pjp) throws Throwable { - Callable callable = (Callable) pjp.proceed(); + Callable callable = (Callable) pjp.proceed(); if (TraceContextHolder.isTracing()) { log.debug("Wrapping callable with span [" + TraceContextHolder.getCurrentSpan() + "]"); - return new TraceCallable(this.trace, callable); + return new TraceCallable(this.trace, callable); } else { return callable; diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java index 321c75dd2..2bf917497 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java @@ -27,8 +27,6 @@ import org.springframework.cloud.sleuth.Trace; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.util.StringUtils; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; /** * Registers beans that add tracing to requests @@ -55,41 +53,15 @@ public class TraceWebAutoConfiguration { @Bean @ConditionalOnMissingBean public TraceWebAspect traceWebAspect() { - return new TraceWebAspect(trace); + return new TraceWebAspect(this.trace); } - //TODO: I don't think TraceHandlerInterceptor is needed with TraceFilter - /*@Bean - @ConditionalOnMissingBean - public TraceHandlerInterceptor traceHandlerInterceptor() { - return new TraceHandlerInterceptor(trace); - } - - @Bean - public WebMvcConfigurerAdapter webMvcConfigurerAdapter( - TraceHandlerInterceptor handlerInterceptor) { - return new TraceWebConfigurer(handlerInterceptor); - } - - protected static class TraceWebConfigurer extends WebMvcConfigurerAdapter { - private TraceHandlerInterceptor interceptor; - - public TraceWebConfigurer(TraceHandlerInterceptor interceptor) { - this.interceptor = interceptor; - } - - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(interceptor).addPathPatterns("/**"); - } - }*/ - @Bean @ConditionalOnMissingBean public FilterRegistrationBean traceFilter() { - Pattern pattern = StringUtils.hasText(skipPattern) ? Pattern.compile(skipPattern) + Pattern pattern = StringUtils.hasText(this.skipPattern) ? Pattern.compile(this.skipPattern) : TraceFilter.DEFAULT_SKIP_PATTERN; - return new FilterRegistrationBean(new TraceFilter(trace, pattern)); + return new FilterRegistrationBean(new TraceFilter(this.trace, pattern)); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java index c45cabd06..98ab01ac8 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java @@ -1,7 +1,8 @@ package org.springframework.cloud.sleuth.instrument.web.client; +import javax.annotation.PostConstruct; + 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.ConditionalOnProperty; @@ -9,8 +10,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; -import javax.annotation.PostConstruct; - /** * @author Spencer Gibb */ @@ -42,8 +41,8 @@ public class TraceWebClientAutoConfiguration { @PostConstruct public void init() { - if (restTemplate != null) { - restTemplate.getInterceptors().add(traceRestTemplateInterceptor); + if (this.restTemplate != null) { + this.restTemplate.getInterceptors().add(this.traceRestTemplateInterceptor); } } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/slf4j/Slf4jSpanStartedListener.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/slf4j/Slf4jSpanStartedListener.java index 6653a4115..7708340e0 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/slf4j/Slf4jSpanStartedListener.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/slf4j/Slf4jSpanStartedListener.java @@ -17,9 +17,9 @@ public class Slf4jSpanStartedListener implements ApplicationListenerspring-cloud-sleuth-sample jar - Spring Cloud Consul Sample - Spring Cloud Consul Sample + Spring Cloud Sleuth Sample + Spring Cloud Sleuth Sample org.springframework.cloud @@ -21,7 +21,6 @@ org.springframework.boot spring-boot-maven-plugin - 1.2.1.RELEASE @@ -49,10 +48,18 @@ org.springframework.cloud spring-cloud-sleuth-core + + org.springframework.cloud + spring-cloud-sleuth-zipkin + org.springframework.boot spring-boot-starter-aop + + org.springframework.boot + spring-boot-starter-actuator + org.projectlombok lombok 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 8897123cf..9a0f08aec 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,7 +1,7 @@ package org.springframework.cloud.sleuth.sample; -import lombok.extern.slf4j.Slf4j; - +import com.github.kristofa.brave.LoggingSpanCollectorImpl; +import com.github.kristofa.brave.SpanCollector; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.cloud.sleuth.Sampler; @@ -18,13 +18,12 @@ import org.springframework.scheduling.annotation.EnableAsync; @EnableAutoConfiguration @EnableAspectJAutoProxy(proxyTargetClass = true) @EnableAsync -@Slf4j public class SampleApplication { public static final String CLIENT_NAME = "testApp"; @Bean - public Sampler defaultSampler() { + public Sampler defaultSampler() { return new AlwaysSampler(); } @@ -37,9 +36,6 @@ public class SampleApplication { SpringApplication.run(SampleApplication.class, args); } - /* - * @Bean public SpanCollector spanCollector() { return new LoggingSpanCollectorImpl(); - * } - */ + //@Bean public SpanCollector spanCollector() { return new LoggingSpanCollectorImpl(); } } diff --git a/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleController.java b/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleController.java index 242ce39e0..95b9ea9ac 100644 --- a/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleController.java +++ b/spring-cloud-sleuth-sample/src/main/java/org/springframework/cloud/sleuth/sample/SampleController.java @@ -1,7 +1,11 @@ package org.springframework.cloud.sleuth.sample; +import java.util.Random; +import java.util.concurrent.Callable; + import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; import org.springframework.cloud.sleuth.Span; @@ -14,16 +18,13 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; -import java.util.Random; -import java.util.concurrent.Callable; - /** * @author Spencer Gibb */ @Slf4j @RestController class SampleController implements - ApplicationListener { +ApplicationListener { @Autowired private RestTemplate restTemplate; @Autowired @@ -36,7 +37,7 @@ class SampleController implements final Random random = new Random(); Thread.sleep(random.nextInt(1000)); - String s = restTemplate.getForObject("http://localhost:" + port + "/hi2", + String s = this.restTemplate.getForObject("http://localhost:" + this.port + "/hi2", String.class); return "hi/" + s; } @@ -64,19 +65,19 @@ class SampleController implements @SneakyThrows @RequestMapping("/traced") public String traced() { - TraceScope scope = trace.startSpan("customTraceEndpoint", new AlwaysSampler()); + TraceScope scope = this.trace.startSpan("customTraceEndpoint", new AlwaysSampler()); final Random random = new Random(); int millis = random.nextInt(1000); log.info("Sleeping for {} millis", millis); Thread.sleep(millis); - String s = restTemplate.getForObject("http://localhost:" + port + "/hi2", String.class); + String s = this.restTemplate.getForObject("http://localhost:" + this.port + "/hi2", String.class); scope.close(); return "hi/" + s; } @Override public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) { - port = event.getEmbeddedServletContainer().getPort(); + this.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 9dafd9d4e..7996c7205 100644 --- a/spring-cloud-sleuth-sample/src/main/resources/application.yml +++ b/spring-cloud-sleuth-sample/src/main/resources/application.yml @@ -5,6 +5,10 @@ spring: application: name: testSleuthApp +logging: + pattern: + console: '%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} %clr([trace=%X{Trace-Id:-},span=%X{Span-Id:-}]){yellow} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex' + endpoints: health: sensitive: false diff --git a/spring-cloud-sleuth-sample/src/main/resources/logback.xml b/spring-cloud-sleuth-sample/src/main/resources/logback.xml deleted file mode 100644 index 1698a3b29..000000000 --- a/spring-cloud-sleuth-sample/src/main/resources/logback.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/spring-cloud-sleuth-zipkin/docker-compose.yml b/spring-cloud-sleuth-zipkin/docker-compose.yml new file mode 100644 index 000000000..e661e39e9 --- /dev/null +++ b/spring-cloud-sleuth-zipkin/docker-compose.yml @@ -0,0 +1,22 @@ +cassandra: + image: itszero/zipkin-cassandra +collector: + image: itszero/zipkin-collector + expose: + - 9410 + ports: + - 9410:9410 + links: + - cassandra:db +query: + image: itszero/zipkin-query + expose: + - 9411 + links: + - cassandra:db +web: + image: itszero/zipkin-web + links: + - query + ports: + - 8082:8080 diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index 262b5359d..abeb57887 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -46,6 +46,11 @@ spring-boot-starter-actuator true + + org.springframework.boot + spring-boot-configuration-processor + true + com.github.kristofa brave-client @@ -67,8 +72,9 @@ brave-zipkin-spancollector - org.springframework.cloud - spring-cloud-starter-zuul + com.netflix.zuul + zuul-core + true com.google.guava @@ -78,7 +84,7 @@ org.projectlombok lombok - provided + true org.springframework.boot diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/SleuthTracer.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/SleuthTracer.java new file mode 100644 index 000000000..9a9682255 --- /dev/null +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/SleuthTracer.java @@ -0,0 +1,206 @@ +package org.springframework.cloud.sleuth.zipkin; + +import java.io.UnsupportedEncodingException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import lombok.extern.apachecommons.CommonsLog; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.TimelineAnnotation; + +import com.github.kristofa.brave.SpanCollector; +import com.twitter.zipkin.gen.Annotation; +import com.twitter.zipkin.gen.AnnotationType; +import com.twitter.zipkin.gen.BinaryAnnotation; +import com.twitter.zipkin.gen.Endpoint; +import com.twitter.zipkin.gen.zipkinCoreConstants; +import org.springframework.cloud.sleuth.event.SpanStoppedEvent; +import org.springframework.context.event.EventListener; + +/** + * @author Spencer Gibb + */ +@CommonsLog +public class SleuthTracer { + + private SpanCollector spanCollector; + @Value("${spring.application.name:application}") + private String appName; + @Autowired + private ServerProperties serverProperties; + + public SleuthTracer(SpanCollector spanCollector) { + this.spanCollector = spanCollector; + } + + @EventListener + public void start(SpanStoppedEvent event) { + this.spanCollector.collect(convert(event.getSpan())); + } + + /** + * Converts a given HTrace span to a Zipkin Span. + *
    + *
  • First set the start annotation. [CS, SR], depending whether it is a client service or not. + *
  • Set other id's, etc [TraceId's etc] + *
  • Create binary annotations based on data from HTrace Span object. + *
  • Set the last annotation. [SS, CR] + *
+ */ + public com.twitter.zipkin.gen.Span convert(Span span) { + com.twitter.zipkin.gen.Span zipkinSpan = new com.twitter.zipkin.gen.Span(); + + String serviceName = getServiceName(span); + int address = getAddress(); + Integer port = getPort(); + + Endpoint ep = new Endpoint(address, port.shortValue(), serviceName); + List annotationList = createZipkinAnnotations(span, ep); + List binaryAnnotationList = createZipkinBinaryAnnotations(span, ep); + zipkinSpan.setTrace_id(hash(span.getTraceId())); + if (span.getParents().size() > 0) { + if (span.getParents().size() > 1) { + log.error("zipkin doesn't support spans with multiple parents. Omitting " + + "other parents for " + span); + } + zipkinSpan.setParent_id(hash(span.getParents().get(0))); + } + zipkinSpan.setId(hash(span.getSpanId())); + zipkinSpan.setName(span.getName()); + zipkinSpan.setAnnotations(annotationList); + zipkinSpan.setBinary_annotations(binaryAnnotationList); + return zipkinSpan; + } + + public Integer getPort() { + Integer port; + if (serverProperties.getPort() != null) { + port = serverProperties.getPort(); + } else { + port = 8080; //TODO: support random port + } + return port; + } + + public int getAddress() { + String address; + if (serverProperties.getAddress() != null) { + address = serverProperties.getAddress().getHostAddress(); + } else { + address = "127.0.0.1"; //TODO: get address from config + } + return ipAddressToInt(address); + } + + public String getServiceName(Span span) { + String serviceName; + if (span.getProcessId() != null) { + serviceName = span.getProcessId().toLowerCase(); + } else { + serviceName = appName; + } + return serviceName; + } + + + private int ipAddressToInt(final String ip) { + InetAddress inetAddress = null; + try { + inetAddress = InetAddress.getByName(ip); + } catch (final UnknownHostException e) { + throw new IllegalArgumentException(e); + } + return ByteBuffer.wrap(inetAddress.getAddress()).getInt(); + } + + /** + * Add annotations from the sleuth Span. + */ + private List createZipkinAnnotations(Span span, + Endpoint ep) { + List annotationList = new ArrayList<>(); + + int duration = (int)(span.getEnd() - span.getBegin()); + + // add first zipkin annotation. + annotationList.add(createZipkinAnnotation(zipkinCoreConstants.CLIENT_SEND, span.getBegin(), 0, ep, true)); + annotationList.add(createZipkinAnnotation(zipkinCoreConstants.SERVER_RECV, span.getBegin(), 0, ep, true)); + // add sleuth time annotation + for (TimelineAnnotation ta : span.getTimelineAnnotations()) { + annotationList.add(createZipkinAnnotation(ta.getMsg(), ta.getTime(), 0, ep, true)); + } + // add last zipkin annotation + annotationList.add(createZipkinAnnotation(zipkinCoreConstants.SERVER_SEND, span.getEnd(), duration, ep, false)); + annotationList.add(createZipkinAnnotation(zipkinCoreConstants.CLIENT_RECV, span.getEnd(), duration, ep, false)); + return annotationList; + } + + /** + * Creates a list of Annotations that are present in sleuth Span object. + * + * @return list of Annotations that could be added to Zipkin Span. + */ + private List createZipkinBinaryAnnotations(Span span, + Endpoint ep) { + List l = new ArrayList<>(); + for (Map.Entry e : span.getKVAnnotations().entrySet()) { + BinaryAnnotation binaryAnn = new BinaryAnnotation(); + binaryAnn.setAnnotation_type(AnnotationType.BYTES); + binaryAnn.setKey(e.getKey()); + try { + binaryAnn.setValue(e.getValue().getBytes("UTF-8")); + } catch (UnsupportedEncodingException ex) { + log.error("Error encoding string as UTF-8", ex); + } + binaryAnn.setHost(ep); + l.add(binaryAnn); + } + return l; + } + + /** + * Create an annotation with the correct times and endpoint. + * + * @param value Annotation value + * @param time timestamp will be extracted + * @param ep the endopint this annotation will be associated with. + * @param sendRequest use the first or last timestamp. + */ + private static Annotation createZipkinAnnotation(String value, long time, int duration, + Endpoint ep, boolean sendRequest) { + Annotation annotation = new Annotation(); + annotation.setHost(ep); + + // Zipkin is in microseconds + if (sendRequest) { + annotation.setTimestamp(time * 1000); + } else { + annotation.setTimestamp(time * 1000); + } + + if (duration > 0) { + annotation.setDuration(duration * 1000); + } + annotation.setValue(value); + return annotation; + } + + private static long hash(String string) { + long h = 1125899906842597L; + int len = string.length(); + + for (int i = 0; i < len; i++) { + h = 31 * h + string.charAt(i); + } + return h; + } + +} diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/TraceData.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/TraceData.java deleted file mode 100644 index 1511242d6..000000000 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/TraceData.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.springframework.cloud.sleuth.zipkin; - -import lombok.Data; - -/** -* @author Spencer Gibb -*/ -@Data -public 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/ZipkinAutoConfiguration.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinAutoConfiguration.java index c4a3e69f4..72a5c1131 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,11 +1,24 @@ package org.springframework.cloud.sleuth.zipkin; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +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.ServerSpanThreadBinderConfig; +import com.github.kristofa.brave.ServerTracer; import com.github.kristofa.brave.ServerTracerConfig; import com.github.kristofa.brave.SpanCollector; import com.github.kristofa.brave.TraceFilter; @@ -15,16 +28,6 @@ import com.github.kristofa.brave.client.ClientResponseInterceptor; import com.github.kristofa.brave.client.spanfilter.SpanNameFilter; import com.github.kristofa.brave.zipkin.ZipkinSpanCollector; import com.google.common.base.Optional; -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.autoconfigure.condition.ConditionalOnProperty; -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 java.util.List; /** * @author Spencer Gibb @@ -34,15 +37,15 @@ import java.util.List; @ConditionalOnClass(ServerTracerConfig.class) @ConditionalOnProperty(value = "spring.cloud.sleuth.zipkin.enabled", matchIfMissing = true) @Import({ AnnotationSubmitterConfig.class, ClientTracerConfig.class, - EndPointSubmitterConfig.class, ServerSpanThreadBinderConfig.class, - ServerTracerConfig.class }) + EndPointSubmitterConfig.class, ServerSpanThreadBinderConfig.class, + ServerTracerConfig.class }) public class ZipkinAutoConfiguration { @Bean @ConditionalOnMissingBean public SpanCollector spanCollector() { - return new ZipkinSpanCollector(zipkinProperties().getHost(), - zipkinProperties().getPort()); + return new ZipkinSpanCollector(zipkinProperties().getHost(), zipkinProperties() + .getPort()); } @Bean @@ -61,6 +64,18 @@ public class ZipkinAutoConfiguration { return new TraceFilters(traceFilters); } + @Bean + @ConditionalOnProperty(value = "spring.cloud.sleuth.zipkin.braveTracer.enabled", matchIfMissing = true) + public ZipkinSpanListener zipkinTrace(ServerTracer serverTracer) { + return new ZipkinSpanListener(serverTracer); + } + + @Bean + @ConditionalOnProperty(value = "spring.cloud.sleuth.zipkin.braveTracer.enabled", havingValue = "false") + public SleuthTracer sleuthTracer(SpanCollector spanCollector) { + return new SleuthTracer(spanCollector); + } + @Configuration protected static class InterceptorConfig { @@ -73,14 +88,14 @@ public class ZipkinAutoConfiguration { @Bean @ConditionalOnMissingBean public ClientRequestInterceptor clientRequestInterceptor() { - return new ClientRequestInterceptor(clientTracer, - Optional.fromNullable(spanNameFilter)); + return new ClientRequestInterceptor(this.clientTracer, + Optional.fromNullable(this.spanNameFilter)); } @Bean @ConditionalOnMissingBean public ClientResponseInterceptor clientResponseInterceptor() { - return new ClientResponseInterceptor(clientTracer); + return new ClientResponseInterceptor(this.clientTracer); } } } diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinInterceptor.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinInterceptor.java deleted file mode 100644 index d184cb5f3..000000000 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinInterceptor.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.springframework.cloud.sleuth.zipkin; - -import com.github.kristofa.brave.EndPointSubmitter; -import com.github.kristofa.brave.ServerTracer; -import lombok.extern.apachecommons.CommonsLog; - -/** - * @author Spencer Gibb - */ -@CommonsLog -public abstract class ZipkinInterceptor { - - private final ServerTracer serverTracer; - private final EndPointSubmitter endPointSubmitter; - - protected ZipkinInterceptor(ServerTracer serverTracer, EndPointSubmitter endPointSubmitter) { - this.serverTracer = serverTracer; - this.endPointSubmitter = endPointSubmitter; - } - - public void preTrace(T context) { - submitEndpoint(context, endPointSubmitter); - - final TraceData traceData = getTraceData(context); - serverTracer.clearCurrentSpan(); - - if (Boolean.FALSE.equals(traceData.getShouldBeSampled())) { - serverTracer.setStateNoTracing(); - log.debug("Received indication that we should NOT trace."); - } else { - final String spanName = getSpanName(context, 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(); - } - } - - protected abstract void submitEndpoint(T context, EndPointSubmitter endPointSubmitter); - protected abstract TraceData getTraceData(T context); - protected abstract String getSpanName(T context, TraceData traceData); - - public void postTrace(T context) { - // 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(); - } - } - - protected EndPointSubmitter getEndPointSubmitter() { - return endPointSubmitter; - } - - protected ServerTracer getServerTracer() { - return serverTracer; - } -} 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 deleted file mode 100644 index bb6128ddf..000000000 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinRestTemplateInterceptor.java +++ /dev/null @@ -1,100 +0,0 @@ -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 { - - RequestAdapter requestAdapter = new RequestAdapter(request); - clientRequestInterceptor.handle(requestAdapter, 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/ZipkinSpanListener.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java new file mode 100644 index 000000000..18a0702ed --- /dev/null +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/ZipkinSpanListener.java @@ -0,0 +1,97 @@ +package org.springframework.cloud.sleuth.zipkin; + +import lombok.Data; +import lombok.extern.apachecommons.CommonsLog; + +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.event.SpanStartedEvent; +import org.springframework.cloud.sleuth.event.SpanStoppedEvent; +import org.springframework.context.event.EventListener; + +import com.github.kristofa.brave.ServerTracer; + +/** + * @author Spencer Gibb + */ +@CommonsLog +public class ZipkinSpanListener { + + private final ServerTracer serverTracer; + + public ZipkinSpanListener(ServerTracer serverTracer) { + this.serverTracer = serverTracer; + } + + @EventListener + public void start(SpanStartedEvent event) { + preTrace(event.getSpan()); + } + + @EventListener + public void start(SpanStoppedEvent event) { + postTrace(event.getSpan()); + } + + protected void preTrace(Span context) { + final TraceData traceData = getTraceData(context); + + final String spanName = getSpanName(context, traceData); + if (traceData.getTraceId() != null && traceData.getSpanId() != null) { + + log.debug("Received span information as part of request."); + this.serverTracer.setStateCurrentTrace(traceData.getTraceId(), + traceData.getSpanId(), traceData.getParentSpanId(), spanName); + } + else { + log.debug("Received no span state."); + this.serverTracer.setStateUnknown(spanName); + } + this.serverTracer.setServerReceived(); + } + + protected TraceData getTraceData(Span context) { + TraceData trace = new TraceData(); + trace.setTraceId(hash(context.getTraceId())); + trace.setSpanId(hash(context.getSpanId())); + trace.setSpanName(context.getName()); + if (!context.getParents().isEmpty()) { + trace.setParentSpanId(hash(context.getParents().iterator().next())); + } + return trace; + } + + protected String getSpanName(Span context, TraceData traceData) { + return context.getName(); + } + + protected void postTrace(Span context) { + // 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 { + this.serverTracer.setServerSend(); + } + finally { + this.serverTracer.clearCurrentSpan(); + } + } + + @Data + private static class TraceData { + private Long traceId; + private Long spanId; + private Long parentSpanId; + private String spanName; + } + + private static long hash(String string) { + long h = 1125899906842597L; + int len = string.length(); + + for (int i = 0; i < len; i++) { + h = 31 * h + string.charAt(i); + } + return h; + } + +} diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/web/HttpServletRequestInterceptor.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/web/HttpServletRequestInterceptor.java deleted file mode 100644 index 28f07cfe8..000000000 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/web/HttpServletRequestInterceptor.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.springframework.cloud.sleuth.zipkin.web; - -import static com.github.kristofa.brave.BraveHttpHeaders.ParentSpanId; -import static com.github.kristofa.brave.BraveHttpHeaders.Sampled; -import static com.github.kristofa.brave.BraveHttpHeaders.SpanId; -import static com.github.kristofa.brave.BraveHttpHeaders.SpanName; -import static com.github.kristofa.brave.BraveHttpHeaders.TraceId; - -import java.util.List; -import java.util.Map; - -import javax.servlet.http.HttpServletRequest; - -import com.github.kristofa.brave.EndPointSubmitter; -import com.github.kristofa.brave.ServerTracer; -import lombok.extern.apachecommons.CommonsLog; - -import org.apache.commons.lang3.StringUtils; -import org.springframework.cloud.sleuth.zipkin.TraceData; -import org.springframework.cloud.sleuth.zipkin.ZipkinInterceptor; -import org.springframework.http.HttpHeaders; -import org.springframework.http.server.ServletServerHttpRequest; - -import com.github.kristofa.brave.IdConversion; -import org.springframework.web.util.UriComponents; -import org.springframework.web.util.UriComponentsBuilder; - -/** - * @author Spencer Gibb - */ -@CommonsLog -public class HttpServletRequestInterceptor extends ZipkinInterceptor { - - public HttpServletRequestInterceptor(ServerTracer serverTracer, EndPointSubmitter endPointSubmitter) { - super(serverTracer, endPointSubmitter); - } - - @Override - public void submitEndpoint(HttpServletRequest servletRequest, EndPointSubmitter endPointSubmitter) { - 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); - } - } - - @Override - public TraceData getTraceData(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; - } - - protected Long getFirstLong(final Map.Entry> headerEntry) { - final String value = getFirstString(headerEntry); - if (value == null) { - return null; - } - return IdConversion.convertToLong(value); - - } - - protected Boolean getFirstBoolean(final Map.Entry> headerEntry) { - final String firstStringValueFor = getFirstString(headerEntry); - return firstStringValueFor == null ? null : Boolean.valueOf(firstStringValueFor); - } - - protected String getFirstString(final Map.Entry> headerEntry) { - final List values = headerEntry.getValue(); - if (values != null && values.size() > 0) { - return headerEntry.getValue().get(0); - } - return null; - } - - @Override - protected String getSpanName(HttpServletRequest request, 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(); - } - } -} diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/web/ZipkinFilter.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/web/ZipkinFilter.java index f8de9d915..037332200 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/web/ZipkinFilter.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/web/ZipkinFilter.java @@ -1,6 +1,6 @@ package org.springframework.cloud.sleuth.zipkin.web; -import org.springframework.cloud.sleuth.zipkin.ZipkinInterceptor; +import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -8,36 +8,54 @@ import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; -import java.io.IOException; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; + +import com.github.kristofa.brave.EndPointSubmitter; /** * @author Spencer Gibb */ +@Order(Ordered.HIGHEST_PRECEDENCE + 2) public class ZipkinFilter implements Filter { - private final ZipkinInterceptor zipkinInterceptor; + @Value("${spring.application.name:application}") + private String serviceName; - public ZipkinFilter(ZipkinInterceptor zipkinInterceptor) { - this.zipkinInterceptor = zipkinInterceptor; - } + private EndPointSubmitter endPointSubmitter; + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } - @Override - public void init(FilterConfig filterConfig) throws ServletException { - //NOOP - } + public ZipkinFilter(EndPointSubmitter endPointSubmitter) { + this.endPointSubmitter = endPointSubmitter; + } - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - zipkinInterceptor.preTrace(request); + @Override + public void init(FilterConfig filterConfig) throws ServletException { + // NOOP + } - chain.doFilter(request, response); + @Override + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, ServletException { + if (!this.endPointSubmitter.endPointSubmitted()) { + final String localAddr = request.getLocalAddr(); + final int localPort = request.getLocalPort(); + final String contextPath = this.serviceName + + ((request instanceof HttpServletRequest) ? ((HttpServletRequest) request) + .getContextPath() : ""); + this.endPointSubmitter.submit(localAddr, localPort, contextPath); + } + chain.doFilter(request, response); + } - zipkinInterceptor.postTrace(request); - } - - @Override - public void destroy() { - //NOOP - } + @Override + public void destroy() { + // NOOP + } } diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/web/ZipkinHandlerInterceptor.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/web/ZipkinHandlerInterceptor.java deleted file mode 100644 index fc62196a4..000000000 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/web/ZipkinHandlerInterceptor.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.springframework.cloud.sleuth.zipkin.web; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import lombok.extern.apachecommons.CommonsLog; - -import org.springframework.cloud.sleuth.zipkin.ZipkinInterceptor; -import org.springframework.web.servlet.HandlerInterceptor; -import org.springframework.web.servlet.ModelAndView; - -/** - * @author Spencer Gibb - */ -@CommonsLog -public class ZipkinHandlerInterceptor implements HandlerInterceptor { - - private final ZipkinInterceptor zipkinInterceptor; - - public ZipkinHandlerInterceptor(ZipkinInterceptor zipkinInterceptor) { - this.zipkinInterceptor = zipkinInterceptor; - } - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - zipkinInterceptor.preTrace(request); - return true; - } - - @Override - public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { - zipkinInterceptor.postTrace(request); - } - - @Override - public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { - } - -} 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 index ba22a6999..0f1f69ccd 100644 --- 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 @@ -1,22 +1,18 @@ package org.springframework.cloud.sleuth.zipkin.web; -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; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; import org.springframework.cloud.sleuth.zipkin.ZipkinAutoConfiguration; -import org.springframework.cloud.sleuth.zipkin.ZipkinRestTemplateInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -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.ServerTracerConfig; /** * @author Spencer Gibb @@ -26,62 +22,15 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter @ConditionalOnWebApplication @ConditionalOnProperty(value = "spring.cloud.sleuth.zipkin.enabled", matchIfMissing = true) @AutoConfigureAfter(ZipkinAutoConfiguration.class) +@AutoConfigureBefore(TraceAutoConfiguration.class) public class ZipkinWebAutoConfiguration { @Autowired private EndPointSubmitter endPointSubmitter; - @Autowired - private ServerTracer serverTracer; - @Bean - public ZipkinHandlerInterceptor zipkinHandlerInterceptor() { - return new ZipkinHandlerInterceptor(httpServletRequestInterceptor()); + public ZipkinFilter zipkinFilter() { + return new ZipkinFilter(this.endPointSubmitter); } - @Bean - public ZipkinFilter zipkinFilter() { - return new ZipkinFilter(httpServletRequestInterceptor()); - } - - @Bean - public HttpServletRequestInterceptor httpServletRequestInterceptor() { - return new HttpServletRequestInterceptor(serverTracer, endPointSubmitter); - } - - @Bean - @ConditionalOnBean(ZipkinHandlerInterceptor.class) - public WebMvcConfigurerAdapter webMvcConfigurerAdapter(ZipkinHandlerInterceptor zipkinHandlerInterceptor) { - return new ZipkinWebConfigurer(zipkinHandlerInterceptor); - } - - @Configuration - protected static class RestTemplateConfig { - - @Autowired - private ClientRequestInterceptor clientRequestInterceptor; - - @Autowired - private ClientResponseInterceptor clientResponseInterceptor; - - @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/java/org/springframework/cloud/sleuth/zipkin/zuul/ZipkinPostFilter.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/zuul/ZipkinPostFilter.java deleted file mode 100644 index ced38c08a..000000000 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/zuul/ZipkinPostFilter.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.springframework.cloud.sleuth.zipkin.zuul; - -import com.netflix.zuul.ZuulFilter; -import com.netflix.zuul.context.RequestContext; -import lombok.extern.apachecommons.CommonsLog; -import org.springframework.cloud.sleuth.zipkin.ZipkinInterceptor; - -import javax.servlet.http.HttpServletRequest; - -/** - * @author Spencer Gibb - */ -@CommonsLog -public class ZipkinPostFilter extends ZuulFilter { - - private ZipkinInterceptor zipkinInterceptor; - - public ZipkinPostFilter(ZipkinInterceptor zipkinInterceptor) { - this.zipkinInterceptor = zipkinInterceptor; - } - - @Override - public String filterType() { - return "post"; - } - - @Override - public int filterOrder() { - return 0; - } - - @Override - public boolean shouldFilter() { - return true; - } - - @Override - public Object run() { - HttpServletRequest request = RequestContext.getCurrentContext().getRequest(); - - zipkinInterceptor.postTrace(request); - - return null; - } -} diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/zuul/ZipkinPreFilter.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/zuul/ZipkinPreFilter.java deleted file mode 100644 index 6d9e1857a..000000000 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/zuul/ZipkinPreFilter.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.springframework.cloud.sleuth.zipkin.zuul; - -import javax.servlet.http.HttpServletRequest; - -import lombok.extern.apachecommons.CommonsLog; - -import org.springframework.cloud.sleuth.zipkin.ZipkinInterceptor; - -import com.netflix.zuul.ZuulFilter; -import com.netflix.zuul.context.RequestContext; - -/** - * @author Spencer Gibb - */ -@CommonsLog -public class ZipkinPreFilter extends ZuulFilter { - - private ZipkinInterceptor zipkinInterceptor; - - public ZipkinPreFilter(ZipkinInterceptor zipkinInterceptor) { - this.zipkinInterceptor = zipkinInterceptor; - } - - @Override - public String filterType() { - return "pre"; - } - - @Override - public int filterOrder() { - return 0; - } - - @Override - public boolean shouldFilter() { - return true; - } - - @Override - public Object run() { - HttpServletRequest request = RequestContext.getCurrentContext().getRequest(); - - zipkinInterceptor.preTrace(request); - - return null; - } -} diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/zuul/ZipkinZuulAutoConfiguration.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/zuul/ZipkinZuulAutoConfiguration.java deleted file mode 100644 index 1ab83ebe5..000000000 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin/zuul/ZipkinZuulAutoConfiguration.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.springframework.cloud.sleuth.zipkin.zuul; - -import com.netflix.zuul.ZuulFilter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.cloud.sleuth.zipkin.ZipkinInterceptor; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * @author Spencer Gibb - */ -@Configuration -@ConditionalOnClass(ZuulFilter.class) -@ConditionalOnBean(ZipkinInterceptor.class) -public class ZipkinZuulAutoConfiguration { - - @Bean - public ZipkinPreFilter zipkinPreFilter(ZipkinInterceptor zipkinInterceptor) { - return new ZipkinPreFilter(zipkinInterceptor); - } - - @Bean - public ZipkinPostFilter zipkinPostFilter(ZipkinInterceptor zipkinInterceptor) { - return new ZipkinPostFilter(zipkinInterceptor); - } -}