Skip to content

Commit 2a1b4be

Browse files
authored
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.
1 parent 6cb0c21 commit 2a1b4be

File tree

17 files changed

+343
-95
lines changed

17 files changed

+343
-95
lines changed

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

Lines changed: 127 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ public class Span {
137137
private final long begin;
138138
private long end = 0;
139139
private final String name;
140+
private final long traceIdHigh;
140141
private final long traceId;
141142
private List<Long> parents = new ArrayList<>();
142143
private final long spanId;
@@ -166,6 +167,7 @@ public Span(Span current, Span savedSpan) {
166167
this.begin = current.getBegin();
167168
this.end = current.getEnd();
168169
this.name = current.getName();
170+
this.traceIdHigh = current.getTraceIdHigh();
169171
this.traceId = current.getTraceId();
170172
this.parents = current.getParents();
171173
this.spanId = current.getSpanId();
@@ -179,36 +181,61 @@ public Span(Span current, Span savedSpan) {
179181
this.savedSpan = savedSpan;
180182
}
181183

184+
/**
185+
* @deprecated please use {@link SpanBuilder}
186+
*/
187+
@Deprecated
182188
public Span(long begin, long end, String name, long traceId, List<Long> parents,
183189
long spanId, boolean remote, boolean exportable, String processId) {
184190
this(begin, end, name, traceId, parents, spanId, remote, exportable, processId,
185191
null);
186192
}
187193

194+
/**
195+
* @deprecated please use {@link SpanBuilder}
196+
*/
197+
@Deprecated
188198
public Span(long begin, long end, String name, long traceId, List<Long> parents,
189199
long spanId, boolean remote, boolean exportable, String processId,
190200
Span savedSpan) {
191-
if (begin > 0) { // conventionally, 0 indicates unset
201+
this(new SpanBuilder()
202+
.begin(begin)
203+
.end(end)
204+
.name(name)
205+
.traceId(traceId)
206+
.parents(parents)
207+
.spanId(spanId)
208+
.remote(remote)
209+
.exportable(exportable)
210+
.processId(processId)
211+
.savedSpan(savedSpan));
212+
}
213+
214+
Span(SpanBuilder builder) {
215+
if (builder.begin > 0) { // conventionally, 0 indicates unset
192216
this.startNanos = null; // don't know the start tick
193-
this.begin = begin;
217+
this.begin = builder.begin;
194218
} else {
195219
this.startNanos = nanoTime();
196220
this.begin = System.currentTimeMillis();
197221
}
198-
if (end > 0) {
199-
this.end = end;
200-
this.durationMicros = (end - begin) * 1000;
222+
if (builder.end > 0) {
223+
this.end = builder.end;
224+
this.durationMicros = (this.end - this.begin) * 1000;
201225
}
202-
this.name = name != null ? name : "";
203-
this.traceId = traceId;
204-
this.parents = parents;
205-
this.spanId = spanId;
206-
this.remote = remote;
207-
this.exportable = exportable;
208-
this.processId = processId;
209-
this.savedSpan = savedSpan;
226+
this.name = builder.name != null ? builder.name : "";
227+
this.traceIdHigh = builder.traceIdHigh;
228+
this.traceId = builder.traceId;
229+
this.parents.addAll(builder.parents);
230+
this.spanId = builder.spanId;
231+
this.remote = builder.remote;
232+
this.exportable = builder.exportable;
233+
this.processId = builder.processId;
234+
this.savedSpan = builder.savedSpan;
210235
this.tags = new ConcurrentHashMap<>();
236+
this.tags.putAll(builder.tags);
211237
this.logs = new ConcurrentLinkedQueue<>();
238+
this.logs.addAll(builder.logs);
212239
}
213240

214241
public static SpanBuilder builder() {
@@ -358,7 +385,30 @@ public long getSpanId() {
358385
}
359386

360387
/**
361-
* A pseudo-unique (random) number assigned to the trace associated with this span
388+
* When non-zero, the trace containing this span uses 128-bit trace identifiers.
389+
*
390+
* <p>{@code traceIdHigh} corresponds to the high bits in big-endian format and
391+
* {@link #getTraceId()} corresponds to the low bits.
392+
*
393+
* <p>Ex. to convert the two fields to a 128bit opaque id array, you'd use code like below.
394+
* <pre>{@code
395+
* ByteBuffer traceId128 = ByteBuffer.allocate(16);
396+
* traceId128.putLong(span.getTraceIdHigh());
397+
* traceId128.putLong(span.getTraceId());
398+
* traceBytes = traceId128.array();
399+
* }</pre>
400+
*
401+
* @see #traceIdString()
402+
* @since 1.0.11
403+
*/
404+
public long getTraceIdHigh() {
405+
return this.traceIdHigh;
406+
}
407+
408+
/**
409+
* Unique 8-byte identifier for a trace, set on all spans within it.
410+
*
411+
* @see #getTraceIdHigh() for notes about 128-bit trace identifiers
362412
*/
363413
public long getTraceId() {
364414
return this.traceId;
@@ -413,8 +463,27 @@ public boolean isExportable() {
413463
return this.exportable;
414464
}
415465

466+
/**
467+
* Returns the 16 or 32 character hex representation of the span's trace ID
468+
*
469+
* @since 1.0.11
470+
*/
471+
public String traceIdString() {
472+
if (this.traceIdHigh != 0) {
473+
char[] result = new char[32];
474+
writeHexLong(result, 0, this.traceIdHigh);
475+
writeHexLong(result, 16, this.traceId);
476+
return new String(result);
477+
}
478+
char[] result = new char[16];
479+
writeHexLong(result, 0, this.traceId);
480+
return new String(result);
481+
}
482+
416483
/**
417484
* Represents given long id as 16-character lower-hex string
485+
*
486+
* @see #traceIdString()
418487
*/
419488
public static String idToHex(long id) {
420489
char[] data = new char[16];
@@ -449,29 +518,40 @@ static void writeHexByte(char[] data, int pos, byte b) {
449518
public static long hexToId(String hexString) {
450519
Assert.hasText(hexString, "Can't convert empty hex string to long");
451520
int length = hexString.length();
452-
if (length < 1 || length > 32) throw new IllegalArgumentException("Malformed id");
521+
if (length < 1 || length > 32) throw new IllegalArgumentException("Malformed id: " + hexString);
453522

454523
// trim off any high bits
455-
int i = length > 16 ? length - 16 : 0;
524+
int beginIndex = length > 16 ? length - 16 : 0;
525+
526+
return hexToId(hexString, beginIndex);
527+
}
456528

529+
/**
530+
* Parses a 16 character lower-hex string with no prefix into an unsigned long, starting at the
531+
* specified index.
532+
*
533+
* @since 1.0.11
534+
*/
535+
public static long hexToId(String lowerHex, int index) {
536+
Assert.hasText(lowerHex, "Can't convert empty hex string to long");
457537
long result = 0;
458-
for (; i < length; i++) {
459-
char c = hexString.charAt(i);
538+
for (int endIndex = Math.min(index + 16, lowerHex.length()); index < endIndex; index++) {
539+
char c = lowerHex.charAt(index);
460540
result <<= 4;
461541
if (c >= '0' && c <= '9') {
462542
result |= c - '0';
463543
} else if (c >= 'a' && c <= 'f') {
464544
result |= c - 'a' + 10;
465545
} else {
466-
throw new IllegalArgumentException("Malformed id");
546+
throw new IllegalArgumentException("Malformed id: " + lowerHex);
467547
}
468548
}
469549
return result;
470550
}
471551

472552
@Override
473553
public String toString() {
474-
return "[Trace: " + idToHex(this.traceId) + ", Span: " + idToHex(this.spanId)
554+
return "[Trace: " + traceIdString() + ", Span: " + idToHex(this.spanId)
475555
+ ", Parent: " + getParentIdIfPresent() + ", exportable:" + this.exportable + "]";
476556
}
477557

@@ -481,31 +561,36 @@ private String getParentIdIfPresent() {
481561

482562
@Override
483563
public int hashCode() {
484-
final int prime = 31;
485-
int result = 1;
486-
result = prime * result + (int) (this.spanId ^ (this.spanId >>> 32));
487-
result = prime * result + (int) (this.traceId ^ (this.traceId >>> 32));
488-
return result;
564+
int h = 1;
565+
h *= 1000003;
566+
h ^= (this.traceIdHigh >>> 32) ^ this.traceIdHigh;
567+
h *= 1000003;
568+
h ^= (this.traceId >>> 32) ^ this.traceId;
569+
h *= 1000003;
570+
h ^= (this.spanId >>> 32) ^ this.spanId;
571+
h *= 1000003;
572+
return h;
489573
}
490574

491575
@Override
492-
public boolean equals(Object obj) {
493-
if (this == obj)
576+
public boolean equals(Object o) {
577+
if (o == this) {
494578
return true;
495-
if (obj == null)
496-
return false;
497-
if (getClass() != obj.getClass())
498-
return false;
499-
Span other = (Span) obj;
500-
if (this.spanId != other.spanId)
501-
return false;
502-
return this.traceId == other.traceId;
579+
}
580+
if (o instanceof Span) {
581+
Span that = (Span) o;
582+
return (this.traceIdHigh == that.traceIdHigh)
583+
&& (this.traceId == that.traceId)
584+
&& (this.spanId == that.spanId);
585+
}
586+
return false;
503587
}
504588

505589
public static class SpanBuilder {
506590
private long begin;
507591
private long end;
508592
private String name;
593+
private long traceIdHigh;
509594
private long traceId;
510595
private ArrayList<Long> parents = new ArrayList<>();
511596
private long spanId;
@@ -541,6 +626,11 @@ public Span.SpanBuilder name(String name) {
541626
return this;
542627
}
543628

629+
public Span.SpanBuilder traceIdHigh(long traceIdHigh) {
630+
this.traceIdHigh = traceIdHigh;
631+
return this;
632+
}
633+
544634
public Span.SpanBuilder traceId(long traceId) {
545635
this.traceId = traceId;
546636
return this;
@@ -602,22 +692,12 @@ public Span.SpanBuilder savedSpan(Span savedSpan) {
602692
}
603693

604694
public Span build() {
605-
Span span = new Span(this.begin, this.end, this.name, this.traceId,
606-
this.parents, this.spanId, this.remote, this.exportable,
607-
this.processId, this.savedSpan);
608-
span.logs.addAll(this.logs);
609-
span.tags.putAll(this.tags);
610-
return span;
695+
return new Span(this);
611696
}
612697

613698
@Override
614699
public String toString() {
615-
return "SpanBuilder{" + "begin=" + this.begin + ", end=" + this.end
616-
+ ", name=" + this.name + ", traceId=" + this.traceId + ", parents="
617-
+ this.parents + ", spanId=" + this.spanId + ", remote=" + this.remote
618-
+ ", exportable=" + this.exportable + ", processId='" + this.processId
619-
+ '\'' + ", savedSpan=" + this.savedSpan + ", logs=" + this.logs
620-
+ ", tags=" + this.tags + '}';
700+
return new Span(this).toString();
621701
}
622702
}
623703
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,14 @@ private Span extractSpanFromNewHeaders(Message<?> carrier, SpanBuilder spanBuild
7676
private Span extractSpanFromHeaders(Message<?> carrier, SpanBuilder spanBuilder,
7777
String traceIdHeader, String spanIdHeader, String spanSampledHeader,
7878
String spanProcessIdHeader, String spanNameHeader, String spanParentIdHeader) {
79-
long traceId = Span
80-
.hexToId(getHeader(carrier, traceIdHeader));
79+
String traceId = getHeader(carrier, traceIdHeader);
80+
spanBuilder.traceIdHigh(traceId.length() == 32 ? Span.hexToId(traceId, 0) : 0);
81+
spanBuilder.traceId(Span.hexToId(traceId));
82+
8183
long spanId = hasHeader(carrier, spanIdHeader)
8284
? Span.hexToId(getHeader(carrier, spanIdHeader))
8385
: this.random.nextLong();
84-
spanBuilder = spanBuilder.traceId(traceId).spanId(spanId);
86+
spanBuilder = spanBuilder.spanId(spanId);
8587
spanBuilder.exportable(
8688
Span.SPAN_SAMPLED.equals(getHeader(carrier, spanSampledHeader)));
8789
String processId = getHeader(carrier, spanProcessIdHeader);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ private void addHeaders(Span span, Message<?> initialMessage,
9595
MessageHeaderAccessor accessor, Map<String, String> headers, String traceIdHeader,
9696
String spanIdHeader, String parentIdHeader, String spanNameHeader, String processIdHeader,
9797
String spanSampledHeader, String spanHeader) {
98-
addHeader(headers, traceIdHeader, Span.idToHex(span.getTraceId()));
98+
addHeader(headers, traceIdHeader, span.traceIdString());
9999
addHeader(headers, spanIdHeader, Span.idToHex(span.getSpanId()));
100100
if (span.isExportable()) {
101101
addAnnotations(this.traceKeys, initialMessage, span);

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

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,32 +58,33 @@ public Span joinTrace(HttpServletRequest carrier) {
5858
String uri = this.urlPathHelper.getPathWithinApplication(carrier);
5959
boolean skip = this.skipPattern.matcher(uri).matches()
6060
|| Span.SPAN_NOT_SAMPLED.equals(carrier.getHeader(Span.SAMPLED_NAME));
61-
long traceId = Span
62-
.hexToId(carrier.getHeader(Span.TRACE_ID_NAME));
63-
long spanId = spanId(carrier, traceId);
64-
return buildParentSpan(carrier, uri, skip, traceId, spanId);
61+
long spanId = spanId(carrier);
62+
return buildParentSpan(carrier, uri, skip, spanId);
6563
} catch (Exception e) {
6664
log.error("Exception occurred while trying to extract span from carrier", e);
6765
return null;
6866
}
6967
}
7068

71-
private long spanId(HttpServletRequest carrier, long traceId) {
69+
private long spanId(HttpServletRequest carrier) {
7270
String spanId = carrier.getHeader(Span.SPAN_ID_NAME);
7371
if (spanId == null) {
7472
if (log.isDebugEnabled()) {
7573
log.debug("Request is missing a span id but it has a trace id. We'll assume that this is "
76-
+ "a root span with span id equal to trace id");
74+
+ "a root span with span id equal to the lower 64-bits of the trace id");
7775
}
78-
return traceId;
76+
return Span.hexToId(carrier.getHeader(Span.TRACE_ID_NAME));
7977
} else {
8078
return Span.hexToId(spanId);
8179
}
8280
}
8381

84-
private Span buildParentSpan(HttpServletRequest carrier, String uri, boolean skip,
85-
long traceId, long spanId) {
86-
SpanBuilder span = Span.builder().traceId(traceId).spanId(spanId);
82+
private Span buildParentSpan(HttpServletRequest carrier, String uri, boolean skip, long spanId) {
83+
String traceId = carrier.getHeader(Span.TRACE_ID_NAME);
84+
SpanBuilder span = Span.builder()
85+
.traceIdHigh(traceId.length() == 32 ? Span.hexToId(traceId, 0) : 0)
86+
.traceId(Span.hexToId(traceId))
87+
.spanId(spanId);
8788
String processId = carrier.getHeader(Span.PROCESS_ID_NAME);
8889
String parentName = carrier.getHeader(Span.SPAN_NAME_NAME);
8990
if (StringUtils.hasText(parentName)) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class HttpRequestInjector implements SpanInjector<HttpRequest> {
3232

3333
@Override
3434
public void inject(Span span, HttpRequest carrier) {
35-
setIdHeader(carrier, Span.TRACE_ID_NAME, span.getTraceId());
35+
setHeader(carrier, Span.TRACE_ID_NAME, span.traceIdString());
3636
setIdHeader(carrier, Span.SPAN_ID_NAME, span.getSpanId());
3737
setHeader(carrier, Span.SAMPLED_NAME, span.isExportable() ? Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED);
3838
setHeader(carrier, Span.SPAN_NAME_NAME, span.getName());

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public void inject(Span span, RequestContext carrier) {
4141
return;
4242
}
4343
setHeader(requestHeaders, Span.SPAN_ID_NAME, span.getSpanId());
44-
setHeader(requestHeaders, Span.TRACE_ID_NAME, span.getTraceId());
44+
setHeader(requestHeaders, Span.TRACE_ID_NAME, span.traceIdString());
4545
setHeader(requestHeaders, Span.SPAN_NAME_NAME, span.getName());
4646
setHeader(requestHeaders, Span.SAMPLED_NAME, span.isExportable() ?
4747
Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED);

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)