Skip to content

Commit cbca519

Browse files
author
Adrian Cole
committed
Propagates logs and reports 128-bit trace IDs
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. See openzipkin/b3-propagation#6
1 parent c943e4c commit cbca519

File tree

18 files changed

+311
-104
lines changed

18 files changed

+311
-104
lines changed

spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java

Lines changed: 128 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ public class Span implements SpanContext {
138138
private final long begin;
139139
private long end = 0;
140140
private final String name;
141+
private final long traceIdHigh;
141142
private final long traceId;
142143
private List<Long> parents = new ArrayList<>();
143144
private final long spanId;
@@ -169,6 +170,7 @@ public Span(Span current, Span savedSpan) {
169170
this.begin = current.getBegin();
170171
this.end = current.getEnd();
171172
this.name = current.getName();
173+
this.traceIdHigh = current.getTraceIdHigh();
172174
this.traceId = current.getTraceId();
173175
this.parents = current.getParents();
174176
this.spanId = current.getSpanId();
@@ -183,37 +185,63 @@ public Span(Span current, Span savedSpan) {
183185
this.savedSpan = savedSpan;
184186
}
185187

188+
/**
189+
* @deprecated please use {@link SpanBuilder}
190+
*/
191+
@Deprecated
186192
public Span(long begin, long end, String name, long traceId, List<Long> parents,
187193
long spanId, boolean remote, boolean exportable, String processId) {
188194
this(begin, end, name, traceId, parents, spanId, remote, exportable, processId,
189195
null);
190196
}
191197

198+
/**
199+
* @deprecated please use {@link SpanBuilder}
200+
*/
201+
@Deprecated
192202
public Span(long begin, long end, String name, long traceId, List<Long> parents,
193203
long spanId, boolean remote, boolean exportable, String processId,
194204
Span savedSpan) {
195-
if (begin > 0) { // conventionally, 0 indicates unset
205+
this(new SpanBuilder()
206+
.begin(begin)
207+
.end(end)
208+
.name(name)
209+
.traceId(traceId)
210+
.parents(parents)
211+
.spanId(spanId)
212+
.remote(remote)
213+
.exportable(exportable)
214+
.processId(processId)
215+
.savedSpan(savedSpan));
216+
}
217+
218+
Span(SpanBuilder builder) {
219+
if (builder.begin > 0) { // conventionally, 0 indicates unset
196220
this.startNanos = null; // don't know the start tick
197-
this.begin = begin;
221+
this.begin = builder.begin;
198222
} else {
199223
this.startNanos = nanoTime();
200224
this.begin = System.currentTimeMillis();
201225
}
202-
if (end > 0) {
203-
this.end = end;
204-
this.durationMicros = (end - begin) * 1000;
226+
if (builder.end > 0) {
227+
this.end = builder.end;
228+
this.durationMicros = (this.end - this.begin) * 1000;
205229
}
206-
this.name = name != null ? name : "";
207-
this.traceId = traceId;
208-
this.parents = parents;
209-
this.spanId = spanId;
210-
this.remote = remote;
211-
this.exportable = exportable;
212-
this.processId = processId;
213-
this.savedSpan = savedSpan;
230+
this.name = builder.name != null ? builder.name : "";
231+
this.traceIdHigh = builder.traceIdHigh;
232+
this.traceId = builder.traceId;
233+
this.parents.addAll(builder.parents);
234+
this.spanId = builder.spanId;
235+
this.remote = builder.remote;
236+
this.exportable = builder.exportable;
237+
this.processId = builder.processId;
238+
this.savedSpan = builder.savedSpan;
214239
this.tags = new ConcurrentHashMap<>();
240+
this.tags.putAll(builder.tags);
215241
this.logs = new ConcurrentLinkedQueue<>();
242+
this.logs.addAll(builder.logs);
216243
this.baggage = new ConcurrentHashMap<>();
244+
this.baggage.putAll(builder.baggage);
217245
}
218246

219247
public static SpanBuilder builder() {
@@ -397,7 +425,30 @@ public long getSpanId() {
397425
}
398426

399427
/**
400-
* A pseudo-unique (random) number assigned to the trace associated with this span
428+
* When non-zero, the trace containing this span uses 128-bit trace identifiers.
429+
*
430+
* <p>{@code traceIdHigh} corresponds to the high bits in big-endian format and
431+
* {@link #getTraceId()} corresponds to the low bits.
432+
*
433+
* <p>Ex. to convert the two fields to a 128bit opaque id array, you'd use code like below.
434+
* <pre>{@code
435+
* ByteBuffer traceId128 = ByteBuffer.allocate(16);
436+
* traceId128.putLong(span.getTraceIdHigh());
437+
* traceId128.putLong(span.getTraceId());
438+
* traceBytes = traceId128.array();
439+
* }</pre>
440+
*
441+
* @see #traceIdString()
442+
* @since 1.2.0
443+
*/
444+
public long getTraceIdHigh() {
445+
return this.traceIdHigh;
446+
}
447+
448+
/**
449+
* Unique 8-byte identifier for a trace, set on all spans within it.
450+
*
451+
* @see #getTraceIdHigh() for notes about 128-bit trace identifiers
401452
*/
402453
public long getTraceId() {
403454
return this.traceId;
@@ -452,8 +503,27 @@ public boolean isExportable() {
452503
return this.exportable;
453504
}
454505

506+
/**
507+
* Returns the 16 or 32 character hex representation of the span's trace ID
508+
*
509+
* @since 1.2.0
510+
*/
511+
public String traceIdString() {
512+
if (this.traceIdHigh != 0) {
513+
char[] result = new char[32];
514+
writeHexLong(result, 0, this.traceIdHigh);
515+
writeHexLong(result, 16, this.traceId);
516+
return new String(result);
517+
}
518+
char[] result = new char[16];
519+
writeHexLong(result, 0, this.traceId);
520+
return new String(result);
521+
}
522+
455523
/**
456524
* Represents given long id as 16-character lower-hex string
525+
*
526+
* @see #traceIdString()
457527
*/
458528
public static String idToHex(long id) {
459529
char[] data = new char[16];
@@ -488,29 +558,40 @@ static void writeHexByte(char[] data, int pos, byte b) {
488558
public static long hexToId(String hexString) {
489559
Assert.hasText(hexString, "Can't convert empty hex string to long");
490560
int length = hexString.length();
491-
if (length < 1 || length > 32) throw new IllegalArgumentException("Malformed id");
561+
if (length < 1 || length > 32) throw new IllegalArgumentException("Malformed id: " + hexString);
492562

493563
// trim off any high bits
494-
int i = length > 16 ? length - 16 : 0;
564+
int beginIndex = length > 16 ? length - 16 : 0;
565+
566+
return hexToId(hexString, beginIndex);
567+
}
495568

569+
/**
570+
* Parses a 16 character lower-hex string with no prefix into an unsigned long, starting at the
571+
* specified index.
572+
*
573+
* @since 1.2.0
574+
*/
575+
public static long hexToId(String lowerHex, int index) {
576+
Assert.hasText(lowerHex, "Can't convert empty hex string to long");
496577
long result = 0;
497-
for (; i < length; i++) {
498-
char c = hexString.charAt(i);
578+
for (int endIndex = Math.min(index + 16, lowerHex.length()); index < endIndex; index++) {
579+
char c = lowerHex.charAt(index);
499580
result <<= 4;
500581
if (c >= '0' && c <= '9') {
501582
result |= c - '0';
502583
} else if (c >= 'a' && c <= 'f') {
503584
result |= c - 'a' + 10;
504585
} else {
505-
throw new IllegalArgumentException("Malformed id");
586+
throw new IllegalArgumentException("Malformed id: " + lowerHex);
506587
}
507588
}
508589
return result;
509590
}
510591

511592
@Override
512593
public String toString() {
513-
return "[Trace: " + idToHex(this.traceId) + ", Span: " + idToHex(this.spanId)
594+
return "[Trace: " + traceIdString() + ", Span: " + idToHex(this.spanId)
514595
+ ", Parent: " + getParentIdIfPresent() + ", exportable:" + this.exportable + "]";
515596
}
516597

@@ -520,31 +601,36 @@ private String getParentIdIfPresent() {
520601

521602
@Override
522603
public int hashCode() {
523-
final int prime = 31;
524-
int result = 1;
525-
result = prime * result + (int) (this.spanId ^ (this.spanId >>> 32));
526-
result = prime * result + (int) (this.traceId ^ (this.traceId >>> 32));
527-
return result;
604+
int h = 1;
605+
h *= 1000003;
606+
h ^= (this.traceIdHigh >>> 32) ^ this.traceIdHigh;
607+
h *= 1000003;
608+
h ^= (this.traceId >>> 32) ^ this.traceId;
609+
h *= 1000003;
610+
h ^= (this.spanId >>> 32) ^ this.spanId;
611+
h *= 1000003;
612+
return h;
528613
}
529614

530615
@Override
531-
public boolean equals(Object obj) {
532-
if (this == obj)
616+
public boolean equals(Object o) {
617+
if (o == this) {
533618
return true;
534-
if (obj == null)
535-
return false;
536-
if (getClass() != obj.getClass())
537-
return false;
538-
Span other = (Span) obj;
539-
if (this.spanId != other.spanId)
540-
return false;
541-
return this.traceId == other.traceId;
619+
}
620+
if (o instanceof Span) {
621+
Span that = (Span) o;
622+
return (this.traceIdHigh == that.traceIdHigh)
623+
&& (this.traceId == that.traceId)
624+
&& (this.spanId == that.spanId);
625+
}
626+
return false;
542627
}
543628

544629
public static class SpanBuilder {
545630
private long begin;
546631
private long end;
547632
private String name;
633+
private long traceIdHigh;
548634
private long traceId;
549635
private ArrayList<Long> parents = new ArrayList<>();
550636
private long spanId;
@@ -581,6 +667,11 @@ public Span.SpanBuilder name(String name) {
581667
return this;
582668
}
583669

670+
public Span.SpanBuilder traceIdHigh(long traceIdHigh) {
671+
this.traceIdHigh = traceIdHigh;
672+
return this;
673+
}
674+
584675
public Span.SpanBuilder traceId(long traceId) {
585676
this.traceId = traceId;
586677
return this;
@@ -652,23 +743,12 @@ public Span.SpanBuilder savedSpan(Span savedSpan) {
652743
}
653744

654745
public Span build() {
655-
Span span = new Span(this.begin, this.end, this.name, this.traceId,
656-
this.parents, this.spanId, this.remote, this.exportable,
657-
this.processId, this.savedSpan);
658-
span.logs.addAll(this.logs);
659-
span.tags.putAll(this.tags);
660-
span.baggage.putAll(this.baggage);
661-
return span;
746+
return new Span(this);
662747
}
663748

664749
@Override
665750
public String toString() {
666-
return "SpanBuilder{" + "begin=" + this.begin + ", end=" + this.end
667-
+ ", name=" + this.name + ", traceId=" + this.traceId + ", parents="
668-
+ this.parents + ", spanId=" + this.spanId + ", remote=" + this.remote
669-
+ ", exportable=" + this.exportable + ", processId='" + this.processId
670-
+ '\'' + ", savedSpan=" + this.savedSpan + ", logs=" + this.logs
671-
+ ", tags=" + this.tags + '}';
751+
return new Span(this).toString();
672752
}
673753
}
674754
}

spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingExtractor.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ public Span joinTrace(SpanTextMap textMap) {
2525
return extractSpanFromHeaders(carrier, Span.builder());
2626
}
2727

28-
private Span extractSpanFromHeaders(Map<String, String> carrier, Span.SpanBuilder builder) {
29-
Span.SpanBuilder spanBuilder = builder;
30-
long traceId = Span
31-
.hexToId(carrier.get(TraceMessageHeaders.TRACE_ID_NAME));
32-
long spanId = Span.hexToId(carrier.get(TraceMessageHeaders.SPAN_ID_NAME));
33-
spanBuilder = spanBuilder.traceId(traceId).spanId(spanId);
28+
private Span extractSpanFromHeaders(Map<String, String> carrier, Span.SpanBuilder spanBuilder) {
29+
String traceId = carrier.get(TraceMessageHeaders.TRACE_ID_NAME);
30+
spanBuilder = spanBuilder
31+
.traceIdHigh(traceId.length() == 32 ? Span.hexToId(traceId, 0) : 0)
32+
.traceId(Span.hexToId(traceId))
33+
.spanId(Span.hexToId(carrier.get(TraceMessageHeaders.SPAN_ID_NAME)));
3434
spanBuilder.exportable(
3535
Span.SPAN_SAMPLED.equals(carrier.get(TraceMessageHeaders.SAMPLED_NAME)));
3636
String processId = carrier.get(TraceMessageHeaders.PROCESS_ID_NAME);

spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingInjector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ private boolean isSampled(Map<String, String> initialMessage, String sampledHead
4141
}
4242

4343
private void addHeaders(Span span, SpanTextMap textMap) {
44-
addHeader(textMap, TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(span.getTraceId()));
44+
addHeader(textMap, TraceMessageHeaders.TRACE_ID_NAME, span.traceIdString());
4545
addHeader(textMap, TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(span.getSpanId()));
4646
if (span.isExportable()) {
4747
addAnnotations(this.traceKeys, textMap, span);

spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanExtractor.java

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,32 +41,33 @@ public Span joinTrace(SpanTextMap textMap) {
4141
String uri = carrier.get(URI_HEADER);
4242
boolean skip = this.skipPattern.matcher(uri).matches()
4343
|| Span.SPAN_NOT_SAMPLED.equals(carrier.get(Span.SAMPLED_NAME));
44-
long traceId = Span
45-
.hexToId(carrier.get(Span.TRACE_ID_NAME));
46-
long spanId = spanId(carrier, traceId);
47-
return buildParentSpan(carrier, uri, skip, traceId, spanId);
44+
long spanId = spanId(carrier);
45+
return buildParentSpan(carrier, uri, skip, spanId);
4846
} catch (Exception e) {
4947
log.error("Exception occurred while trying to extract span from carrier", e);
5048
return null;
5149
}
5250
}
5351

54-
private long spanId(Map<String, String> carrier, long traceId) {
52+
private long spanId(Map<String, String> carrier) {
5553
String spanId = carrier.get(Span.SPAN_ID_NAME);
5654
if (spanId == null) {
5755
if (log.isDebugEnabled()) {
5856
log.debug("Request is missing a span id but it has a trace id. We'll assume that this is "
59-
+ "a root span with span id equal to trace id");
57+
+ "a root span with span id equal to the lower 64-bits of the trace id");
6058
}
61-
return traceId;
59+
return Span.hexToId(carrier.get(Span.TRACE_ID_NAME));
6260
} else {
6361
return Span.hexToId(spanId);
6462
}
6563
}
6664

67-
private Span buildParentSpan(Map<String, String> carrier, String uri, boolean skip,
68-
long traceId, long spanId) {
69-
Span.SpanBuilder span = Span.builder().traceId(traceId).spanId(spanId);
65+
private Span buildParentSpan(Map<String, String> carrier, String uri, boolean skip, long spanId) {
66+
String traceId = carrier.get(Span.TRACE_ID_NAME);
67+
Span.SpanBuilder span = Span.builder()
68+
.traceIdHigh(traceId.length() == 32 ? Span.hexToId(traceId, 0) : 0)
69+
.traceId(Span.hexToId(traceId))
70+
.spanId(spanId);
7071
String processId = carrier.get(Span.PROCESS_ID_NAME);
7172
String parentName = carrier.get(Span.SPAN_NAME_NAME);
7273
if (StringUtils.hasText(parentName)) {

spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanInjector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public class ZipkinHttpSpanInjector implements HttpSpanInjector {
1818

1919
@Override
2020
public void inject(Span span, SpanTextMap carrier) {
21-
setIdHeader(carrier, Span.TRACE_ID_NAME, span.getTraceId());
21+
setHeader(carrier, Span.TRACE_ID_NAME, span.traceIdString());
2222
setIdHeader(carrier, Span.SPAN_ID_NAME, span.getSpanId());
2323
setHeader(carrier, Span.SAMPLED_NAME, span.isExportable() ? Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED);
2424
setHeader(carrier, Span.SPAN_NAME_NAME, span.getName());

spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/SpanInjectingRibbonRequestCustomizer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public void inject(Span span, SpanTextMap carrier) {
6868
}
6969
carrier.put(Span.SAMPLED_NAME, span.isExportable() ?
7070
Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED);
71-
carrier.put(Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId()));
71+
carrier.put(Span.TRACE_ID_NAME, span.traceIdString());
7272
carrier.put(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId()));
7373
carrier.put(Span.SPAN_NAME_NAME, span.getName());
7474
if (getParentId(span) != null) {

spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public Slf4jSpanLogger(String nameSkipPattern) {
4949
public void logStartedSpan(Span parent, Span span) {
5050
MDC.put(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId()));
5151
MDC.put(Span.SPAN_EXPORT_NAME, String.valueOf(span.isExportable()));
52-
MDC.put(Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId()));
52+
MDC.put(Span.TRACE_ID_NAME, span.traceIdString());
5353
log("Starting span: {}", span);
5454
if (parent != null) {
5555
log("With parent: {}", parent);
@@ -59,7 +59,7 @@ public void logStartedSpan(Span parent, Span span) {
5959
@Override
6060
public void logContinuedSpan(Span span) {
6161
MDC.put(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId()));
62-
MDC.put(Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId()));
62+
MDC.put(Span.TRACE_ID_NAME, span.traceIdString());
6363
MDC.put(Span.SPAN_EXPORT_NAME, String.valueOf(span.isExportable()));
6464
log("Continued span: {}", span);
6565
}

0 commit comments

Comments
 (0)