Merge commits from dsyer/refactor/zipkin

* refactor/zipkin:
  add SleuthTracer that uses lower level brave classes
  Do not call serverTracer.clearCurrentSpan() at the start of a span
  Add request path to span name
  Use listeners instead of custom callbacks
  Integrate server-side zipkin with core
  Use generics
This commit is contained in:
Spencer Gibb
2015-07-28 14:42:50 -06:00
41 changed files with 530 additions and 680 deletions

View File

@@ -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

View File

@@ -1,5 +1,3 @@
TODO: intro Spring Cloud Sleuth
=== Terminology
Spring Cloud Sleuth borrows http://research.google.com/pubs/pub36356.html[Dapper's] terminology.

View File

@@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId>
<version>1.0.2.BUILD-SNAPSHOT</version>
<version>1.1.0.BUILD-SNAPSHOT</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>

View File

@@ -66,7 +66,7 @@
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!-- Only needed at compile time -->
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@@ -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<String> parents;
private String spanId;
private Map<String, String> kVAnnotations;
private Map<String, String> kVAnnotations = new LinkedHashMap<>();
private String processId;
@Singular
private List<TimelineAnnotation> timelineAnnotations;
private List<TimelineAnnotation> timelineAnnotations = new ArrayList<>();
@Override
public synchronized void stop() {

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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;
/**

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<Runnable> implements Runnable {
public TraceRunnable(Trace trace, Runnable delagate) {

View File

@@ -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);
}
}

View File

@@ -67,12 +67,12 @@ public class TraceWebAspect {
@Around("anyControllerOrRestControllerWithPublicAsyncMethod()")
@SuppressWarnings("unchecked")
public Object wrapWithCorrelationId(ProceedingJoinPoint pjp) throws Throwable {
Callable callable = (Callable) pjp.proceed();
Callable<Object> callable = (Callable<Object>) pjp.proceed();
if (TraceContextHolder.isTracing()) {
log.debug("Wrapping callable with span ["
+ TraceContextHolder.getCurrentSpan() + "]");
return new TraceCallable(this.trace, callable);
return new TraceCallable<Object>(this.trace, callable);
}
else {
return callable;

View File

@@ -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));
}
}

View File

@@ -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);
}
}
}

View File

@@ -17,9 +17,9 @@ public class Slf4jSpanStartedListener implements ApplicationListener<SpanStarted
@Override
public void onApplicationEvent(SpanStartedEvent event) {
Span span = event.getSpan();
//TODO: what log level?
log.info("Starting span: {}", span);
MDC.put(Trace.SPAN_ID_NAME, span.getSpanId());
MDC.put(Trace.TRACE_ID_NAME, span.getTraceId());
//TODO: what log level?
log.info("Starting span: {}", span);
}
}

View File

@@ -1,10 +1,18 @@
package org.springframework.cloud.sleuth;
package org.springframework.cloud.sleuth.trace;
import static org.springframework.cloud.sleuth.Utils.error;
import static org.springframework.cloud.sleuth.util.ExceptionUtils.error;
import java.util.Collections;
import java.util.concurrent.Callable;
import org.springframework.cloud.sleuth.IdGenerator;
import org.springframework.cloud.sleuth.MilliSpan;
import org.springframework.cloud.sleuth.NullScope;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Trace;
import org.springframework.cloud.sleuth.TraceContextHolder;
import org.springframework.cloud.sleuth.TraceInfo;
import org.springframework.cloud.sleuth.TraceScope;
import org.springframework.cloud.sleuth.event.SpanStartedEvent;
import org.springframework.cloud.sleuth.instrument.TraceCallable;
import org.springframework.cloud.sleuth.instrument.TraceRunnable;
@@ -30,7 +38,7 @@ public class DefaultTrace implements Trace {
@Override
public TraceScope startSpan(String name) {
return this.startSpan(name, defaultSampler);
return this.startSpan(name, this.defaultSampler);
}
@Override
@@ -40,7 +48,7 @@ public class DefaultTrace implements Trace {
.begin(System.currentTimeMillis())
.name(name)
.traceId(tinfo.getTraceId())
.spanId(idGenerator.create())
.spanId(this.idGenerator.create())
.parent(tinfo.getSpanId())
.build();
return doStart(span);
@@ -81,8 +89,8 @@ public class DefaultTrace implements Trace {
return MilliSpan.builder()
.begin(System.currentTimeMillis())
.name(name)
.traceId(idGenerator.create())
.spanId(idGenerator.create())
.traceId(this.idGenerator.create())
.spanId(this.idGenerator.create())
.build();
} else {
return createChild(parent, name);
@@ -95,14 +103,14 @@ public class DefaultTrace implements Trace {
.name(childname)
.traceId(parent.getTraceId())
.parent(parent.getSpanId())
.spanId(idGenerator.create())
.spanId(this.idGenerator.create())
.processId(parent.getProcessId())
.build();
}
protected TraceScope doStart(Span span) {
if (span != null) {
publisher.publishEvent(new SpanStartedEvent(this, span));
this.publisher.publishEvent(new SpanStartedEvent(this, span));
}
return continueSpan(span);
}

View File

@@ -1,4 +1,4 @@
package org.springframework.cloud.sleuth;
package org.springframework.cloud.sleuth.util;
import lombok.extern.apachecommons.CommonsLog;
@@ -6,7 +6,7 @@ import lombok.extern.apachecommons.CommonsLog;
* @author Spencer Gibb
*/
@CommonsLog
public abstract class Utils {
public abstract class ExceptionUtils {
public static void error(String msg) {
log.error(msg);
throw new RuntimeException(msg);

View File

@@ -1,6 +1,6 @@
# Auto Configuration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.sleuth.TraceAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration,\
org.springframework.cloud.sleuth.slf4j.SleuthSlf4jAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.web.TraceWebAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.web.client.TraceWebClientAutoConfiguration

View File

@@ -17,6 +17,7 @@ import org.springframework.cloud.sleuth.event.SpanStartedEvent;
import org.springframework.cloud.sleuth.event.SpanStoppedEvent;
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
import org.springframework.cloud.sleuth.sampler.IsTracingSampler;
import org.springframework.cloud.sleuth.trace.DefaultTrace;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;

View File

@@ -6,8 +6,8 @@
<artifactId>spring-cloud-sleuth-sample</artifactId>
<packaging>jar</packaging>
<name>Spring Cloud Consul Sample</name>
<description>Spring Cloud Consul Sample</description>
<name>Spring Cloud Sleuth Sample</name>
<description>Spring Cloud Sleuth Sample</description>
<parent>
<groupId>org.springframework.cloud</groupId>
@@ -21,7 +21,6 @@
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.2.1.RELEASE</version>
<executions>
<execution>
<goals>
@@ -49,10 +48,18 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>

View File

@@ -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(); }
}

View File

@@ -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<EmbeddedServletContainerInitializedEvent> {
ApplicationListener<EmbeddedServletContainerInitializedEvent> {
@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();
}
}

View File

@@ -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

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/}spring.log}"/>
<property name="CONSOLE_LOG_PATTERN" value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%X{Trace-Id:-} %X{Span-Id:-}]){yellow} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex"/>
<property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${PID:- } --- [%X{Trace-Id:-} %X{Span-Id:-}] [%t] %-40.40logger{39} : %m%n%wex"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
<include resource="org/springframework/boot/logging/logback/file-appender.xml" />
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
<logger name="org.springframework.web" level="DEBUG"/>
</configuration>

