diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index 4b59b7653..7fa86ed3b 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -358,6 +358,12 @@ Span ids are extracted from Zipkin-compatible (B3) headers (either `Message` or HTTP headers), to start or join an existing trace. Trace information is injected into any outbound requests so the next hop can extract them. +The default way of coding tracing context is done via the `b3` header that contains the +`traceId-spanId-sampled` notation (e.g. `0000000000000005-0000000000000004-1`). +For backward compatibility, if the `b3` header is not present, we also check if +`X-B3` entries are present, and retrieve tracing context from there e.g. +(`X-B3-TraceId: 0000000000000005`, `X-B3-SpanId: 0000000000000004`, `X-B3-Sampled: 1`). + The key change in comparison to the previous versions of Sleuth is that Sleuth is implementing the Open Tracing's `TextMap` notion. In Sleuth it's called `SpanTextMap`. Basically the idea is that any means of communication (e.g. message, http request, etc.) can be abstracted via diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java index 9354644c3..1d887c152 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java @@ -75,6 +75,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; @JsonInclude(JsonInclude.Include.NON_DEFAULT) public class Span implements SpanContext { + public static final String B3_NAME = "b3"; public static final String SAMPLED_NAME = "X-B3-Sampled"; public static final String PROCESS_ID_NAME = "X-Process-Id"; public static final String PARENT_ID_NAME = "X-B3-ParentSpanId"; diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingExtractor.java index ff0712e89..b34fe941a 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingExtractor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingExtractor.java @@ -6,6 +6,7 @@ import java.util.Random; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanTextMap; import org.springframework.cloud.sleuth.util.TextMapUtil; +import org.springframework.util.StringUtils; /** * Default implementation for messaging @@ -20,8 +21,8 @@ public class HeaderBasedMessagingExtractor implements MessagingSpanTextMapExtrac @Override public Span joinTrace(SpanTextMap textMap) { Map carrier = TextMapUtil.asMap(textMap); - boolean spanIdMissing = !hasHeader(carrier, TraceMessageHeaders.SPAN_ID_NAME); - boolean traceIdMissing = !hasHeader(carrier, TraceMessageHeaders.TRACE_ID_NAME); + boolean spanIdMissing = !hasSpanId(carrier); + boolean traceIdMissing = !hasTraceId(carrier); if (Span.SPAN_SAMPLED.equals(carrier.get(TraceMessageHeaders.SPAN_FLAGS_NAME))) { String traceId = generateTraceIdIfMissing(carrier, traceIdMissing); if (spanIdMissing) { @@ -35,24 +36,35 @@ public class HeaderBasedMessagingExtractor implements MessagingSpanTextMapExtrac return extractSpanFromHeaders(carrier, Span.builder(), idMissing); } + private boolean hasTraceId(Map carrier) { + return hasHeader(carrier, TraceMessageHeaders.B3_NAME) || + hasHeader(carrier, TraceMessageHeaders.TRACE_ID_NAME); + } + + private boolean hasSpanId(Map carrier) { + return hasHeader(carrier, TraceMessageHeaders.B3_NAME) || + hasHeader(carrier, TraceMessageHeaders.SPAN_ID_NAME); + } + private String generateTraceIdIfMissing(Map carrier, boolean traceIdMissing) { if (traceIdMissing) { - carrier.put(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(this.random.nextLong())); + long id = this.random.nextLong(); + carrier.put(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(id)); } - return carrier.get(TraceMessageHeaders.TRACE_ID_NAME); + return traceId(carrier); } private Span extractSpanFromHeaders(Map carrier, Span.SpanBuilder spanBuilder, boolean idMissing) { - String traceId = carrier.get(TraceMessageHeaders.TRACE_ID_NAME); + String traceId = traceId(carrier); spanBuilder = spanBuilder .traceIdHigh(traceId.length() == 32 ? Span.hexToId(traceId, 0) : 0) .traceId(Span.hexToId(traceId)) - .spanId(Span.hexToId(carrier.get(TraceMessageHeaders.SPAN_ID_NAME))); + .spanId(Span.hexToId(spanId(carrier))); String flags = carrier.get(TraceMessageHeaders.SPAN_FLAGS_NAME); boolean debug = Span.SPAN_SAMPLED.equals(flags); - boolean spanSampled = Span.SPAN_SAMPLED.equals(carrier.get(TraceMessageHeaders.SAMPLED_NAME)); + boolean spanSampled = Span.SPAN_SAMPLED.equals(sampled(carrier)); if (debug) { spanBuilder.exportable(true); } else { @@ -77,6 +89,39 @@ public class HeaderBasedMessagingExtractor implements MessagingSpanTextMapExtrac return spanBuilder.build(); } + private String traceId(Map carrier) { + String b3 = carrier.get(TraceMessageHeaders.B3_NAME); + if (StringUtils.hasText(b3)) { + String[] split = b3.split("-"); + if (split.length == 3) { + return split[0]; + } + } + return carrier.get(TraceMessageHeaders.TRACE_ID_NAME); + } + + private String spanId(Map carrier) { + String b3 = carrier.get(TraceMessageHeaders.B3_NAME); + if (StringUtils.hasText(b3)) { + String[] split = b3.split("-"); + if (split.length == 3) { + return split[1]; + } + } + return carrier.get(TraceMessageHeaders.SPAN_ID_NAME); + } + + private String sampled(Map carrier) { + String b3 = carrier.get(TraceMessageHeaders.B3_NAME); + if (StringUtils.hasText(b3)) { + String[] split = b3.split("-"); + if (split.length == 3) { + return split[2]; + } + } + return carrier.get(TraceMessageHeaders.SAMPLED_NAME); + } + boolean hasHeader(Map message, String name) { return message.containsKey(name); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingInjector.java index 17db826ab..755906baf 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingInjector.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingInjector.java @@ -43,6 +43,8 @@ public class HeaderBasedMessagingInjector implements MessagingSpanTextMapInjecto private void addHeaders(Map map, Span span, SpanTextMap textMap) { addHeader(map, textMap, TraceMessageHeaders.TRACE_ID_NAME, span.traceIdString()); addHeader(map, textMap, TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); + addHeader(map, textMap, TraceMessageHeaders.B3_NAME, span.traceIdString() + "-" + + Span.idToHex(span.getSpanId()) + "-" + (span.isExportable() ? Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED)); if (span.isExportable()) { addAnnotations(this.traceKeys, textMap, span); Long parentId = getFirst(span.getParents()); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java index 6767f5ec2..21a56150a 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java @@ -29,6 +29,7 @@ import java.util.List; */ public class TraceMessageHeaders { + public static final String B3_NAME = "b3"; public static final String SPAN_ID_NAME = "spanId"; public static final String SAMPLED_NAME = "spanSampled"; public static final String PROCESS_ID_NAME = "spanProcessId"; diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanExtractor.java index 0af116726..70c3f2d85 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanExtractor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanExtractor.java @@ -57,18 +57,19 @@ public class ZipkinHttpSpanExtractor implements HttpSpanExtractor { } private boolean traceIdIsMissing(Map carrier) { - return carrier.get(Span.TRACE_ID_NAME) == null; + return traceId(carrier) == null; } private boolean spanIdIsPresent(Map carrier) { - return carrier.get(Span.SPAN_ID_NAME) != null; + return spanId(carrier) != null; } private String generateId() { return Span.idToHex(this.random.nextLong()); } - private long spanId(String spanId, String traceId) { + private long spanIdOrDefault(Map carrier, String traceId) { + String spanId = spanId(carrier); if (spanId == null) { if (log.isDebugEnabled()) { log.debug("Request is missing a span id but it has a trace id. We'll assume that this is " @@ -80,15 +81,53 @@ public class ZipkinHttpSpanExtractor implements HttpSpanExtractor { } } - private Span buildParentSpan(Map carrier, boolean idToBeGenerated) { - String traceId = carrier.get(Span.TRACE_ID_NAME); + private String traceId(Map carrier) { + String b3 = carrier.get(Span.B3_NAME); + if (StringUtils.hasText(b3)) { + String[] split = b3.split("-"); + if (split.length == 3) { + return split[0]; + } + } + return carrier.get(Span.TRACE_ID_NAME); + } + + private String spanId(Map carrier) { + String b3 = carrier.get(Span.B3_NAME); + if (StringUtils.hasText(b3)) { + String[] split = b3.split("-"); + if (split.length == 3) { + return split[1]; + } + } + return carrier.get(Span.SPAN_ID_NAME); + } + + private String sampled(Map carrier) { + String b3 = carrier.get(Span.B3_NAME); + if (StringUtils.hasText(b3)) { + String[] split = b3.split("-"); + if (split.length == 3) { + return split[2]; + } + } + return carrier.get(Span.SAMPLED_NAME); + } + + private String traceIdOrDefaut(Map carrier) { + String traceId = traceId(carrier); if (traceId == null) { traceId = generateId(); } + return traceId; + } + + private Span buildParentSpan(Map carrier, boolean idToBeGenerated) { + String traceId = traceIdOrDefaut(carrier); Span.SpanBuilder span = Span.builder() .traceIdHigh(traceId.length() == 32 ? Span.hexToId(traceId, 0) : 0) .traceId(Span.hexToId(traceId)) - .spanId(spanId(carrier.get(Span.SPAN_ID_NAME), traceId)); + .spanId(spanIdOrDefault(carrier, traceId)); String parentName = carrier.get(Span.SPAN_NAME_NAME); if (StringUtils.hasText(parentName)) { span.name(parentName); @@ -108,7 +147,7 @@ public class ZipkinHttpSpanExtractor implements HttpSpanExtractor { boolean skip = this.skipPattern .matcher(carrier.get(ZipkinHttpSpanMapper.URI_HEADER)).matches() - || Span.SPAN_NOT_SAMPLED.equals(carrier.get(Span.SAMPLED_NAME)); + || Span.SPAN_NOT_SAMPLED.equals(sampled(carrier)); // 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)); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanInjector.java index 6767a1e14..e7c57708f 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanInjector.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanInjector.java @@ -19,6 +19,7 @@ public class ZipkinHttpSpanInjector implements HttpSpanInjector { @Override public void inject(Span span, SpanTextMap map) { Map carrier = SPAN_CARRIER_MAPPER.convert(map); + setHeader(map, carrier, Span.B3_NAME, b3(span)); setHeader(map, carrier, Span.TRACE_ID_NAME, span.traceIdString()); setIdHeader(map, carrier, Span.SPAN_ID_NAME, span.getSpanId()); setHeader(map, carrier, Span.SAMPLED_NAME, span.isExportable() ? Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED); @@ -30,6 +31,11 @@ public class ZipkinHttpSpanInjector implements HttpSpanInjector { } } + private String b3(Span span) { + return span.traceIdString() + "-" + Span.idToHex(span.getSpanId()) + "-" + + (span.isExportable() ? Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED); + } + private String prefixedKey(String key) { if (key.startsWith(Span.SPAN_BAGGAGE_HEADER_PREFIX + ZipkinHttpSpanMapper.HEADER_DELIMITER)) { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanMapper.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanMapper.java index c2c2d9076..cf68ee7d5 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanMapper.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanMapper.java @@ -52,7 +52,8 @@ class ZipkinHttpSpanMapper { static { TreeSet fields = new TreeSet<>(IGNORE_CASE_COMPARATOR); - Collections.addAll(fields, Span.SPAN_FLAGS, Span.TRACE_ID_NAME, Span.SPAN_ID_NAME, + Collections.addAll(fields, Span.B3_NAME, + Span.SPAN_FLAGS, Span.TRACE_ID_NAME, Span.SPAN_ID_NAME, Span.PROCESS_ID_NAME, Span.SPAN_NAME_NAME, Span.PARENT_ID_NAME, Span.SAMPLED_NAME, URI_HEADER); SPAN_FIELDS = Collections.unmodifiableSet(fields); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java index 846987bb1..62a96d1d0 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java @@ -45,6 +45,16 @@ public class SpanAssert extends AbstractAssert { return this; } + public SpanAssert hasSpanIdEqualTo(Long spanId) { + isNotNull(); + if (!Objects.equals(this.actual.getSpanId(), spanId)) { + String message = String.format("Expected span's spanId to be <%s> but was <%s>", spanId, this.actual.getSpanId()); + log.error(message); + failWithMessage(message); + } + return this; + } + public SpanAssert hasNameEqualTo(String name) { isNotNull(); if (!Objects.equals(this.actual.getName(), name)) { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingExtractorTests.java index 572b9dfb4..df44e8318 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingExtractorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingExtractorTests.java @@ -16,8 +16,6 @@ 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; @@ -26,11 +24,49 @@ 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 */ public class HeaderBasedMessagingExtractorTests { + @Test + public void b3HeadersTakePrecedenceOverAnyOtherHeaders() { + HeaderBasedMessagingExtractor extractor = new HeaderBasedMessagingExtractor(); + SpanTextMap spanTextMap = spanTextMap(); + spanTextMap.put(TraceMessageHeaders.B3_NAME, "0000000000000005-0000000000000004-1"); + spanTextMap.put(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(20L)); + spanTextMap.put(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(30L)); + spanTextMap.put(TraceMessageHeaders.SAMPLED_NAME, "0"); + + Span span = extractor.joinTrace(spanTextMap); + + then(span) + .isNotNull() + .hasTraceIdEqualTo(5L) + .hasSpanIdEqualTo(4L) + .isExportable(); + } + + @Test + public void legacyHeadersTakePrecedenceOverB3WhenB3IsInvalid() { + HeaderBasedMessagingExtractor extractor = new HeaderBasedMessagingExtractor(); + SpanTextMap spanTextMap = spanTextMap(); + spanTextMap.put(TraceMessageHeaders.B3_NAME, "invalid"); + spanTextMap.put(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(20L)); + spanTextMap.put(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(30L)); + spanTextMap.put(TraceMessageHeaders.SAMPLED_NAME, "0"); + + Span span = extractor.joinTrace(spanTextMap); + + then(span) + .isNotNull() + .hasTraceIdEqualTo(30L) + .hasSpanIdEqualTo(20L) + .isNotExportable(); + } + @Test public void overridesTheSampleFlagWithSpanFlagForSampledScenario() { HeaderBasedMessagingExtractor extractor = new HeaderBasedMessagingExtractor(); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingInjectorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingInjectorTests.java index a40d5501e..2f558e815 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingInjectorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingInjectorTests.java @@ -40,6 +40,7 @@ public class HeaderBasedMessagingInjectorTests { injector.inject(span, map); then(map) + .contains(new AbstractMap.SimpleEntry(TraceMessageHeaders.B3_NAME, "0000000000000002-0000000000000001-1")) .contains(new AbstractMap.SimpleEntry(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(10L))) .contains(new AbstractMap.SimpleEntry(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(20L))) .contains(new AbstractMap.SimpleEntry(TraceMessageHeaders.PARENT_ID_NAME, Span.idToHex(30L))) diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java index 2a53e7b8c..56da3c529 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java @@ -16,13 +16,11 @@ package org.springframework.cloud.sleuth.instrument.web; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; - -import javax.servlet.http.HttpServletRequest; import java.util.Arrays; import java.util.Random; import java.util.Vector; import java.util.regex.Pattern; +import javax.servlet.http.HttpServletRequest; import org.junit.Before; import org.junit.Test; @@ -32,6 +30,8 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.cloud.sleuth.Span; +import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; + @RunWith(MockitoJUnitRunner.class) public class HttpServletRequestExtractorTests { @@ -80,11 +80,62 @@ public class HttpServletRequestExtractorTests { then(this.extractor.joinTrace(new HttpServletRequestTextMap(this.request))).isNull(); } + @Test + public void should_pick_values_from_b3_if_present() { + BDDMockito.given(this.request.getHeaderNames()) + .willReturn(new Vector<>(Arrays.asList(Span.B3_NAME, Span.TRACE_ID_NAME, + Span.SPAN_ID_NAME, Span.PARENT_ID_NAME, Span.SAMPLED_NAME, + Span.SPAN_FLAGS)).elements()); + BDDMockito.given(this.request.getHeader(Span.TRACE_ID_NAME)) + .willReturn(Span.idToHex(10L)); + BDDMockito.given(this.request.getHeader(Span.SPAN_ID_NAME)) + .willReturn(Span.idToHex(20L)); + BDDMockito.given(this.request.getHeader(Span.PARENT_ID_NAME)) + .willReturn(Span.idToHex(30L)); + BDDMockito.given(this.request.getHeader(Span.SAMPLED_NAME)) + .willReturn(Span.SPAN_NOT_SAMPLED); + BDDMockito.given(this.request.getHeader(Span.B3_NAME)) + .willReturn("0000000000000005-0000000000000004-1"); + + Span span = this.extractor.joinTrace(new HttpServletRequestTextMap(this.request)); + + then(span) + .isNotNull() + .hasTraceIdEqualTo(5L) + .hasSpanIdEqualTo(4L) + .isExportable(); + } + + @Test + public void should_pick_values_from_old_headers_when_b3_is_invalid() { + BDDMockito.given(this.request.getHeaderNames()) + .willReturn(new Vector<>(Arrays.asList(Span.B3_NAME, Span.TRACE_ID_NAME, + Span.SPAN_ID_NAME, Span.PARENT_ID_NAME, Span.SAMPLED_NAME, + Span.SPAN_FLAGS)).elements()); + BDDMockito.given(this.request.getHeader(Span.TRACE_ID_NAME)) + .willReturn(Span.idToHex(10L)); + BDDMockito.given(this.request.getHeader(Span.SPAN_ID_NAME)) + .willReturn(Span.idToHex(20L)); + BDDMockito.given(this.request.getHeader(Span.PARENT_ID_NAME)) + .willReturn(Span.idToHex(30L)); + BDDMockito.given(this.request.getHeader(Span.SAMPLED_NAME)) + .willReturn(Span.SPAN_NOT_SAMPLED); + BDDMockito.given(this.request.getHeader(Span.B3_NAME)) + .willReturn("invalid"); + + Span span = this.extractor.joinTrace(new HttpServletRequestTextMap(this.request)); + + then(span) + .isNotNull() + .hasTraceIdEqualTo(10L) + .hasSpanIdEqualTo(20L) + .isNotExportable(); + } + @Test public void should_accept_128bit_trace_id() { String hex128Bits = spanInHeaders(); - Span span = this.extractor.joinTrace(new HttpServletRequestTextMap(this.request)); then(span.traceIdString()).isEqualTo(hex128Bits); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanInjectorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanInjectorTests.java index 4110eaeb1..e8c83d777 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanInjectorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanInjectorTests.java @@ -38,6 +38,8 @@ public class ZipkinHttpSpanInjectorTests { injector.inject(span, map); then(map) + .contains(new AbstractMap.SimpleEntry(Span.B3_NAME, + "0000000000000002-0000000000000001-1")) .contains(new AbstractMap.SimpleEntry(Span.SPAN_ID_NAME, Span.idToHex(10L))) .contains(new AbstractMap.SimpleEntry(Span.TRACE_ID_NAME, Span.idToHex(20L))) .contains(new AbstractMap.SimpleEntry(Span.PARENT_ID_NAME, Span.idToHex(30L)))