Merge branch 'master' into 2.0.x

This commit is contained in:
Marcin Grzejszczak
2017-09-29 12:21:30 +02:00
17 changed files with 307 additions and 100 deletions

View File

@@ -14,7 +14,7 @@
<properties>
<docs.main>spring-cloud-sleuth</docs.main>
<!-- Comma separated list of whitelisted branches -->
<docs.whitelisted.branches>1.0.x,1.1.x,2.0.x</docs.whitelisted.branches>
<docs.whitelisted.branches>1.0.x,1.1.x,1.2.x,2.0.x</docs.whitelisted.branches>
<main.basedir>${basedir}/..</main.basedir>
</properties>
<profiles>

View File

@@ -16,12 +16,6 @@
package org.springframework.cloud.sleuth;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -34,6 +28,13 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
/**
* Class for gathering and reporting statistics about a block of execution.
* <p>
@@ -160,6 +161,13 @@ public class Span implements SpanContext {
@JsonIgnore
private final Long startNanos;
private Long durationMicros; // serialized in json so micros precision isn't lost
/*
Using B3 propagation, it is most typical to share the same span ID across client and
the server. This has backend implications like who owns the timestamp (hint the
client does). When a SpanReporter receives a completed span, it should know if it
is shared or not.
*/
private final boolean shared;
@SuppressWarnings("unused")
private Span() {
@@ -191,17 +199,18 @@ public class Span implements SpanContext {
this.durationMicros = current.durationMicros;
this.baggage = current.baggage;
this.savedSpan = savedSpan;
this.shared = current.shared;
}
Span(long begin, long end, String name, long traceId, List<Long> parents,
long spanId, boolean remote, boolean exportable, String processId) {
this(begin, end, name, traceId, parents, spanId, remote, exportable, processId,
null);
null, false);
}
Span(long begin, long end, String name, long traceId, List<Long> parents,
long spanId, boolean remote, boolean exportable, String processId,
Span savedSpan) {
Span savedSpan, boolean shared) {
this(new SpanBuilder()
.begin(begin)
.end(end)
@@ -212,7 +221,8 @@ public class Span implements SpanContext {
.remote(remote)
.exportable(exportable)
.processId(processId)
.savedSpan(savedSpan));
.savedSpan(savedSpan)
.shared(shared));
}
Span(SpanBuilder builder) {
@@ -242,6 +252,7 @@ public class Span implements SpanContext {
this.logs.addAll(builder.logs);
this.baggage = new ConcurrentHashMap<>();
this.baggage.putAll(builder.baggage);
this.shared = builder.shared;
}
public static SpanBuilder builder() {
@@ -499,6 +510,16 @@ public class Span implements SpanContext {
return this.exportable;
}
/**
* Span and trace id got extracted from a carrier?
* We are adding data to the same span created by a remote client
*
* @since 1.3.0
*/
public boolean isShared() {
return this.shared;
}
/**
* Returns the 16 or 32 character hex representation of the span's trace ID
*
@@ -644,6 +665,7 @@ public class Span implements SpanContext {
private final List<Log> logs = new ArrayList<>();
private final Map<String, String> tags = new LinkedHashMap<>();
private final Map<String, String> baggage = new LinkedHashMap<>();
private boolean shared;
SpanBuilder() {
}
@@ -748,6 +770,11 @@ public class Span implements SpanContext {
return this;
}
public Span.SpanBuilder shared(boolean shared) {
this.shared = shared;
return this;
}
/**
* Creates a {@link Span.SpanBuilder} from the {@link Span}.
*/

View File

@@ -68,7 +68,7 @@ public class TraceAutoConfiguration {
@Bean
@ConditionalOnMissingBean(Tracer.class)
public DefaultTracer sleuthTracer(Sampler sampler, Random random,
public Tracer sleuthTracer(Sampler sampler, Random random,
SpanNamer spanNamer, SpanLogger spanLogger,
SpanReporter spanReporter, TraceKeys traceKeys) {
return new DefaultTracer(sampler, random, spanNamer, spanLogger,

View File

@@ -1,12 +1,12 @@
package org.springframework.cloud.sleuth.instrument.messaging;
import java.util.Map;
import java.util.Random;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanTextMap;
import org.springframework.cloud.sleuth.util.TextMapUtil;
import java.util.Map;
import java.util.Random;
/**
* Default implementation for messaging
*
@@ -18,9 +18,11 @@ public class HeaderBasedMessagingExtractor implements MessagingSpanTextMapExtrac
@Override
public Span joinTrace(SpanTextMap textMap) {
Map<String, String> carrier = TextMapUtil.asMap(textMap);
boolean spanIdMissing = !hasHeader(carrier, TraceMessageHeaders.SPAN_ID_NAME);
boolean traceIdMissing = !hasHeader(carrier, TraceMessageHeaders.TRACE_ID_NAME);
if (Span.SPAN_SAMPLED.equals(carrier.get(TraceMessageHeaders.SPAN_FLAGS_NAME))) {
String traceId = generateTraceIdIfMissing(carrier);
if (!carrier.containsKey(TraceMessageHeaders.SPAN_ID_NAME)) {
String traceId = generateTraceIdIfMissing(carrier, traceIdMissing);
if (spanIdMissing) {
carrier.put(TraceMessageHeaders.SPAN_ID_NAME, traceId);
}
} else if (!hasHeader(carrier, TraceMessageHeaders.SPAN_ID_NAME)
@@ -28,28 +30,32 @@ public class HeaderBasedMessagingExtractor implements MessagingSpanTextMapExtrac
return null;
// TODO: Consider throwing IllegalArgumentException;
}
return extractSpanFromHeaders(carrier, Span.builder());
boolean idMissing = spanIdMissing || traceIdMissing;
return extractSpanFromHeaders(carrier, Span.builder(), idMissing);
}
private String generateTraceIdIfMissing(Map<String, String> carrier) {
if (!hasHeader(carrier, TraceMessageHeaders.TRACE_ID_NAME)) {
private String generateTraceIdIfMissing(Map<String, String> carrier,
boolean traceIdMissing) {
if (traceIdMissing) {
carrier.put(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(new Random().nextLong()));
}
return carrier.get(TraceMessageHeaders.TRACE_ID_NAME);
}
private Span extractSpanFromHeaders(Map<String, String> carrier, Span.SpanBuilder spanBuilder) {
private Span extractSpanFromHeaders(Map<String, String> carrier,
Span.SpanBuilder spanBuilder, boolean idMissing) {
String traceId = carrier.get(TraceMessageHeaders.TRACE_ID_NAME);
spanBuilder = spanBuilder
.traceIdHigh(traceId.length() == 32 ? Span.hexToId(traceId, 0) : 0)
.traceId(Span.hexToId(traceId))
.spanId(Span.hexToId(carrier.get(TraceMessageHeaders.SPAN_ID_NAME)));
String flags = carrier.get(TraceMessageHeaders.SPAN_FLAGS_NAME);
if (Span.SPAN_SAMPLED.equals(flags)) {
boolean debug = Span.SPAN_SAMPLED.equals(flags);
boolean spanSampled = Span.SPAN_SAMPLED.equals(carrier.get(TraceMessageHeaders.SAMPLED_NAME));
if (debug) {
spanBuilder.exportable(true);
} else {
spanBuilder.exportable(
Span.SPAN_SAMPLED.equals(carrier.get(TraceMessageHeaders.SAMPLED_NAME)));
spanBuilder.exportable(spanSampled);
}
String processId = carrier.get(TraceMessageHeaders.PROCESS_ID_NAME);
String spanName = carrier.get(TraceMessageHeaders.SPAN_NAME_NAME);
@@ -61,6 +67,7 @@ public class HeaderBasedMessagingExtractor implements MessagingSpanTextMapExtrac
}
setParentIdIfApplicable(carrier, spanBuilder, TraceMessageHeaders.PARENT_ID_NAME);
spanBuilder.remote(true);
spanBuilder.shared((debug || spanSampled) && !idMissing);
for (Map.Entry<String, String> entry : carrier.entrySet()) {
if (entry.getKey().toLowerCase().startsWith(Span.SPAN_BAGGAGE_HEADER_PREFIX + TraceMessageHeaders.HEADER_DELIMITER)) {
spanBuilder.baggage(unprefixedKey(entry.getKey()), entry.getValue());

View File

@@ -171,7 +171,6 @@ public class TraceFilter extends GenericFilterBean {
// TODO: how to deal with response annotations and async?
return;
}
spanFromRequest = createSpanIfRequestNotHandled(request, spanFromRequest, name, skip);
detachOrCloseSpans(request, response, spanFromRequest, exception);
}
}
@@ -211,22 +210,6 @@ public class TraceFilter extends GenericFilterBean {
}
}
// This method is a fallback in case if handler interceptors didn't catch the request.
// In that case we are creating an artificial span so that it can be visible in Zipkin.
private Span createSpanIfRequestNotHandled(HttpServletRequest request,
Span spanFromRequest, String name, boolean skip) {
if (!requestHasAlreadyBeenHandled(request)) {
spanFromRequest = tracer().createSpan(name);
request.setAttribute(TRACE_REQUEST_ATTR, spanFromRequest);
if (log.isDebugEnabled() && !skip) {
log.debug("The request with uri [" + request.getRequestURI() + "] hasn't been handled by any of Sleuth's components. "
+ "That means that most likely you're using custom HandlerMappings and didn't add Sleuth's TraceHandlerInterceptor. "
+ "Sleuth will create a span to ensure that the graph of calls remains valid in Zipkin");
}
}
return spanFromRequest;
}
private boolean requestHasAlreadyBeenHandled(HttpServletRequest request) {
return request.getAttribute(TraceRequestAttributes.HANDLED_SPAN_REQUEST_ATTR) != null;
}
@@ -239,8 +222,6 @@ public class TraceFilter extends GenericFilterBean {
addResponseTagsForSpanWithoutParent(request, response);
if (span.hasSavedSpan() && requestHasAlreadyBeenHandled(request)) {
recordParentSpan(span.getSavedSpan());
} else if (!requestHasAlreadyBeenHandled(request)) {
span = tracer().close(span);
}
recordParentSpan(span);
// in case of a response with exception status will close the span when exception dispatch is handled

View File

@@ -35,7 +35,8 @@ public class ZipkinHttpSpanExtractor implements HttpSpanExtractor {
public Span joinTrace(SpanTextMap textMap) {
Map<String, String> carrier = TextMapUtil.asMap(textMap);
boolean debug = Span.SPAN_SAMPLED.equals(carrier.get(Span.SPAN_FLAGS));
if (debug && onlySpanIdIsPresent(carrier)) {
boolean idToBeGenerated = debug && onlySpanIdIsPresent(carrier);
if (idToBeGenerated) {
// we're only generating Trace ID since if there's no Span ID will assume
// that it's equal to Trace ID - we're trying to fix a malformed request
generateIdIfMissing(carrier, Span.TRACE_ID_NAME);
@@ -48,7 +49,7 @@ public class ZipkinHttpSpanExtractor implements HttpSpanExtractor {
boolean skip = this.skipPattern.matcher(uri).matches()
|| Span.SPAN_NOT_SAMPLED.equals(carrier.get(Span.SAMPLED_NAME));
long spanId = spanId(carrier);
return buildParentSpan(carrier, uri, skip, spanId);
return buildParentSpan(carrier, uri, skip, spanId, idToBeGenerated);
} catch (Exception e) {
log.error("Exception occurred while trying to extract span from carrier", e);
return null;
@@ -86,7 +87,8 @@ public class ZipkinHttpSpanExtractor implements HttpSpanExtractor {
}
}
private Span buildParentSpan(Map<String, String> carrier, String uri, boolean skip, long spanId) {
private Span buildParentSpan(Map<String, String> carrier, String uri, boolean skip,
long spanId, boolean idToBeGenerated) {
String traceId = carrier.get(Span.TRACE_ID_NAME);
Span.SpanBuilder span = Span.builder()
.traceIdHigh(traceId.length() == 32 ? Span.hexToId(traceId, 0) : 0)
@@ -106,6 +108,8 @@ public class ZipkinHttpSpanExtractor implements HttpSpanExtractor {
span.parent(Span.hexToId(carrier.get(Span.PARENT_ID_NAME)));
}
span.remote(true);
// trace, span id were retrieved from the headers and span is sampled
span.shared(!(skip || idToBeGenerated));
boolean debug = Span.SPAN_SAMPLED.equals(carrier.get(Span.SPAN_FLAGS));
if (debug) {
span.exportable(true);

View File

@@ -16,15 +16,16 @@
package org.springframework.cloud.sleuth;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.assertThat;
import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicLong;
import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.assertThat;
import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then;
import org.junit.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @author Marcin Grzejszczak
@@ -286,6 +287,7 @@ public class SpanTests {
private Span.SpanBuilder builder() {
return Span.builder().name("http:name").traceId(1L).spanId(2L).parent(3L)
.begin(1L).end(2L).traceId(3L).exportable(true).parent(4L)
.baggage("foo", "bar").remote(true).tag("tag", "tag").log(new Log(System.currentTimeMillis(), "log"));
.baggage("foo", "bar")
.remote(true).shared(true).tag("tag", "tag").log(new Log(System.currentTimeMillis(), "log"));
}
}

View File

@@ -190,6 +190,26 @@ public class SpanAssert extends AbstractAssert<SpanAssert, Span> {
return this;
}
public SpanAssert isShared() {
isNotNull();
if (!this.actual.isShared()) {
String message = "The span is supposed to be shared but it's not!";
log.error(message);
failWithMessage(message);
}
return this;
}
public SpanAssert isNotShared() {
isNotNull();
if (this.actual.isShared()) {
String message = "The span is NOT supposed to be shared but it is!";
log.error(message);
failWithMessage(message);
}
return this;
}
public SpanAssert isNotExportable() {
isNotNull();
if (this.actual.isExportable()) {

View File

@@ -16,6 +16,8 @@
package org.springframework.cloud.sleuth.instrument.messaging;
import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@@ -24,8 +26,6 @@ import org.junit.Test;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanTextMap;
import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then;
/**
* @author Marcin Grzejszczak
*/
@@ -42,7 +42,7 @@ public class HeaderBasedMessagingExtractorTests {
Span span = extractor.joinTrace(spanTextMap);
then(span).isExportable();
then(span).isExportable().isShared();
}
@Test
@@ -56,7 +56,7 @@ public class HeaderBasedMessagingExtractorTests {
Span span = extractor.joinTrace(spanTextMap);
then(span).isExportable();
then(span).isExportable().isShared();
}
@Test
@@ -69,7 +69,7 @@ public class HeaderBasedMessagingExtractorTests {
Span span = extractor.joinTrace(spanTextMap);
then(span).isExportable();
then(span).isExportable().isShared();
}
@Test
@@ -82,7 +82,7 @@ public class HeaderBasedMessagingExtractorTests {
Span span = extractor.joinTrace(spanTextMap);
then(span).isNotExportable();
then(span).isNotExportable().isNotShared();
}
@Test
@@ -93,7 +93,7 @@ public class HeaderBasedMessagingExtractorTests {
Span span = extractor.joinTrace(spanTextMap);
then(span).isExportable();
then(span).isExportable().isNotShared();
then(span.traceIdString()).isNotEmpty();
then(span.getSpanId()).isNotNull();
}
@@ -107,7 +107,7 @@ public class HeaderBasedMessagingExtractorTests {
Span span = extractor.joinTrace(spanTextMap);
then(span).isExportable();
then(span).isExportable().isNotShared();
then(span.traceIdString()).isNotEmpty();
then(span.getSpanId()).isEqualTo(10L);
}
@@ -121,7 +121,7 @@ public class HeaderBasedMessagingExtractorTests {
Span span = extractor.joinTrace(spanTextMap);
then(span).isExportable();
then(span).isExportable().isNotShared();
then(span.getTraceId()).isEqualTo(10L);
then(span.getSpanId()).isEqualTo(10L);
}

View File

@@ -94,18 +94,60 @@ public class HttpServletRequestExtractorTests {
@Test
public void should_accept_128bit_trace_id() {
String hex128Bits = "463ac35c9f6413ad48485a3953bb6124";
String lower64Bits = "48485a3953bb6124";
String hex128Bits = spanInHeaders();
BDDMockito.given(this.request.getHeaderNames())
.willReturn(new Vector<>(Arrays.asList(Span.TRACE_ID_NAME, Span.SPAN_ID_NAME)).elements());
BDDMockito.given(this.request.getHeader(Span.TRACE_ID_NAME))
.willReturn(hex128Bits);
BDDMockito.given(this.request.getHeader(Span.SPAN_ID_NAME))
.willReturn(lower64Bits);
Span span = this.extractor.joinTrace(new HttpServletRequestTextMap(this.request));
then(span.traceIdString()).isEqualTo(hex128Bits);
}
@Test
public void should_set_shared_flag_for_sampled_span_in_headers() {
spanInHeaders();
Span span = this.extractor.joinTrace(new HttpServletRequestTextMap(this.request));
then(span.isShared()).isTrue();
}
@Test
public void should_not_set_shared_flag_for_non_sampled_span_in_headers() {
spanInHeaders();
BDDMockito.given(this.request.getHeader(Span.SAMPLED_NAME))
.willReturn(Span.SPAN_NOT_SAMPLED);
Span span = this.extractor.joinTrace(new HttpServletRequestTextMap(this.request));
then(span.isShared()).isFalse();
}
@Test
public void should_not_set_shared_flag_for_sampled_span_in_headers_without_span_trace_id() {
BDDMockito.given(this.request.getHeaderNames())
.willReturn(new Vector<>(Arrays.asList(Span.SPAN_FLAGS, Span.SPAN_ID_NAME)).elements());
BDDMockito.given(this.request.getHeader(Span.SPAN_FLAGS))
.willReturn("1");
BDDMockito.given(this.request.getHeader(Span.SPAN_ID_NAME))
.willReturn("48485a3953bb6124");
Span span = this.extractor.joinTrace(new HttpServletRequestTextMap(this.request));
then(span.isShared()).isFalse();
}
private String spanInHeaders() {
String hex128Bits = "463ac35c9f6413ad48485a3953bb6124";
String lower64Bits = "48485a3953bb6124";
BDDMockito.given(this.request.getHeaderNames())
.willReturn(new Vector<>(Arrays.asList(Span.TRACE_ID_NAME, Span.SPAN_ID_NAME, Span.SAMPLED_NAME)).elements());
BDDMockito.given(this.request.getHeader(Span.TRACE_ID_NAME))
.willReturn(hex128Bits);
BDDMockito.given(this.request.getHeader(Span.SPAN_ID_NAME))
.willReturn(lower64Bits);
BDDMockito.given(this.request.getHeader(Span.SAMPLED_NAME))
.willReturn(Span.SPAN_SAMPLED);
return hex128Bits;
}
}

View File

@@ -45,6 +45,7 @@ import org.springframework.cloud.sleuth.trace.DefaultTracer;
import org.springframework.cloud.sleuth.trace.TestSpanContextHolder;
import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator;
import org.springframework.cloud.sleuth.util.ExceptionUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockFilterChain;
@@ -135,6 +136,35 @@ public class TraceFilterTests {
assertThat(this.span.tags()).containsEntry("http.status_code", HttpStatus.OK.toString());
then(TestSpanContextHolder.getCurrentSpan()).isNull();
then(new ListOfSpans(this.spanReporter.getSpans()))
.hasSize(1)
.hasASpanWithTagEqualTo("http.url", "http://localhost/?foo=bar")
.hasASpanWithTagEqualTo("http.host", "localhost")
.hasASpanWithTagEqualTo("http.path", "/")
.hasASpanWithTagEqualTo("http.method", HttpMethod.GET.toString())
.hasASpanWithTagEqualTo("http.status_code", HttpStatus.OK.toString())
.allSpansAreExportable();
}
@Test
public void startsNewTraceWithTraceHandlerInterceptor() throws Exception {
final BeanFactory beanFactory = beanFactory();
TraceFilter filter = new TraceFilter(beanFactory);
filter.doFilter(this.request, this.response, (req, resp) -> {
this.filterChain.doFilter(req, resp);
// Simulate execution of the TraceHandlerInterceptor
request.setAttribute(TraceRequestAttributes.HANDLED_SPAN_REQUEST_ATTR, tracer.getCurrentSpan());
});
then(TestSpanContextHolder.getCurrentSpan()).isNull();
then(new ListOfSpans(this.spanReporter.getSpans()))
.hasSize(1)
.hasASpanWithTagEqualTo("http.url", "http://localhost/?foo=bar")
.hasASpanWithTagEqualTo("http.host", "localhost")
.hasASpanWithTagEqualTo("http.path", "/")
.hasASpanWithTagEqualTo("http.method", HttpMethod.GET.toString())
.hasASpanWithTagEqualTo("http.status_code", HttpStatus.OK.toString())
.allSpansAreExportable();
}
@Test
@@ -160,16 +190,14 @@ public class TraceFilterTests {
TraceFilter filter = new TraceFilter(beanFactory);
filter.doFilter(this.request, this.response, this.filterChain);
// this creates a child span which is why we'd expect the parents to include the parent id
// especially important if no handler interceptors have been used.
// We add a child span on the server side to show which controller serviced the request
assertThat(this.span.getParents()).containsOnly(PARENT_ID);
assertThat(parentSpan())
assertThat(this.span.getSpanId()).isEqualTo(PARENT_ID);
assertThat(this.span)
.hasATag("http.url", "http://localhost/?foo=bar")
.hasATag("http.host", "localhost")
.hasATag("http.path", "/")
.hasATag("http.method", "GET");
then(TestSpanContextHolder.getCurrentSpan()).isNull();
then(ExceptionUtils.getLastException()).isNull();
}
private Span parentSpan() {
@@ -252,8 +280,8 @@ public class TraceFilterTests {
this.request.addHeader("X-Foo", "bar");
filter.doFilter(this.request, this.response, this.filterChain);
assertThat(parentSpan().tags()).contains(entry("http.x-foo", "bar"));
assertThat(parentSpan().tags()).contains(entry("http.x-foo", "bar"));
assertThat(this.span.tags()).contains(entry("http.x-foo", "bar"));
assertThat(this.span.tags()).contains(entry("http.x-foo", "bar"));
then(TestSpanContextHolder.getCurrentSpan()).isNull();
}
@@ -283,7 +311,7 @@ public class TraceFilterTests {
this.request.addHeader("X-Foo", "spam");
filter.doFilter(this.request, this.response, this.filterChain);
assertThat(parentSpan().tags()).contains(entry("http.x-foo", "'bar','spam'"));
assertThat(this.span.tags()).contains(entry("http.x-foo", "'bar','spam'"));
then(TestSpanContextHolder.getCurrentSpan()).isNull();
}
@@ -426,7 +454,7 @@ public class TraceFilterTests {
filter.doFilter(this.request, this.response, this.filterChain);
then(new ListOfSpans(this.spanReporter.getSpans()))
.allSpansAreExportable().hasSize(2).hasASpanWithSpanId(Span.hexToId("10"));
.allSpansAreExportable().hasSize(1).hasASpanWithSpanId(Span.hexToId("10"));
then(TestSpanContextHolder.getCurrentSpan()).isNull();
then(ExceptionUtils.getLastException()).isNull();
}
@@ -479,11 +507,7 @@ public class TraceFilterTests {
this.sampler = new NeverSampler();
TraceFilter filter = new TraceFilter(beanFactory());
filter.doFilter(this.request, this.response, (req, res) -> {
// Simulate the TraceHandlerInterceptor
req.setAttribute(TraceRequestAttributes.HANDLED_SPAN_REQUEST_ATTR, span);
this.filterChain.doFilter(req, res);
});
filter.doFilter(this.request, this.response, this.filterChain);
then(new ListOfSpans(this.spanReporter.getSpans()))
.doesNotHaveASpanWithName("http:/parent/")
@@ -503,7 +527,7 @@ public class TraceFilterTests {
* org.springframework.cloud.sleuth.instrument.TraceKeys}.
*/
public void verifyParentSpanHttpTags(HttpStatus status) {
assertThat(parentSpan().tags()).contains(entry("http.host", "localhost"),
assertThat(this.span.tags()).contains(entry("http.host", "localhost"),
entry("http.url", "http://localhost/?foo=bar"), entry("http.path", "/"),
entry("http.method", "GET"));
verifyCurrentSpanStatusCodeForAContinuedSpan(status);

View File

@@ -23,7 +23,9 @@ import org.springframework.boot.web.servlet.context.ServletWebServerInitializedE
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.cloud.commons.util.InetUtilsProperties;
import org.springframework.cloud.sleuth.Span;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -43,15 +45,17 @@ import java.lang.invoke.MethodHandles;
* @author Dave Syer
* @since 1.0.0
*/
public class ServerPropertiesHostLocator implements HostLocator {
public class ServerPropertiesHostLocator implements HostLocator, EnvironmentAware {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private static final String IP_ADDRESS_PROP_NAME = "spring.cloud.client.ipAddress";
private final ServerProperties serverProperties; // Nullable
private final String appName;
private final InetUtils inetUtils;
private final ZipkinProperties zipkinProperties;
private Integer port; // Lazy assigned
private Environment environment;
public ServerPropertiesHostLocator(ServerProperties serverProperties, String appName,
ZipkinProperties zipkinProperties, InetUtils inetUtils) {
@@ -98,6 +102,9 @@ public class ServerPropertiesHostLocator implements HostLocator {
if (this.serverProperties != null && this.serverProperties.getAddress() != null) {
address = this.serverProperties.getAddress().getHostAddress();
}
else if (this.environment != null) {
address = this.environment.getProperty(IP_ADDRESS_PROP_NAME, String.class);
}
else {
address = this.inetUtils.findFirstNonLoopbackAddress().getHostAddress();
}
@@ -120,4 +127,8 @@ public class ServerPropertiesHostLocator implements HostLocator {
return serviceName;
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}

View File

@@ -15,6 +15,10 @@
*/
package org.springframework.cloud.sleuth.zipkin.stream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.logging.Log;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.stream.Host;
@@ -26,10 +30,6 @@ import zipkin.Constants;
import zipkin.Endpoint;
import zipkin.Span.Builder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* This converts sleuth spans to zipkin ones, skipping invalid or unsampled.
*
@@ -96,9 +96,14 @@ final class ConvertToZipkinSpanList {
// rather let the client do that. Worst case we were propagated an unreported ID and
// Zipkin backfills timestamp and duration.
if (!span.isRemote()) {
zipkinSpan.timestamp(span.getBegin() * 1000);
if (!span.isRunning()) { // duration is authoritative, only write when the span stopped
zipkinSpan.duration(calculateDurationInMicros(span));
if (Boolean.TRUE.equals(span.isShared())) {
// don't report server-side timestamp on shared spans
zipkinSpan.timestamp(null).duration(null);
} else {
zipkinSpan.timestamp(span.getBegin() * 1000);
if (!span.isRunning()) { // duration is authoritative, only write when the span stopped
zipkinSpan.duration(calculateDurationInMicros(span));
}
}
}
zipkinSpan.traceIdHigh(span.getTraceIdHigh());

View File

@@ -15,6 +15,11 @@
*/
package org.springframework.cloud.sleuth.zipkin.stream;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import org.assertj.core.api.Condition;
import org.junit.Test;
import org.springframework.cloud.sleuth.Span;
@@ -23,11 +28,6 @@ import org.springframework.cloud.sleuth.stream.Spans;
import zipkin.Constants;
import zipkin.Endpoint;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import static org.assertj.core.api.Assertions.assertThat;
public class ConvertToZipkinSpanListTests {
@@ -217,6 +217,39 @@ public class ConvertToZipkinSpanListTests {
assertThat(result.traceId).isEqualTo(span.getTraceId());
}
@Test
public void shouldRemoveTimestampAndDurationForNonRemoteSharedSpan() {
Span span = Span.builder()
.name("foo")
.exportable(false)
.remote(false)
.shared(true)
.build();
Spans spans = new Spans(this.host, Collections.singletonList(span));
zipkin.Span result = ConvertToZipkinSpanList.convert(spans).get(0);
assertThat(result.duration).isNull();
assertThat(result.timestamp).isNull();
}
@Test
public void shouldNotRemoveTimestampAndDurationForNonRemoteNonSharedSpan() {
Span span = Span.builder()
.name("foo")
.exportable(false)
.remote(false)
.shared(false)
.build();
span.stop();
Spans spans = new Spans(this.host, Collections.singletonList(span));
zipkin.Span result = ConvertToZipkinSpanList.convert(spans).get(0);
assertThat(result.duration).isNotNull();
assertThat(result.timestamp).isNotNull();
}
Span span(String name) {
return span(name, false);
}

View File

@@ -22,7 +22,9 @@ import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.cloud.commons.util.InetUtilsProperties;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
import zipkin.Endpoint;
@@ -42,15 +44,18 @@ import java.nio.ByteBuffer;
* @author Dave Syer
* @since 1.0.0
*/
public class ServerPropertiesEndpointLocator implements EndpointLocator {
public class ServerPropertiesEndpointLocator implements EndpointLocator,
EnvironmentAware {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private static final String IP_ADDRESS_PROP_NAME = "spring.cloud.client.ipAddress";
private final ServerProperties serverProperties;
private final String appName;
private final InetUtils inetUtils;
private final ZipkinProperties zipkinProperties;
private Integer port;
private Environment environment;
public ServerPropertiesEndpointLocator(ServerProperties serverProperties,
String appName, ZipkinProperties zipkinProperties, InetUtils inetUtils) {
@@ -102,8 +107,18 @@ public class ServerPropertiesEndpointLocator implements EndpointLocator {
return ByteBuffer.wrap(this.serverProperties.getAddress().getAddress())
.getInt();
}
else if (this.environment != null) {
String ipAddress = this.environment
.getProperty(IP_ADDRESS_PROP_NAME, String.class);
return InetUtils.getIpAddressAsInt(ipAddress);
}
else {
return ByteBuffer.wrap(this.inetUtils.findFirstNonLoopbackAddress().getAddress()).getInt();
}
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}

View File

@@ -99,9 +99,14 @@ public class ZipkinSpanListener implements SpanReporter {
// rather let the client do that. Worst case we were propagated an unreported ID and
// Zipkin backfills timestamp and duration.
if (!convertedSpan.isRemote()) {
zipkinSpan.timestamp(convertedSpan.getBegin() * 1000L);
if (!convertedSpan.isRunning()) { // duration is authoritative, only write when the span stopped
zipkinSpan.duration(calculateDurationInMicros(convertedSpan));
// don't report server-side timestamp on shared spans
if (Boolean.TRUE.equals(convertedSpan.isShared())) {
zipkinSpan.timestamp(null).duration(null);
} else {
zipkinSpan.timestamp(convertedSpan.getBegin() * 1000L);
if (!convertedSpan.isRunning()) { // duration is authoritative, only write when the span stopped
zipkinSpan.duration(calculateDurationInMicros(convertedSpan));
}
}
}
zipkinSpan.traceIdHigh(convertedSpan.getTraceIdHigh());

View File

@@ -319,6 +319,37 @@ public class ZipkinSpanListenerTests {
assertThat(result.name).isEqualTo("foo");
}
@Test
public void shouldRemoveTimestampAndDurationForNonRemoteSharedSpan() {
Span span = Span.builder()
.name("foo")
.exportable(false)
.remote(false)
.shared(true)
.build();
zipkin.Span result = this.spanListener.convert(span);
assertThat(result.duration).isNull();
assertThat(result.timestamp).isNull();
}
@Test
public void shouldNotRemoveTimestampAndDurationForNonRemoteNonSharedSpan() {
Span span = Span.builder()
.name("foo")
.exportable(false)
.remote(false)
.shared(false)
.build();
span.stop();
zipkin.Span result = this.spanListener.convert(span);
assertThat(result.duration).isNotNull();
assertThat(result.timestamp).isNotNull();
}
@Configuration
@EnableAutoConfiguration
protected static class TestConfiguration {