View File

@@ -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

View File

@@ -46,6 +46,11 @@
<artifactId>spring-boot-starter-actuator</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.github.kristofa</groupId>
<artifactId>brave-client</artifactId>
@@ -67,8 +72,9 @@
<artifactId>brave-zipkin-spancollector</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
<groupId>com.netflix.zuul</groupId>
<artifactId>zuul-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
@@ -78,7 +84,7 @@
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!-- Only needed at compile time -->
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@@ -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.
* <ul>
* <li>First set the start annotation. [CS, SR], depending whether it is a client service or not.
* <li>Set other id's, etc [TraceId's etc]
* <li>Create binary annotations based on data from HTrace Span object.
* <li>Set the last annotation. [SS, CR]
* </ul>
*/
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<Annotation> annotationList = createZipkinAnnotations(span, ep);
List<BinaryAnnotation> 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<Annotation> createZipkinAnnotations(Span span,
Endpoint ep) {
List<Annotation> 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<BinaryAnnotation> createZipkinBinaryAnnotations(Span span,
Endpoint ep) {
List<BinaryAnnotation> l = new ArrayList<>();
for (Map.Entry<String, String> 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;
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}
}

View File

@@ -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<T> {
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;
}
}

View File

@@ -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.<String>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<String> 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();
}
}
}

View File

@@ -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;
}
}

View File

@@ -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<HttpServletRequest> {
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<String, List<String>> 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<String, List<String>> headerEntry) {
final String value = getFirstString(headerEntry);
if (value == null) {
return null;
}
return IdConversion.convertToLong(value);
}
protected Boolean getFirstBoolean(final Map.Entry<String, List<String>> headerEntry) {
final String firstStringValueFor = getFirstString(headerEntry);
return firstStringValueFor == null ? null : Boolean.valueOf(firstStringValueFor);
}
protected String getFirstString(final Map.Entry<String, List<String>> headerEntry) {
final List<String> 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();
}
}
}

View File

@@ -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
}
}

View File

@@ -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 {
}
}

View File

@@ -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("/**");
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}