Propagates logs and reports 128-bit trace IDs (1.1.x) (#453)

This supports 128-bit traces via a new field traceIdHigh, which matches
other zipkin implementations. In encoded form, the trace ID is simply
twice as long (32 hex characters).

With this change in, a 128-bit trace propagated will not be downgraded
to 64-bits when sending downstream, reporting to Zipkin or adding to
the logging context.

This will be followed by a change to support initiating 128-bit traces.
This commit is contained in:
Adrian Cole
2016-11-18 14:40:25 +08:00
committed by GitHub
parent 6cb0c21e5a
commit 2a1b4befba
17 changed files with 343 additions and 95 deletions

View File

@@ -137,6 +137,7 @@ public class Span {
private final long begin;
private long end = 0;
private final String name;
private final long traceIdHigh;
private final long traceId;
private List<Long> parents = new ArrayList<>();
private final long spanId;
@@ -166,6 +167,7 @@ public class Span {
this.begin = current.getBegin();
this.end = current.getEnd();
this.name = current.getName();
this.traceIdHigh = current.getTraceIdHigh();
this.traceId = current.getTraceId();
this.parents = current.getParents();
this.spanId = current.getSpanId();
@@ -179,36 +181,61 @@ public class Span {
this.savedSpan = savedSpan;
}
/**
* @deprecated please use {@link SpanBuilder}
*/
@Deprecated
public 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);
}
/**
* @deprecated please use {@link SpanBuilder}
*/
@Deprecated
public Span(long begin, long end, String name, long traceId, List<Long> parents,
long spanId, boolean remote, boolean exportable, String processId,
Span savedSpan) {
if (begin > 0) { // conventionally, 0 indicates unset
this(new SpanBuilder()
.begin(begin)
.end(end)
.name(name)
.traceId(traceId)
.parents(parents)
.spanId(spanId)
.remote(remote)
.exportable(exportable)
.processId(processId)
.savedSpan(savedSpan));
}
Span(SpanBuilder builder) {
if (builder.begin > 0) { // conventionally, 0 indicates unset
this.startNanos = null; // don't know the start tick
this.begin = begin;
this.begin = builder.begin;
} else {
this.startNanos = nanoTime();
this.begin = System.currentTimeMillis();
}
if (end > 0) {
this.end = end;
this.durationMicros = (end - begin) * 1000;
if (builder.end > 0) {
this.end = builder.end;
this.durationMicros = (this.end - this.begin) * 1000;
}
this.name = name != null ? name : "";
this.traceId = traceId;
this.parents = parents;
this.spanId = spanId;
this.remote = remote;
this.exportable = exportable;
this.processId = processId;
this.savedSpan = savedSpan;
this.name = builder.name != null ? builder.name : "";
this.traceIdHigh = builder.traceIdHigh;
this.traceId = builder.traceId;
this.parents.addAll(builder.parents);
this.spanId = builder.spanId;
this.remote = builder.remote;
this.exportable = builder.exportable;
this.processId = builder.processId;
this.savedSpan = builder.savedSpan;
this.tags = new ConcurrentHashMap<>();
this.tags.putAll(builder.tags);
this.logs = new ConcurrentLinkedQueue<>();
this.logs.addAll(builder.logs);
}
public static SpanBuilder builder() {
@@ -358,7 +385,30 @@ public class Span {
}
/**
* A pseudo-unique (random) number assigned to the trace associated with this span
* When non-zero, the trace containing this span uses 128-bit trace identifiers.
*
* <p>{@code traceIdHigh} corresponds to the high bits in big-endian format and
* {@link #getTraceId()} corresponds to the low bits.
*
* <p>Ex. to convert the two fields to a 128bit opaque id array, you'd use code like below.
* <pre>{@code
* ByteBuffer traceId128 = ByteBuffer.allocate(16);
* traceId128.putLong(span.getTraceIdHigh());
* traceId128.putLong(span.getTraceId());
* traceBytes = traceId128.array();
* }</pre>
*
* @see #traceIdString()
* @since 1.0.11
*/
public long getTraceIdHigh() {
return this.traceIdHigh;
}
/**
* Unique 8-byte identifier for a trace, set on all spans within it.
*
* @see #getTraceIdHigh() for notes about 128-bit trace identifiers
*/
public long getTraceId() {
return this.traceId;
@@ -413,8 +463,27 @@ public class Span {
return this.exportable;
}
/**
* Returns the 16 or 32 character hex representation of the span's trace ID
*
* @since 1.0.11
*/
public String traceIdString() {
if (this.traceIdHigh != 0) {
char[] result = new char[32];
writeHexLong(result, 0, this.traceIdHigh);
writeHexLong(result, 16, this.traceId);
return new String(result);
}
char[] result = new char[16];
writeHexLong(result, 0, this.traceId);
return new String(result);
}
/**
* Represents given long id as 16-character lower-hex string
*
* @see #traceIdString()
*/
public static String idToHex(long id) {
char[] data = new char[16];
@@ -449,21 +518,32 @@ public class Span {
public static long hexToId(String hexString) {
Assert.hasText(hexString, "Can't convert empty hex string to long");
int length = hexString.length();
if (length < 1 || length > 32) throw new IllegalArgumentException("Malformed id");
if (length < 1 || length > 32) throw new IllegalArgumentException("Malformed id: " + hexString);
// trim off any high bits
int i = length > 16 ? length - 16 : 0;
int beginIndex = length > 16 ? length - 16 : 0;
return hexToId(hexString, beginIndex);
}
/**
* Parses a 16 character lower-hex string with no prefix into an unsigned long, starting at the
* specified index.
*
* @since 1.0.11
*/
public static long hexToId(String lowerHex, int index) {
Assert.hasText(lowerHex, "Can't convert empty hex string to long");
long result = 0;
for (; i < length; i++) {
char c = hexString.charAt(i);
for (int endIndex = Math.min(index + 16, lowerHex.length()); index < endIndex; index++) {
char c = lowerHex.charAt(index);
result <<= 4;
if (c >= '0' && c <= '9') {
result |= c - '0';
} else if (c >= 'a' && c <= 'f') {
result |= c - 'a' + 10;
} else {
throw new IllegalArgumentException("Malformed id");
throw new IllegalArgumentException("Malformed id: " + lowerHex);
}
}
return result;
@@ -471,7 +551,7 @@ public class Span {
@Override
public String toString() {
return "[Trace: " + idToHex(this.traceId) + ", Span: " + idToHex(this.spanId)
return "[Trace: " + traceIdString() + ", Span: " + idToHex(this.spanId)
+ ", Parent: " + getParentIdIfPresent() + ", exportable:" + this.exportable + "]";
}
@@ -481,31 +561,36 @@ public class Span {
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (this.spanId ^ (this.spanId >>> 32));
result = prime * result + (int) (this.traceId ^ (this.traceId >>> 32));
return result;
int h = 1;
h *= 1000003;
h ^= (this.traceIdHigh >>> 32) ^ this.traceIdHigh;
h *= 1000003;
h ^= (this.traceId >>> 32) ^ this.traceId;
h *= 1000003;
h ^= (this.spanId >>> 32) ^ this.spanId;
h *= 1000003;
return h;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
public boolean equals(Object o) {
if (o == this) {
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Span other = (Span) obj;
if (this.spanId != other.spanId)
return false;
return this.traceId == other.traceId;
}
if (o instanceof Span) {
Span that = (Span) o;
return (this.traceIdHigh == that.traceIdHigh)
&& (this.traceId == that.traceId)
&& (this.spanId == that.spanId);
}
return false;
}
public static class SpanBuilder {
private long begin;
private long end;
private String name;
private long traceIdHigh;
private long traceId;
private ArrayList<Long> parents = new ArrayList<>();
private long spanId;
@@ -541,6 +626,11 @@ public class Span {
return this;
}
public Span.SpanBuilder traceIdHigh(long traceIdHigh) {
this.traceIdHigh = traceIdHigh;
return this;
}
public Span.SpanBuilder traceId(long traceId) {
this.traceId = traceId;
return this;
@@ -602,22 +692,12 @@ public class Span {
}
public Span build() {
Span span = new Span(this.begin, this.end, this.name, this.traceId,
this.parents, this.spanId, this.remote, this.exportable,
this.processId, this.savedSpan);
span.logs.addAll(this.logs);
span.tags.putAll(this.tags);
return span;
return new Span(this);
}
@Override
public String toString() {
return "SpanBuilder{" + "begin=" + this.begin + ", end=" + this.end
+ ", name=" + this.name + ", traceId=" + this.traceId + ", parents="
+ this.parents + ", spanId=" + this.spanId + ", remote=" + this.remote
+ ", exportable=" + this.exportable + ", processId='" + this.processId
+ '\'' + ", savedSpan=" + this.savedSpan + ", logs=" + this.logs
+ ", tags=" + this.tags + '}';
return new Span(this).toString();
}
}
}

View File

@@ -76,12 +76,14 @@ class MessagingSpanExtractor implements SpanExtractor<Message<?>> {
private Span extractSpanFromHeaders(Message<?> carrier, SpanBuilder spanBuilder,
String traceIdHeader, String spanIdHeader, String spanSampledHeader,
String spanProcessIdHeader, String spanNameHeader, String spanParentIdHeader) {
long traceId = Span
.hexToId(getHeader(carrier, traceIdHeader));
String traceId = getHeader(carrier, traceIdHeader);
spanBuilder.traceIdHigh(traceId.length() == 32 ? Span.hexToId(traceId, 0) : 0);
spanBuilder.traceId(Span.hexToId(traceId));
long spanId = hasHeader(carrier, spanIdHeader)
? Span.hexToId(getHeader(carrier, spanIdHeader))
: this.random.nextLong();
spanBuilder = spanBuilder.traceId(traceId).spanId(spanId);
spanBuilder = spanBuilder.spanId(spanId);
spanBuilder.exportable(
Span.SPAN_SAMPLED.equals(getHeader(carrier, spanSampledHeader)));
String processId = getHeader(carrier, spanProcessIdHeader);

View File

@@ -95,7 +95,7 @@ class MessagingSpanInjector implements SpanInjector<MessageBuilder<?>> {
MessageHeaderAccessor accessor, Map<String, String> headers, String traceIdHeader,
String spanIdHeader, String parentIdHeader, String spanNameHeader, String processIdHeader,
String spanSampledHeader, String spanHeader) {
addHeader(headers, traceIdHeader, Span.idToHex(span.getTraceId()));
addHeader(headers, traceIdHeader, span.traceIdString());
addHeader(headers, spanIdHeader, Span.idToHex(span.getSpanId()));
if (span.isExportable()) {
addAnnotations(this.traceKeys, initialMessage, span);

View File

@@ -58,32 +58,33 @@ class HttpServletRequestExtractor implements SpanExtractor<HttpServletRequest> {
String uri = this.urlPathHelper.getPathWithinApplication(carrier);
boolean skip = this.skipPattern.matcher(uri).matches()
|| Span.SPAN_NOT_SAMPLED.equals(carrier.getHeader(Span.SAMPLED_NAME));
long traceId = Span
.hexToId(carrier.getHeader(Span.TRACE_ID_NAME));
long spanId = spanId(carrier, traceId);
return buildParentSpan(carrier, uri, skip, traceId, spanId);
long spanId = spanId(carrier);
return buildParentSpan(carrier, uri, skip, spanId);
} catch (Exception e) {
log.error("Exception occurred while trying to extract span from carrier", e);
return null;
}
}
private long spanId(HttpServletRequest carrier, long traceId) {
private long spanId(HttpServletRequest carrier) {
String spanId = carrier.getHeader(Span.SPAN_ID_NAME);
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 "
+ "a root span with span id equal to trace id");
+ "a root span with span id equal to the lower 64-bits of the trace id");
}
return traceId;
return Span.hexToId(carrier.getHeader(Span.TRACE_ID_NAME));
} else {
return Span.hexToId(spanId);
}
}
private Span buildParentSpan(HttpServletRequest carrier, String uri, boolean skip,
long traceId, long spanId) {
SpanBuilder span = Span.builder().traceId(traceId).spanId(spanId);
private Span buildParentSpan(HttpServletRequest carrier, String uri, boolean skip, long spanId) {
String traceId = carrier.getHeader(Span.TRACE_ID_NAME);
SpanBuilder span = Span.builder()
.traceIdHigh(traceId.length() == 32 ? Span.hexToId(traceId, 0) : 0)
.traceId(Span.hexToId(traceId))
.spanId(spanId);
String processId = carrier.getHeader(Span.PROCESS_ID_NAME);
String parentName = carrier.getHeader(Span.SPAN_NAME_NAME);
if (StringUtils.hasText(parentName)) {

View File

@@ -32,7 +32,7 @@ class HttpRequestInjector implements SpanInjector<HttpRequest> {
@Override
public void inject(Span span, HttpRequest carrier) {
setIdHeader(carrier, Span.TRACE_ID_NAME, span.getTraceId());
setHeader(carrier, Span.TRACE_ID_NAME, span.traceIdString());
setIdHeader(carrier, Span.SPAN_ID_NAME, span.getSpanId());
setHeader(carrier, Span.SAMPLED_NAME, span.isExportable() ? Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED);
setHeader(carrier, Span.SPAN_NAME_NAME, span.getName());

View File

@@ -41,7 +41,7 @@ class RequestContextInjector implements SpanInjector<RequestContext> {
return;
}
setHeader(requestHeaders, Span.SPAN_ID_NAME, span.getSpanId());
setHeader(requestHeaders, Span.TRACE_ID_NAME, span.getTraceId());
setHeader(requestHeaders, Span.TRACE_ID_NAME, span.traceIdString());
setHeader(requestHeaders, Span.SPAN_NAME_NAME, span.getName());
setHeader(requestHeaders, Span.SAMPLED_NAME, span.isExportable() ?
Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED);

View File

@@ -49,7 +49,7 @@ public class Slf4jSpanLogger implements SpanLogger {
public void logStartedSpan(Span parent, Span span) {
MDC.put(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId()));
MDC.put(Span.SPAN_EXPORT_NAME, String.valueOf(span.isExportable()));
MDC.put(Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId()));
MDC.put(Span.TRACE_ID_NAME, span.traceIdString());
log("Starting span: {}", span);
if (parent != null) {
log("With parent: {}", parent);
@@ -59,7 +59,7 @@ public class Slf4jSpanLogger implements SpanLogger {
@Override
public void logContinuedSpan(Span span) {
MDC.put(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId()));
MDC.put(Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId()));
MDC.put(Span.TRACE_ID_NAME, span.traceIdString());
MDC.put(Span.SPAN_EXPORT_NAME, String.valueOf(span.isExportable()));
log("Continued span: {}", span);
}

View File

@@ -17,7 +17,7 @@
package org.springframework.cloud.sleuth;
import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicLong;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -33,6 +33,45 @@ import static org.assertj.core.api.BDDAssertions.then;
* @author Spencer Gibb
*/
public class SpanTests {
Span span = Span.builder().begin(1).end(2).name("http:name").traceId(1L).spanId(2L)
.remote(true).exportable(true).processId("process").build();
@Test
public void should_consider_trace_and_span_id_on_equals_and_hashCode() throws Exception {
Span span = Span.builder().traceId(1L).spanId(2L).build();
Span differentSpan = Span.builder().traceId(1L).spanId(3L).build();
Span withParent =Span.builder().traceId(1L).spanId(2L).parent(3L).build();
then(span).isEqualTo(withParent);
then(span).isNotEqualTo(differentSpan);
then(span.hashCode()).isNotEqualTo(differentSpan.hashCode());
}
@Test
public void should_have_toString_with_identifiers_and_export() throws Exception {
span = Span.builder().traceId(1L).spanId(2L).parent(3L).name("foo").build();
then(span).hasToString(
"[Trace: 0000000000000001, Span: 0000000000000002, Parent: 0000000000000003, exportable:true]");
}
@Test
public void should_have_toString_with_128bit_trace_id() throws Exception {
span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).parent(4L).build();
then(span.toString()).startsWith("[Trace: 00000000000000010000000000000002,");
}
@Test
public void should_consider_128bit_trace_and_span_id_on_equals_and_hashCode() throws Exception {
Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).build();
Span differentSpan = Span.builder().traceIdHigh(2L).traceId(2L).spanId(3L).build();
Span withParent = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).parent(4L).build();
then(span).isEqualTo(withParent);
then(span).isNotEqualTo(differentSpan);
then(span.hashCode()).isNotEqualTo(differentSpan.hashCode());
}
@Test
public void should_convert_long_to_16_character_hex_string() throws Exception {
@@ -62,6 +101,36 @@ public class SpanTests {
then(someLong).isEqualTo(Span.hexToId(lower64Bits));
}
@Test
public void should_convert_offset_64bits_of_hex_string_to_long() throws Exception {
String hex128Bits = "463ac35c9f6413ad48485a3953bb6124";
String high64Bits = "463ac35c9f6413ad";
long someLong = Span.hexToId(hex128Bits, 0);
then(someLong).isEqualTo(Span.hexToId(high64Bits));
}
@Test
public void should_writeFixedLength64BitTraceId() throws Exception {
String traceId = span.traceIdString();
then(traceId).isEqualTo("0000000000000001");
}
@Test
public void should_writeFixedLength128BitTraceId() throws Exception {
String high128Bits = "463ac35c9f6413ad";
String low64Bits = "48485a3953bb6124";
span = Span.builder().traceIdHigh(Span.hexToId(high128Bits)).traceId(Span.hexToId(low64Bits))
.spanId(1L).name("foo").build();
String traceId = span.traceIdString();
then(traceId).isEqualTo(high128Bits + low64Bits);
}
@Test(expected = IllegalArgumentException.class)
public void should_throw_exception_when_null_string_is_to_be_converted_to_long() throws Exception {
Span.hexToId(null);
@@ -69,23 +138,15 @@ public class SpanTests {
@Test(expected = UnsupportedOperationException.class)
public void getAnnotationsReadOnly() {
Span span = new Span(1, 2, "http:name", 1L, Collections.<Long>emptyList(), 2L, true,
true, "process");
span.tags().put("a", "b");
}
@Test(expected = UnsupportedOperationException.class)
public void getTimelineAnnotationsReadOnly() {
Span span = new Span(1, 2, "http:name", 1L, Collections.<Long>emptyList(), 2L, true,
true, "process");
span.logs().add(new Log(1, "1"));
}
@Test public void should_properly_serialize_object() throws JsonProcessingException {
Span span = new Span(1, 2, "http:name", 1L,
Collections.<Long>emptyList(), 2L, true, true, "process");
ObjectMapper objectMapper = new ObjectMapper();
String serializedName = objectMapper.writeValueAsString(span);
@@ -94,8 +155,6 @@ public class SpanTests {
}
@Test public void should_properly_serialize_logs() throws IOException {
Span span = new Span(1, 2, "http:name", 1L,
Collections.<Long>emptyList(), 2L, true, true, "process");
span.logEvent("cs");
ObjectMapper objectMapper = new ObjectMapper();
@@ -108,8 +167,6 @@ public class SpanTests {
}
@Test public void should_properly_serialize_tags() throws IOException {
Span span = new Span(1, 2, "http:name", 1L,
Collections.<Long>emptyList(), 2L, true, true, "process");
span.tag("calculatedTax", "100");
ObjectMapper objectMapper = new ObjectMapper();
@@ -128,7 +185,7 @@ public class SpanTests {
/** When going over a transport like spring-cloud-stream, we must retain the precise duration. */
@Test public void shouldSerializeDurationMicros() throws IOException {
Span span = Span.builder().traceId(1L).name("http:parent").remote(true).build();
Span span = Span.builder().traceId(1L).name("http:parent").build();
span.stop();
assertThat(span.getAccumulatedMicros())
@@ -145,4 +202,41 @@ public class SpanTests {
assertThat(deserialized.getAccumulatedMicros())
.isEqualTo(span.getAccumulatedMicros());
}
// Duration of 0 is confusing to plot and can be misinterpreted as null
@Test public void getAccumulatedMicros_roundsUpToOneWhenRunning() throws IOException {
AtomicLong nanoTime = new AtomicLong();
// starts the span, recording its initial tick as zero
Span span = new Span(Span.builder().name("http:name").traceId(1L).spanId(2L)) {
@Override long nanoTime() {
return nanoTime.get();
}
};
// When only 100 nanoseconds passed
nanoTime.set(100L);
// We round so that we don't confuse "not started" with a short span.
assertThat(span.getAccumulatedMicros()).isEqualTo(1L);
}
// Duration of 0 is confusing to plot and can be misinterpreted as null
@Test public void getAccumulatedMicros_roundsUpToOneWhenStopped() throws IOException {
final AtomicLong nanoTime = new AtomicLong();
// starts the span, recording its initial tick as zero
Span span = new Span(Span.builder().name("http:name").traceId(1L).spanId(2L)) {
@Override long nanoTime() {
return nanoTime.get();
}
};
// When only 100 nanoseconds passed
nanoTime.set(100L);
span.stop();
// We round so that we don't confuse "not started" with a short span.
assertThat(span.getAccumulatedMicros()).isEqualTo(1L);
}
}

View File

@@ -81,7 +81,7 @@ public class Issue410Tests {
try {
String response = this.restTemplate.getForObject("http://localhost:" + port() + "/without_pool", String.class);
then(response).isEqualTo(Span.idToHex(span.getTraceId()));
then(response).isEqualTo(span.traceIdString());
Awaitility.await().until(() -> {
then(this.asyncTask.getSpan().get()).isNotNull();
then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId());
@@ -97,7 +97,7 @@ public class Issue410Tests {
try {
String response = this.restTemplate.getForObject("http://localhost:" + port() + "/with_pool", String.class);
then(response).isEqualTo(Span.idToHex(span.getTraceId()));
then(response).isEqualTo(span.traceIdString());
Awaitility.await().until(() -> {
then(this.asyncTask.getSpan().get()).isNotNull();
then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId());
@@ -116,7 +116,7 @@ public class Issue410Tests {
try {
String response = this.restTemplate.getForObject("http://localhost:" + port() + "/completable", String.class);
then(response).isEqualTo(Span.idToHex(span.getTraceId()));
then(response).isEqualTo(span.traceIdString());
Awaitility.await().until(() -> {
then(this.asyncTask.getSpan().get()).isNotNull();
then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId());
@@ -135,7 +135,7 @@ public class Issue410Tests {
try {
String response = this.restTemplate.getForObject("http://localhost:" + port() + "/taskScheduler", String.class);
then(response).isEqualTo(Span.idToHex(span.getTraceId()));
then(response).isEqualTo(span.traceIdString());
Awaitility.await().until(() -> {
then(this.asyncTask.getSpan().get()).isNotNull();
then(this.asyncTask.getSpan().get().getTraceId()).isEqualTo(span.getTraceId());
@@ -273,7 +273,7 @@ class Application {
public String withPool() {
log.info("Executing with pool.");
this.asyncTask.runWithPool();
return Span.idToHex(this.tracer.getCurrentSpan().getTraceId());
return this.tracer.getCurrentSpan().traceIdString();
}
@@ -281,19 +281,19 @@ class Application {
public String withoutPool() {
log.info("Executing without pool.");
this.asyncTask.runWithoutPool();
return Span.idToHex(this.tracer.getCurrentSpan().getTraceId());
return this.tracer.getCurrentSpan().traceIdString();
}
@RequestMapping("/completable")
public String completable() throws ExecutionException, InterruptedException {
log.info("Executing completable");
return Span.idToHex(this.asyncTask.completableFutures().getTraceId());
return this.asyncTask.completableFutures().traceIdString();
}
@RequestMapping("/taskScheduler")
public String taskScheduler() throws ExecutionException, InterruptedException {
log.info("Executing completable via task scheduler");
return Span.idToHex(this.asyncTask.taskScheduler().getTraceId());
return this.asyncTask.taskScheduler().traceIdString();
}
/**

View File

@@ -19,10 +19,10 @@ package org.springframework.cloud.sleuth.instrument.messaging;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import org.junit.Test;
import org.springframework.cloud.sleuth.Span;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.StringUtils;
@@ -56,6 +56,17 @@ public class MessagingSpanExtractorTests {
}
}
@Test
public void should_parse_128bit_trace_id() {
String traceId128 = "463ac35c9f6413ad48485a3953bb6124";
Span span = this.extractor.joinTrace(
MessageBuilder.createMessage("",
headers(traceId128, randomId())));
then(span.traceIdString()).isEqualTo(traceId128);
}
@Test
public void should_set_random_spanid_if_header_value_is_invalid() {
Message message = MessageBuilder.createMessage("",

View File

@@ -79,7 +79,7 @@ public class HttpServletRequestExtractorTests {
}
@Test
public void should_downgrade_128bit_trace_id_by_dropping_high_bits() {
public void should_accept_128bit_trace_id() {
String hex128Bits = "463ac35c9f6413ad48485a3953bb6124";
String lower64Bits = "48485a3953bb6124";
@@ -90,6 +90,6 @@ public class HttpServletRequestExtractorTests {
Span span = this.extractor.joinTrace(this.request);
then(span.getTraceId()).isEqualTo(Span.hexToId(lower64Bits));
then(span.traceIdString()).isEqualTo(hex128Bits);
}
}

View File

@@ -119,7 +119,7 @@ public class TraceCustomFilterResponseInjectorTests {
@Override
public void inject(Span span, HttpServletResponse carrier) {
carrier.addHeader(Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId()));
carrier.addHeader(Span.TRACE_ID_NAME, span.traceIdString());
carrier.addHeader(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId()));
}
}

View File

@@ -20,8 +20,10 @@ import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.slf4j.Logger;
import org.slf4j.MDC;
import org.springframework.cloud.sleuth.Span;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Matchers.anyList;
@@ -42,9 +44,46 @@ public class Slf4JSpanLoggerTest {
@Before
public void setup() {
MDC.clear();
given(log.isTraceEnabled()).willReturn(true);
}
@Test
public void when_start_event_arrived_should_add_64bit_trace_id_to_MDC() throws Exception {
Span span = Span.builder().traceId(1L).spanId(2L).build();
this.slf4JSpanLogger.logStartedSpan(this.spanWithNameNotToBeExcluded, span);
assertThat(MDC.get("X-B3-TraceId")).isEqualTo("0000000000000001");
}
@Test
public void when_start_event_arrived_should_add_128bit_trace_id_to_MDC() throws Exception {
Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).build();
this.slf4JSpanLogger.logStartedSpan(this.spanWithNameNotToBeExcluded, span);
assertThat(MDC.get("X-B3-TraceId")).isEqualTo("00000000000000010000000000000002");
}
@Test
public void when_continued_event_arrived_should_add_64bit_trace_id_to_MDC() throws Exception {
Span span = Span.builder().traceId(1L).spanId(2L).build();
this.slf4JSpanLogger.logContinuedSpan(span);
assertThat(MDC.get("X-B3-TraceId")).isEqualTo("0000000000000001");
}
@Test
public void when_continued_event_arrived_should_add_128bit_trace_id_to_MDC() throws Exception {
Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).build();
this.slf4JSpanLogger.logContinuedSpan(span);
assertThat(MDC.get("X-B3-TraceId")).isEqualTo("00000000000000010000000000000002");
}
@Test
public void should_log_when_start_event_arrived_and_pattern_doesnt_match_span_name() throws Exception {
this.slf4JSpanLogger.logStartedSpan(this.spanWithNameNotToBeExcluded,

View File

@@ -99,6 +99,7 @@ final class ConvertToZipkinSpanList {
zipkinSpan.duration(calculateDurationInMicros(span));
}
}
zipkinSpan.traceIdHigh(span.getTraceIdHigh());
zipkinSpan.traceId(span.getTraceId());
if (span.getParents().size() > 0) {
if (span.getParents().size() > 1) {

View File

@@ -19,12 +19,10 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import org.junit.Test;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.stream.Host;
import org.springframework.cloud.sleuth.stream.Spans;
import zipkin.Constants;
import static org.assertj.core.api.Assertions.assertThat;
@@ -183,13 +181,24 @@ public class ConvertToZipkinSpanListTests {
.isNull();
}
@Test
public void converts128BitTraceId() {
Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).name("foo").build();
Spans spans = new Spans(this.host, Collections.singletonList(span));
zipkin.Span result = ConvertToZipkinSpanList.convert(spans).get(0);
assertThat(result.traceIdHigh).isEqualTo(span.getTraceIdHigh());
assertThat(result.traceId).isEqualTo(span.getTraceId());
}
Span span(String name) {
return span(name, false);
}
Span span(String name, boolean remote) {
Long id = new Random().nextLong();
return new Span(1, 3, "message:" + name, id, Collections.<Long>emptyList(), id, remote, true,
"process");
return Span.builder().begin(1).end(3).name("message:" + name).traceId(id).spanId(id)
.remote(remote).processId("process").build();
}
}

View File

@@ -95,6 +95,7 @@ public class ZipkinSpanListener implements SpanReporter {
zipkinSpan.duration(calculateDurationInMicros(span));
}
}
zipkinSpan.traceIdHigh(span.getTraceIdHigh());
zipkinSpan.traceId(span.getTraceId());
if (span.getParents().size() > 0) {
if (span.getParents().size() > 1) {

View File

@@ -215,6 +215,16 @@ public class ZipkinSpanListenerTests {
.isNotEmpty();
}
@Test
public void converts128BitTraceId() {
Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).name("foo").build();
zipkin.Span result = this.spanReporter.convert(span);
assertThat(result.traceIdHigh).isEqualTo(span.getTraceIdHigh());
assertThat(result.traceId).isEqualTo(span.getTraceId());
}
@Test
public void shouldReuseServerAddressTag() {
this.parent.logEvent(Constants.CLIENT_SEND);