Integrate server-side zipkin with core

This commit is contained in:
Dave Syer
2015-07-28 17:11:18 +01:00
parent 19dfe2b0d6
commit cba63d0dec
32 changed files with 297 additions and 654 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 the docker images at [Zipkin Docker](https://github.com/openzipkin/zipkin-docker)
7. Run sample application
8. Hit `http://localhost:3380`
9. Goto `http://localhost:8080` 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,12 +1,16 @@
package org.springframework.cloud.sleuth;
import java.io.Closeable;
import java.util.ArrayList;
import java.util.List;
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 +18,7 @@ import org.springframework.context.ApplicationEventPublisher;
*/
@Value
@NonFinal
@CommonsLog
public class TraceScope implements Closeable {
private final ApplicationEventPublisher publisher;
@@ -28,6 +33,11 @@ public class TraceScope implements Closeable {
*/
private final Span savedSpan;
/**
* List of callbacks to run on close.
*/
private List<Runnable> callbacks = new ArrayList<Runnable>();
@NonFinal
private boolean detached = false;
@@ -45,41 +55,54 @@ 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 this.span;
}
public void register(Runnable callback) {
if (!this.callbacks .contains(callback)) {
this.callbacks.add(callback);
}
return span;
}
@Override
@SneakyThrows
public void close() {
if (detached) {
if (this.detached) {
return;
}
detached = true;
this.detached = true;
for (Runnable callback : this.callbacks) {
try {
callback.run();
} catch (Throwable e) {
log.error("Error with callback on close", e);
}
}
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,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,6 +30,8 @@ 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;
/**
@@ -44,6 +46,7 @@ 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
@@ -65,10 +68,10 @@ 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) {
@@ -78,13 +81,13 @@ public class TraceFilter extends OncePerRequestFilter {
TraceInfo traceInfo = new TraceInfo(traceId, spanId);
// TODO: trace description?
traceScope = trace.startSpan("traceFilter", traceInfo);
traceScope = this.trace.startSpan("traceFilter", traceInfo);
// Send new span id back
addToResponseIfNotPresent(response, SPAN_ID_NAME, traceScope.getSpan()
.getSpanId());
}
else {
traceScope = trace.startSpan("traceFilter");
traceScope = this.trace.startSpan("traceFilter");
}
}

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,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,6 +48,10 @@
<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>

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

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

@@ -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,26 @@
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.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.sleuth.IdGenerator;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.context.ApplicationEventPublisher;
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 +30,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 +39,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 +66,11 @@ public class ZipkinAutoConfiguration {
return new TraceFilters(traceFilters);
}
@Bean
public ZipkinTrace zipkinTrace(ServerTracer serverTracer, Sampler<?> sampler, IdGenerator idGenerator, ApplicationEventPublisher publisher) {
return new ZipkinTrace(serverTracer, sampler, idGenerator, publisher);
}
@Configuration
protected static class InterceptorConfig {
@@ -73,14 +83,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,116 @@
package org.springframework.cloud.sleuth.zipkin;
import lombok.Data;
import lombok.extern.apachecommons.CommonsLog;
import org.springframework.cloud.sleuth.IdGenerator;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.TraceScope;
import org.springframework.cloud.sleuth.trace.DefaultTrace;
import org.springframework.context.ApplicationEventPublisher;
import com.github.kristofa.brave.ServerTracer;
/**
* @author Spencer Gibb
*/
@CommonsLog
public class ZipkinTrace extends DefaultTrace {
private final ServerTracer serverTracer;
@Override
protected TraceScope doStart(final Span span) {
preTrace(span);
TraceScope scope = super.doStart(span);
scope.register(new Runnable() {
@Override
public void run() {
postTrace(span);
}
});
return scope;
}
public ZipkinTrace(ServerTracer serverTracer, Sampler<?> defaultSampler,
IdGenerator idGenerator, ApplicationEventPublisher publisher) {
super(defaultSampler, idGenerator, publisher);
this.serverTracer = serverTracer;
}
public void preTrace(Span context) {
final TraceData traceData = getTraceData(context);
this.serverTracer.clearCurrentSpan();
if (Boolean.FALSE.equals(traceData.getShouldBeSampled())) {
this.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.");
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.setShouldBeSampled(true);
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();
}
public 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();
}
}
protected ServerTracer getServerTracer() {
return this.serverTracer;
}
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;
}
@Data
private static class TraceData {
private Long traceId;
private Long spanId;
private Long parentSpanId;
private Boolean shouldBeSampled;
private String spanName;
}
}

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