Merge pull request #7 from dsyer/refactor/collector

Use brave collector and add needed parents/ids
This commit is contained in:
Spencer Gibb
2015-07-29 11:53:12 -06:00
32 changed files with 600 additions and 686 deletions

View File

@@ -20,50 +20,50 @@ public class MilliSpan implements Span {
@NonFinal
private long end = 0;
private String name;
private String traceId;
private final String traceId;
@Singular
private List<String> parents;
private String spanId;
private final String spanId;
private Map<String, String> kVAnnotations = new LinkedHashMap<>();
private String processId;
private final String processId;
@Singular
private List<TimelineAnnotation> timelineAnnotations = new ArrayList<>();
@Override
public synchronized void stop() {
if (end == 0) {
if (begin == 0) {
throw new IllegalStateException("Span for " + name
if (this.end == 0) {
if (this.begin == 0) {
throw new IllegalStateException("Span for " + this.name
+ " has not been started");
}
end = System.currentTimeMillis();
this.end = System.currentTimeMillis();
}
}
@Override
public synchronized long getAccumulatedMillis() {
if (begin == 0) {
if (this.begin == 0) {
return 0;
}
if (end > 0) {
return end - begin;
if (this.end > 0) {
return this.end - this.begin;
}
return System.currentTimeMillis() - begin;
return System.currentTimeMillis() - this.begin;
}
@Override
public synchronized boolean isRunning() {
return begin != 0 && end == 0;
return this.begin != 0 && this.end == 0;
}
@Override
public void addKVAnnotation(String key, String value) {
kVAnnotations.put(key, value);
this.kVAnnotations.put(key, value);
}
@Override
public void addTimelineAnnotation(String msg) {
timelineAnnotations.add(new TimelineAnnotation(System.currentTimeMillis(), msg));
this.timelineAnnotations.add(new TimelineAnnotation(System.currentTimeMillis(), msg));
}
}

View File

@@ -12,6 +12,40 @@ import java.util.Map;
* parents.<p/>
*/
public interface Span {
/**
* A human-readable name assigned to this span instance.<p/>
*/
String getName();
/**
* A pseudo-unique (random) number assigned to this span instance.<p/>
* <p/>
* The spanId is immutable and cannot be changed. It is safe to access this
* from multiple threads.
*/
String getSpanId();
/**
* A pseudo-unique (random) number assigned to the trace associated with this
* span
*/
String getTraceId();
/**
* Return a unique id for the process from which this Span originated.<p/>
* <p/>
* Will never be null.
*/
String getProcessId();
/**
* Returns the parent IDs of the span.<p/>
* <p/>
* The collection will be empty if there are no parents.
*/
List<String> getParents();
/**
* The block has completed, stop the clock
*/
@@ -38,34 +72,6 @@ public interface Span {
*/
boolean isRunning();
/**
* Return a textual name of this span.<p/>
* <p/>
* Will never be null.
*/
String getName();
/**
* A pseudo-unique (random) number assigned to this span instance.<p/>
* <p/>
* The spanId is immutable and cannot be changed. It is safe to access this
* from multiple threads.
*/
String getSpanId();
/**
* A pseudo-unique (random) number assigned to the trace associated with this
* span
*/
String getTraceId();
/**
* Returns the parent IDs of the span.<p/>
* <p/>
* The collection will be empty if there are no parents.
*/
List<String> getParents();
/**
* Add a data annotation associated with this span
*/
@@ -89,11 +95,4 @@ public interface Span {
* Will never be null.
*/
List<TimelineAnnotation> getTimelineAnnotations();
/**
* Return a unique id for the process from which this Span originated.<p/>
* <p/>
* Will never be null.
*/
String getProcessId();
}

View File

@@ -3,71 +3,76 @@ package org.springframework.cloud.sleuth;
import java.util.concurrent.Callable;
/**
* The Trace class is the primary way to interact with the library. It provides
* methods to create and manipulate spans.
* The Trace class is the primary way to interact with the library. It provides methods to
* create and manipulate spans.
*
* A 'Span' represents a length of time. It has many other attributes such as a
* name, ID, and even potentially a set of key/value strings attached to
* it.
* A 'Span' represents a length of time. It has many other attributes such as a name, ID,
* and even potentially a set of key/value strings attached to it.
*
* Each thread in your application has a single currently active currentSpan
* associated with it. When this is non-null, it represents the current
* operation that the thread is doing. Spans are NOT thread-safe, and must
* never be used by multiple threads at once. With care, it is possible to
* safely pass a Span object between threads, but in most cases this is not
* necessary.
* Each thread in your application has a single currently active currentSpan associated
* with it. When this is non-null, it represents the current operation that the thread is
* doing. Spans are NOT thread-safe, and must never be used by multiple threads at once.
* With care, it is possible to safely pass a Span object between threads, but in most
* cases this is not necessary.
*
* A 'TraceScope' can either be empty, or contain a Span. TraceScope objects
* implement the Java's Closeable interface. Similar to file descriptors, they
* must be closed after they are created. When a TraceScope contains a Span,
* this span is closed when the scope is closed.
* A 'TraceScope' can either be empty, or contain a Span. TraceScope objects implement the
* Java's Closeable interface. Similar to file descriptors, they must be closed after they
* are created. When a TraceScope contains a Span, this span is closed when the scope is
* closed.
*
* The 'startSpan' methods in this class do a few things:
* <ul>
* <li>Create a new Span which has this thread's currentSpan as one of its parents.</li>
* <li>Set currentSpan to the new Span.</li>
* <li>Create a TraceSpan object to manage the new Span.</li>
* <li>Create a new Span which has this thread's currentSpan as one of its parents.</li>
* <li>Set currentSpan to the new Span.</li>
* <li>Create a TraceSpan object to manage the new Span.</li>
* </ul>
*
* Closing a TraceScope does a few things:
* <ul>
* <li>It closes the span which the scope was managing.</li>
* <li>Set currentSpan to the previous currentSpan (which may be null).</li>
* <li>It closes the span which the scope was managing.</li>
* <li>Set currentSpan to the previous currentSpan (which may be null).</li>
* </ul>
*/
public interface Trace {
String SPAN_ID_NAME = "Span-Id";
String TRACE_ID_NAME = "Trace-Id";
String SPAN_ID_NAME = "X-Span-Id";
String TRACE_ID_NAME = "X-Trace-Id";
String SPAN_NAME_NAME = "X-Span-Name";
String PARENT_ID_NAME = "X-Parent-Id";
String PROCESS_ID_NAME = "X-Process-Id";
/**
* Creates a new trace scope.
* Creates a trace scope wrapping a new span.
* <p/>
* If this thread has a currently active trace span, the trace scope we create
* here will contain a new span descending from the currently active span.
* If there is no currently active trace span, the trace scope we create will
* be empty.
* If this thread has a currently active span, it will be the parent of the span we
* create here, and the trace scope will contain the new span and the parent. If there
* is no currently active trace span, the trace scope we create will be empty.
*
* @param name The name field for the new span to create.
*/
TraceScope startSpan(String name);
TraceScope startSpan(String name, TraceInfo tinfo);
/**
* Creates a new trace scope.
* Creates a new trace scope with a specific parent. The parent might be in another
* process or thread.
* <p/>
* If this thread has a currently active trace span, it must be the 'parent'
* span that you pass in here as a parameter. The trace scope we create here
* will contain a new span which is a child of 'parent'.
* If this thread has a currently active trace span, it must be the 'parent' span that
* you pass in here as a parameter. The trace scope we create here will contain a new
* span which is a child of 'parent'.
*
* @param name The name field for the new span to create.
*/
TraceScope startSpan(String name, Span parent);
<T> TraceScope startSpan(String name, Sampler<T> s);
<T> TraceScope startSpan(String name, Sampler<T> s, T info);
/**
* Start a new span if the sampler allows it or if we are already tracing in this
* thread. A sampler can be used to limit the number of traces created.
*
* @param name the name of the span
* @param sampler a sampler to decide whether to create the span or not
* @param info the samplers context information
*/
<T> TraceScope startSpan(String name, Sampler<T> sampler, T info);
/**
* Pick up an existing span from another thread.

View File

@@ -1,12 +0,0 @@
package org.springframework.cloud.sleuth;
import lombok.Data;
/**
* @author Spencer Gibb
*/
@Data
public class TraceInfo {
private final String traceId;
private final String spanId;
}

View File

@@ -5,7 +5,6 @@ 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;
@@ -16,7 +15,6 @@ import org.springframework.context.ApplicationEventPublisher;
*/
@Value
@NonFinal
@CommonsLog
public class TraceScope implements Closeable {
private final ApplicationEventPublisher publisher;
@@ -41,26 +39,27 @@ public class TraceScope implements Closeable {
}
/**
* Remove this span as the current thread, but don't stop it yet or
* send it for collection. This is useful if the span object is then
* passed to another thread for use with Trace.continueTrace().
* Remove this span as the current thread, but don't stop it yet or send it for
* collection. This is useful if the span object is then passed to another thread for
* use with Trace.continueTrace().
*
* @return the same Span object
*/
public Span detach() {
if (this.detached) {
ExceptionUtils.error("Tried to detach trace span " + this.span + " but " +
"it has already been detached.");
ExceptionUtils.error("Tried to detach trace span " + this.span + " but "
+ "it has already been detached.");
}
this.detached = true;
Span cur = TraceContextHolder.getCurrentSpan();
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 {
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(this.savedSpan);
}
return this.span;
@@ -75,13 +74,19 @@ public class TraceScope implements Closeable {
this.detached = true;
Span cur = TraceContextHolder.getCurrentSpan();
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 {
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 {
this.span.stop();
this.publisher.publishEvent(new SpanStoppedEvent(this, this.span));
if (this.savedSpan != null && this.span.getParents().contains(this.savedSpan.getSpanId())) {
this.publisher.publishEvent(new SpanStoppedEvent(this, this.savedSpan, this.span));
}
else {
this.publisher.publishEvent(new SpanStoppedEvent(this, this.span));
}
TraceContextHolder.setCurrentSpan(this.savedSpan);
}
}

View File

@@ -25,13 +25,13 @@ public class TraceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public Sampler<?> defaultSampler() {
public Sampler<Void> defaultSampler() {
return new IsTracingSampler();
}
@Bean
@ConditionalOnMissingBean
public Trace trace(Sampler<?> sampler, IdGenerator idGenerator,
public Trace trace(Sampler<Void> sampler, IdGenerator idGenerator,
ApplicationEventPublisher publisher) {
return new DefaultTrace(sampler, idGenerator, publisher);
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.event;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.cloud.sleuth.Span;
import org.springframework.context.ApplicationEvent;
/**
* @author Dave Syer
*
*/
@Data
@EqualsAndHashCode(callSuper = false)
@SuppressWarnings("serial")
public class ClientReceivedEvent extends ApplicationEvent {
private final Span span;
public ClientReceivedEvent(Object source, Span span) {
super(source);
this.span = span;
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.event;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.cloud.sleuth.Span;
import org.springframework.context.ApplicationEvent;
/**
* @author Dave Syer
*
*/
@Data
@EqualsAndHashCode(callSuper = false)
@SuppressWarnings("serial")
public class ClientSentEvent extends ApplicationEvent {
private final Span span;
public ClientSentEvent(Object source, Span span) {
super(source);
this.span = span;
}
}

View File

@@ -1,7 +1,7 @@
package org.springframework.cloud.sleuth.event;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.springframework.cloud.sleuth.Span;
import org.springframework.context.ApplicationEvent;
@@ -9,15 +9,21 @@ import org.springframework.context.ApplicationEvent;
/**
* @author Spencer Gibb
*/
@Value
@Data
@EqualsAndHashCode(callSuper=false)
@SuppressWarnings("serial")
public class SpanStartedEvent extends ApplicationEvent {
private final Span parent;
private final Span span;
public SpanStartedEvent(Object source, Span span) {
this(source, null, span);
}
public SpanStartedEvent(Object source, Span parent, Span span) {
super(source);
this.parent = parent;
this.span = span;
}
}

View File

@@ -1,7 +1,7 @@
package org.springframework.cloud.sleuth.event;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.springframework.cloud.sleuth.Span;
import org.springframework.context.ApplicationEvent;
@@ -9,15 +9,21 @@ import org.springframework.context.ApplicationEvent;
/**
* @author Spencer Gibb
*/
@Value
@Data
@EqualsAndHashCode(callSuper=false)
@SuppressWarnings("serial")
public class SpanStoppedEvent extends ApplicationEvent {
private final Span span;
private final Span parent;
public SpanStoppedEvent(Object source, Span span) {
this(source, null, span);
}
public SpanStoppedEvent(Object source, Span parent, Span span) {
super(source);
this.parent = parent;
this.span = span;
}
}

View File

@@ -2,6 +2,7 @@ package org.springframework.cloud.sleuth.instrument;
import java.util.concurrent.Callable;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.springframework.cloud.sleuth.Span;
@@ -12,27 +13,28 @@ import org.springframework.cloud.sleuth.TraceScope;
* @author Spencer Gibb
*/
@Value
@EqualsAndHashCode(callSuper=false)
public class TraceCallable<V> extends TraceDelegate<Callable<V>> implements Callable<V> {
public TraceCallable(Trace trace, Callable<V> delagate) {
super(trace, delagate);
public TraceCallable(Trace trace, Callable<V> delegate) {
super(trace, delegate);
}
public TraceCallable(Trace trace, Callable<V> delagate, Span parent) {
super(trace, delagate, parent);
public TraceCallable(Trace trace, Callable<V> delegate, Span parent) {
super(trace, delegate, parent);
}
public TraceCallable(Trace trace, Callable<V> delagate, Span parent, String name) {
super(trace, delagate, parent, name);
public TraceCallable(Trace trace, Callable<V> delegate, Span parent, String name) {
super(trace, delegate, parent, name);
}
@Override
public V call() throws Exception {
if (this.parent != null) {
if (this.getParent() != null) {
TraceScope scope = startSpan();
try {
return this.delagate.call();
return this.getDelegate().call();
}
finally {
scope.close();
@@ -40,7 +42,7 @@ public class TraceCallable<V> extends TraceDelegate<Callable<V>> implements Call
}
else {
return this.delagate.call();
return this.getDelegate().call();
}
}

View File

@@ -1,5 +1,7 @@
package org.springframework.cloud.sleuth.instrument;
import lombok.Getter;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Trace;
import org.springframework.cloud.sleuth.TraceContextHolder;
@@ -8,24 +10,25 @@ import org.springframework.cloud.sleuth.TraceScope;
/**
* @author Spencer Gibb
*/
@Getter
public abstract class TraceDelegate<T> {
protected final Trace trace;
protected final T delagate;
protected final Span parent;
protected final String name;
public TraceDelegate(Trace trace, T delagate) {
this(trace, delagate, TraceContextHolder.getCurrentSpan(), null);
private final Trace trace;
private final T delegate;
private final Span parent;
private final String name;
public TraceDelegate(Trace trace, T delegate) {
this(trace, delegate, TraceContextHolder.getCurrentSpan(), null);
}
public TraceDelegate(Trace trace, T delagate, Span parent) {
this(trace, delagate, parent, null);
public TraceDelegate(Trace trace, T delegate, Span parent) {
this(trace, delegate, parent, null);
}
public TraceDelegate(Trace trace, T delagate, Span parent, String name) {
public TraceDelegate(Trace trace, T delegate, Span parent, String name) {
this.trace = trace;
this.delagate = delagate;
this.delegate = delegate;
this.parent = parent;
this.name = name;
}
@@ -35,6 +38,6 @@ public abstract class TraceDelegate<T> {
}
protected String getSpanName() {
return this.name == null ? Thread.currentThread().getName() : name;
return this.name == null ? Thread.currentThread().getName() : this.name;
}
}

View File

@@ -28,18 +28,18 @@ public class TraceRunnable extends TraceDelegate<Runnable> implements Runnable {
@Override
public void run() {
if (this.parent != null) {
if (this.getParent() != null) {
TraceScope scope = startSpan();
try {
this.delagate.run();
this.getDelegate().run();
}
finally {
scope.close();
}
}
else {
this.delagate.run();
this.getDelegate().run();
}
}
}

View File

@@ -15,7 +15,10 @@
*/
package org.springframework.cloud.sleuth.instrument.web;
import static org.springframework.cloud.sleuth.Trace.PARENT_ID_NAME;
import static org.springframework.cloud.sleuth.Trace.PROCESS_ID_NAME;
import static org.springframework.cloud.sleuth.Trace.SPAN_ID_NAME;
import static org.springframework.cloud.sleuth.Trace.SPAN_NAME_NAME;
import static org.springframework.cloud.sleuth.Trace.TRACE_ID_NAME;
import static org.springframework.util.StringUtils.hasText;
@@ -27,8 +30,9 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.cloud.sleuth.MilliSpan;
import org.springframework.cloud.sleuth.MilliSpan.MilliSpanBuilder;
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;
@@ -72,19 +76,32 @@ public class TraceFilter extends OncePerRequestFilter {
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String uri = hasText(request.getRequestURI()) ? request.getRequestURI() : "";
String uri = this.urlPathHelper.getPathWithinApplication(request);
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);
String name = "http" + uri;
if (hasText(spanId) && hasText(traceId)) {
TraceInfo traceInfo = new TraceInfo(traceId, spanId);
MilliSpanBuilder traceInfo = MilliSpan.builder().traceId(traceId).spanId(spanId);
String parentId = getHeader(request, response, PARENT_ID_NAME);
String processId = getHeader(request, response, PROCESS_ID_NAME);
String parentName = getHeader(request, response, SPAN_NAME_NAME);
if (parentName!=null) {
traceInfo.name(parentName);
}
if (processId!=null) {
traceInfo.processId(processId);
}
if (parentId!=null) {
traceInfo.parent(parentId);
}
// TODO: trace description?
traceScope = this.trace.startSpan(name, traceInfo);
traceScope = this.trace.startSpan(name, traceInfo.build());
// Send new span id back
addToResponseIfNotPresent(response, SPAN_ID_NAME, traceScope.getSpan()
.getSpanId());

View File

@@ -15,22 +15,31 @@
*/
package org.springframework.cloud.sleuth.instrument.web.client;
import static org.springframework.cloud.sleuth.Trace.PARENT_ID_NAME;
import static org.springframework.cloud.sleuth.Trace.PROCESS_ID_NAME;
import static org.springframework.cloud.sleuth.Trace.SPAN_ID_NAME;
import static org.springframework.cloud.sleuth.Trace.SPAN_NAME_NAME;
import static org.springframework.cloud.sleuth.Trace.TRACE_ID_NAME;
import static org.springframework.cloud.sleuth.TraceContextHolder.getCurrentSpan;
import static org.springframework.cloud.sleuth.TraceContextHolder.isTracing;
import java.io.IOException;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Trace;
import org.springframework.cloud.sleuth.event.ClientReceivedEvent;
import org.springframework.cloud.sleuth.event.ClientSentEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
/**
* Interceptor that verifies whether the trance and span id has been set on the
* request and sets them if one or both of them are missing.
* Interceptor that verifies whether the trance and span id has been set on the request
* and sets them if one or both of them are missing.
*
* @see org.springframework.web.client.RestTemplate
* @see Trace
@@ -38,14 +47,47 @@ import org.springframework.http.client.ClientHttpResponse;
* @author Marcin Grzejszczak, 4financeIT
* @author Spencer Gibb
*/
public class TraceRestTemplateInterceptor implements ClientHttpRequestInterceptor {
public class TraceRestTemplateInterceptor implements ClientHttpRequestInterceptor,
ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
setHeader(request, SPAN_ID_NAME, getCurrentSpan().getSpanId());
setHeader(request, TRACE_ID_NAME, getCurrentSpan().getTraceId());
return execution.execute(request, body);
setHeader(request, SPAN_NAME_NAME, getCurrentSpan().getName());
String parentId = getParentId(getCurrentSpan());
if (parentId != null) {
setHeader(request, PARENT_ID_NAME, parentId);
}
String processId = getCurrentSpan().getProcessId();
if (processId != null) {
setHeader(request, PROCESS_ID_NAME, processId);
}
publish(new ClientSentEvent(this, getCurrentSpan()));
try {
return execution.execute(request, body);
} finally {
publish(new ClientReceivedEvent(this, getCurrentSpan()));
}
}
private void publish(ApplicationEvent event) {
if (this.publisher !=null) {
this.publisher.publishEvent(event);
}
}
private String getParentId(Span span) {
return span.getParents() != null && !span.getParents().isEmpty() ? span
.getParents().get(0) : null;
}
public void setHeader(HttpRequest request, String name, String value) {

View File

@@ -1,46 +0,0 @@
package org.springframework.cloud.sleuth.instrument.zuul;
import org.springframework.cloud.sleuth.Trace;
import org.springframework.cloud.sleuth.TraceScope;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
/**
* @author Spencer Gibb
*/
public class TracePostFilter extends ZuulFilter {
private Trace trace;
public TracePostFilter(Trace trace) {
this.trace = trace;
}
@Override
public String filterType() {
return "post";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
TraceScope traceScope = (TraceScope) RequestContext.getCurrentContext().get(
"traceScope");
if (traceScope != null) {
traceScope.close();
}
return null;
}
}

View File

@@ -1,65 +0,0 @@
package org.springframework.cloud.sleuth.instrument.zuul;
import static org.springframework.cloud.sleuth.Trace.SPAN_ID_NAME;
import static org.springframework.cloud.sleuth.Trace.TRACE_ID_NAME;
import static org.springframework.util.StringUtils.hasText;
import javax.servlet.http.HttpServletRequest;
import org.springframework.cloud.sleuth.Trace;
import org.springframework.cloud.sleuth.TraceInfo;
import org.springframework.cloud.sleuth.TraceScope;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
/**
* @author Spencer Gibb
*/
public class TracePreFilter extends ZuulFilter {
private Trace trace;
public TracePreFilter(Trace trace) {
this.trace = trace;
}
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
String spanId = request.getHeader(SPAN_ID_NAME);
String traceId = request.getHeader(TRACE_ID_NAME);
TraceScope traceScope = null;
if (hasText(spanId) && hasText(traceId)) {
TraceInfo traceInfo = new TraceInfo(traceId, spanId);
// TODO: trace description?
traceScope = trace.startSpan("traceZuulFilter", traceInfo);
}
else {
traceScope = trace.startSpan("traceZuulFilter");
}
context.set("traceScope", traceScope);
return null;
}
}

View File

@@ -1,28 +0,0 @@
package org.springframework.cloud.sleuth.instrument.zuul;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cloud.sleuth.Trace;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.netflix.zuul.ZuulFilter;
/**
* @author Spencer Gibb
*/
@Configuration
@ConditionalOnClass(ZuulFilter.class)
@ConditionalOnBean(Trace.class)
public class TraceZuulAutoConfiguration {
@Bean
public TracePreFilter tracePreFilter(Trace trace) {
return new TracePreFilter(trace);
}
@Bean
public TracePostFilter tracePostFilter(Trace trace) {
return new TracePostFilter(trace);
}
}

View File

@@ -5,9 +5,9 @@ import org.springframework.cloud.sleuth.Sampler;
/**
* @author Spencer Gibb
*/
public class AlwaysSampler implements Sampler<Object> {
public class AlwaysSampler implements Sampler<Void> {
@Override
public boolean next(Object info) {
public boolean next(Void info) {
return true;
}
}

View File

@@ -6,10 +6,10 @@ import org.springframework.cloud.sleuth.TraceContextHolder;
/**
* @author Spencer Gibb
*/
public class IsTracingSampler implements Sampler<Object> {
public class IsTracingSampler implements Sampler<Void> {
@Override
public boolean next(Object info) {
public boolean next(Void info) {
return TraceContextHolder.getCurrentSpan() != null;
}
}

View File

@@ -13,12 +13,8 @@ import org.springframework.context.annotation.Configuration;
public class SleuthSlf4jAutoConfiguration {
@Bean
public Slf4jSpanStartedListener slf4jSpanStartedListener() {
return new Slf4jSpanStartedListener();
public Slf4jSpanListener slf4jSpanStartedListener() {
return new Slf4jSpanListener();
}
@Bean
public Slf4jSpanStoppedListener slf4jSpanStoppedListener() {
return new Slf4jSpanStoppedListener();
}
}

View File

@@ -0,0 +1,46 @@
package org.springframework.cloud.sleuth.slf4j;
import static org.springframework.cloud.sleuth.Trace.SPAN_ID_NAME;
import static org.springframework.cloud.sleuth.Trace.TRACE_ID_NAME;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Trace;
import org.springframework.cloud.sleuth.event.SpanStartedEvent;
import org.springframework.cloud.sleuth.event.SpanStoppedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
/**
* @author Spencer Gibb
*/
@Slf4j
@Order(Ordered.LOWEST_PRECEDENCE)
public class Slf4jSpanListener {
@EventListener(SpanStartedEvent.class)
public void start(SpanStartedEvent event) {
Span span = event.getSpan();
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);
if (event.getParent()!=null) {
log.info("Starting parent: {}", event.getParent());
}
}
@EventListener(SpanStoppedEvent.class)
public void stop(SpanStoppedEvent event) {
//TODO: what should this log level be?
log.info("Stopped span: {}", event.getSpan());
if (event.getParent()!=null) {
log.info("Stopped parent: {}", event.getParent());
}
MDC.remove(SPAN_ID_NAME);
MDC.remove(TRACE_ID_NAME);
}
}

View File

@@ -1,25 +0,0 @@
package org.springframework.cloud.sleuth.slf4j;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Trace;
import org.springframework.cloud.sleuth.event.SpanStartedEvent;
import org.springframework.context.ApplicationListener;
/**
* @author Spencer Gibb
*/
@Slf4j
public class Slf4jSpanStartedListener implements ApplicationListener<SpanStartedEvent> {
@Override
public void onApplicationEvent(SpanStartedEvent event) {
Span span = event.getSpan();
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,24 +0,0 @@
package org.springframework.cloud.sleuth.slf4j;
import static org.springframework.cloud.sleuth.Trace.SPAN_ID_NAME;
import static org.springframework.cloud.sleuth.Trace.TRACE_ID_NAME;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.cloud.sleuth.event.SpanStoppedEvent;
import org.springframework.context.ApplicationListener;
/**
* @author Spencer Gibb
*/
@Slf4j
public class Slf4jSpanStoppedListener implements ApplicationListener<SpanStoppedEvent> {
@Override
public void onApplicationEvent(SpanStoppedEvent event) {
//TODO: what should this log level be?
log.info("Received span: {}", event.getSpan());
MDC.remove(SPAN_ID_NAME);
MDC.remove(TRACE_ID_NAME);
}
}

View File

@@ -11,7 +11,6 @@ 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;
@@ -23,102 +22,73 @@ import org.springframework.context.ApplicationEventPublisher;
*/
public class DefaultTrace implements Trace {
private final Sampler<?> defaultSampler;
private final Sampler<Void> defaultSampler;
private final IdGenerator idGenerator;
private final ApplicationEventPublisher publisher;
public DefaultTrace(Sampler<?> defaultSampler, IdGenerator idGenerator,
public DefaultTrace(Sampler<Void> defaultSampler, IdGenerator idGenerator,
ApplicationEventPublisher publisher) {
this.defaultSampler = defaultSampler;
this.idGenerator = idGenerator;
this.publisher = publisher;
}
@Override
public TraceScope startSpan(String name) {
return this.startSpan(name, this.defaultSampler);
}
@Override
public TraceScope startSpan(String name, TraceInfo tinfo) {
if (tinfo == null) return doStart(null);
MilliSpan span = MilliSpan.builder()
.begin(System.currentTimeMillis())
.name(name)
.traceId(tinfo.getTraceId())
.spanId(this.idGenerator.create())
.parent(tinfo.getSpanId())
.build();
return doStart(span);
}
@Override
public TraceScope startSpan(String name, Span parent) {
if (parent == null) {
return startSpan(name);
}
Span currentSpan = getCurrentSpan();
if ((currentSpan != null) && (currentSpan != parent)) {
error("HTrace client error: thread " +
Thread.currentThread().getName() + " tried to start a new Span " +
"with parent " + parent.toString() + ", but there is already a " +
"currentSpan " + currentSpan);
if (currentSpan != null && !parent.equals(currentSpan)) {
error("HTrace client error: thread " + Thread.currentThread().getName()
+ " tried to start a new Span " + "with parent " + parent.toString()
+ ", but there is already a " + "currentSpan " + currentSpan);
}
return doStart(createChild(parent, name));
if (currentSpan==null) {
TraceContextHolder.setCurrentSpan(parent);
}
return continueSpan(createChild(parent, name));
}
@Override
public <T> TraceScope startSpan(String name, Sampler<T> s) {
return startSpan(name, s, null);
public TraceScope startSpan(String name) {
return this.startSpan(name, this.defaultSampler, null);
}
@Override
public <T> TraceScope startSpan(String name, Sampler<T> s, T info) {
Span span = null;
if (TraceContextHolder.isTracing() || s.next(info)) {
span = createNew(name);
}
return doStart(span);
}
protected Span createNew(String name) {
Span parent = getCurrentSpan();
if (parent == null) {
return MilliSpan.builder()
.begin(System.currentTimeMillis())
.name(name)
.traceId(this.idGenerator.create())
.spanId(this.idGenerator.create())
.build();
} else {
return createChild(parent, name);
}
}
protected Span createChild(Span parent, String childname) {
return MilliSpan.builder()
.begin(System.currentTimeMillis())
.name(childname)
.traceId(parent.getTraceId())
.parent(parent.getSpanId())
.spanId(this.idGenerator.create())
.processId(parent.getProcessId())
.build();
}
protected TraceScope doStart(Span span) {
if (span != null) {
this.publisher.publishEvent(new SpanStartedEvent(this, span));
span = createChild(getCurrentSpan(), name);
}
return continueSpan(span);
}
protected Span createChild(Span parent, String name) {
if (parent == null) {
MilliSpan span = MilliSpan.builder().begin(System.currentTimeMillis()).name(name)
.traceId(this.idGenerator.create()).spanId(this.idGenerator.create())
.build();
this.publisher.publishEvent(new SpanStartedEvent(this, span));
return span;
}
else {
MilliSpan span = MilliSpan.builder().begin(System.currentTimeMillis()).name(name)
.traceId(parent.getTraceId()).parent(parent.getSpanId())
.spanId(this.idGenerator.create()).processId(parent.getProcessId())
.build();
this.publisher.publishEvent(new SpanStartedEvent(this, parent, span));
return span;
}
}
@Override
public TraceScope continueSpan(Span span) {
// Return an empty TraceScope that does nothing on close
if (span == null) return NullScope.INSTANCE;
if (span == null)
return NullScope.INSTANCE;
Span oldSpan = getCurrentSpan();
TraceContextHolder.setCurrentSpan(span);
return new TraceScope(this.publisher, span, oldSpan);
@@ -144,7 +114,8 @@ public class DefaultTrace implements Trace {
@Override
public <V> Callable<V> wrap(Callable<V> callable) {
if (TraceContextHolder.isTracing()) {
return new TraceCallable<>(this, callable, TraceContextHolder.getCurrentSpan());
return new TraceCallable<>(this, callable,
TraceContextHolder.getCurrentSpan());
}
return callable;
}

View File

@@ -2,8 +2,8 @@ package org.springframework.cloud.sleuth;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.isA;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -38,7 +38,7 @@ public class DefaultTraceTests {
DefaultTrace trace = new DefaultTrace(new IsTracingSampler(),
new RandomUuidGenerator(), publisher);
TraceScope scope = trace.startSpan(CREATE_SIMPLE_TRACE, new AlwaysSampler());
TraceScope scope = trace.startSpan(CREATE_SIMPLE_TRACE, new AlwaysSampler(), null);
try {
importantWork1(trace);
}

View File

@@ -1,7 +1,5 @@
package org.springframework.cloud.sleuth.sample;
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;
@@ -36,6 +34,10 @@ public class SampleApplication {
SpringApplication.run(SampleApplication.class, args);
}
//@Bean public SpanCollector spanCollector() { return new LoggingSpanCollectorImpl(); }
// Use this for debugging (or if there is no Zipkin collector running on port 9410)
// @Bean
// public SpanCollector spanCollector() {
// return new LoggingSpanCollectorImpl();
// }
}

View File

@@ -67,7 +67,7 @@ ApplicationListener<EmbeddedServletContainerInitializedEvent> {
@SneakyThrows
@RequestMapping("/traced")
public String traced() {
TraceScope scope = this.trace.startSpan("customTraceEndpoint", new AlwaysSampler());
TraceScope scope = this.trace.startSpan("customTraceEndpoint", new AlwaysSampler(), null);
final Random random = new Random();
int millis = random.nextInt(1000);
log.info("Sleeping for {} millis", millis);

View File

@@ -7,7 +7,7 @@ spring:
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'
console: '%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} %clr([trace=%X{X-Trace-Id:-},span=%X{X-Span-Id:-}]){yellow} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex'
endpoints:
health:

View File

@@ -1,206 +0,0 @@
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

@@ -17,7 +17,6 @@ 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;
@@ -41,8 +40,8 @@ import com.google.common.base.Optional;
public class ZipkinAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public SpanCollector spanCollector() {
@ConditionalOnMissingBean(SpanCollector.class)
public ZipkinSpanCollector spanCollector() {
return new ZipkinSpanCollector(zipkinProperties().getHost(), zipkinProperties()
.getPort());
}
@@ -63,16 +62,16 @@ 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", matchIfMissing = true)
// public ZipkinSpanListener zipkinTrace(ServerTracer serverTracer, ClientTracer clientTracer) {
// return new ZipkinSpanListener(serverTracer, clientTracer);
// }
@Bean
@ConditionalOnProperty(value = "spring.cloud.sleuth.zipkin.braveTracer.enabled", havingValue = "false")
public SleuthTracer sleuthTracer(SpanCollector spanCollector) {
return new SleuthTracer(spanCollector);
// @ConditionalOnProperty(value = "spring.cloud.sleuth.zipkin.braveTracer.enabled", havingValue = "false")
public ZipkinSpanListener sleuthTracer(SpanCollector spanCollector) {
return new ZipkinSpanListener(spanCollector);
}
@Configuration

View File

@@ -1,87 +1,228 @@
package org.springframework.cloud.sleuth.zipkin;
import lombok.Data;
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 org.springframework.cloud.sleuth.event.ClientReceivedEvent;
import org.springframework.cloud.sleuth.event.ClientSentEvent;
import org.springframework.cloud.sleuth.event.SpanStartedEvent;
import org.springframework.cloud.sleuth.event.SpanStoppedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import com.github.kristofa.brave.ServerTracer;
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;
/**
* @author Spencer Gibb
*/
@CommonsLog
@Order(0)
public class ZipkinSpanListener {
private final ServerTracer serverTracer;
private SpanCollector spanCollector;
@Value("${spring.application.name:application}")
private String appName;
@Autowired
private ServerProperties serverProperties;
public ZipkinSpanListener(ServerTracer serverTracer) {
this.serverTracer = serverTracer;
public ZipkinSpanListener(SpanCollector spanCollector) {
this.spanCollector = spanCollector;
}
@EventListener
public void start(SpanStartedEvent event) {
preTrace(event.getSpan());
if (event.getParent()!=null) {
event.getParent().addTimelineAnnotation(zipkinCoreConstants.SERVER_RECV);
}
}
@EventListener
public void clientSend(ClientSentEvent event) {
event.getSpan().addTimelineAnnotation(zipkinCoreConstants.CLIENT_SEND);
}
@EventListener
public void clientReceive(ClientReceivedEvent event) {
event.getSpan().addTimelineAnnotation(zipkinCoreConstants.CLIENT_RECV);
}
@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);
if (event.getParent()!=null) {
event.getParent().addTimelineAnnotation(zipkinCoreConstants.SERVER_SEND);
this.spanCollector.collect(convert(event.getParent()));
}
else {
log.debug("Received no span state.");
this.serverTracer.setStateUnknown(spanName);
this.spanCollector.collect(convert(event.getSpan()));
}
/**
* Converts a given Sleuth 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)));
}
this.serverTracer.setServerReceived();
zipkinSpan.setId(hash(span.getSpanId()));
zipkinSpan.setName(span.getName());
zipkinSpan.setAnnotations(annotationList);
zipkinSpan.setBinary_annotations(binaryAnnotationList);
return zipkinSpan;
}
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()));
public Integer getPort() {
Integer port;
if (this.serverProperties.getPort() != null) {
port = this.serverProperties.getPort();
} else {
port = 8080; //TODO: support random port
}
return trace;
return port;
}
protected String getSpanName(Span context, TraceData traceData) {
return context.getName();
public int getAddress() {
String address;
if (this.serverProperties.getAddress() != null) {
address = this.serverProperties.getAddress().getHostAddress();
} else {
address = "127.0.0.1"; //TODO: get address from config
}
return ipAddressToInt(address);
}
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.");
public String getServiceName(Span span) {
String serviceName;
if (span.getProcessId() != null) {
serviceName = span.getProcessId().toLowerCase();
} else {
serviceName = this.appName;
}
return serviceName;
}
private int ipAddressToInt(final String ip) {
InetAddress inetAddress = null;
try {
this.serverTracer.setServerSend();
}
finally {
this.serverTracer.clearCurrentSpan();
inetAddress = InetAddress.getByName(ip);
} catch (final UnknownHostException e) {
throw new IllegalArgumentException(e);
}
return ByteBuffer.wrap(inetAddress.getAddress()).getInt();
}
@Data
private static class TraceData {
private Long traceId;
private Long spanId;
private Long parentSpanId;
private String spanName;
/**
* Add annotations from the sleuth Span.
*/
private List<Annotation> createZipkinAnnotations(Span span,
Endpoint endpoint) {
List<Annotation> annotationList = new ArrayList<>();
long srTime = 0, csTime = 0;
// add sleuth time annotation
for (TimelineAnnotation ta : span.getTimelineAnnotations()) {
Annotation zipkinAnnotation = createZipkinAnnotation(ta.getMsg(), ta.getTime(), 0, endpoint, true);
if (zipkinCoreConstants.SERVER_RECV.equals(ta.getMsg())) {
srTime = ta.getTime();
}
if (zipkinCoreConstants.SERVER_SEND.equals(ta.getMsg()) && srTime!=0) {
zipkinAnnotation.setDuration(new Long(ta.getTime() - srTime).intValue()*1000);
}
if (zipkinCoreConstants.CLIENT_SEND.equals(ta.getMsg())) {
csTime = ta.getTime();
}
if (zipkinCoreConstants.CLIENT_RECV.equals(ta.getMsg()) && csTime!=0) {
zipkinAnnotation.setDuration(new Long(ta.getTime() - csTime).intValue()*1000);
}
annotationList.add(zipkinAnnotation);
}
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 endpoint) {
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(endpoint);
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 endpoint the endpoint 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 endpoint, boolean sendRequest) {
Annotation annotation = new Annotation();
annotation.setHost(endpoint);
// 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) {