Implementing Custom traceId with extract + TextMapAdapter¶
Author: 刘锐
Introduction¶
In certain scenarios, you need to implement a custom traceId programmatically.
The approach: Use tracer.extract to construct a SpanContext, then use that SpanContext as the parent context by passing it to asChildOf(SpanContext) to create the current span.
How traceId Parameters Are Defined¶
When constructing a SpanContext via tracer.extract, the internal ContextInterpreter parses the headers and extracts the corresponding traceId and spanId. Part of the ContextInterpreter implementation is shown below.
Propagators¶
ddtrace supports several propagation protocols, and the parameter names for traceId differ across protocols.
For Java, ddtrace supports two propagation protocols:
- Datadog: Default propagation protocol
- B3: B3 propagation is a specification for headers
b3and headers prefixed withx-b3-. These headers are used to propagate tracing context across service boundaries. B3 has two modes:- B3SINGLE (B3_SINGLE_HEADER): the header key is
b3 - B3 (B3MULTI): the header key is
x-b3-
- B3SINGLE (B3_SINGLE_HEADER): the header key is
Implementing Custom traceId with the Datadog Propagator¶
Enabling the Datadog Propagator¶
Mechanism Source Code Overview¶
ddtrace uses Datadog as the default propagation protocol, with the interceptor DatadogContextInterpreter. Part of its code is as follows:
public boolean accept(String key, String value) {
case 'x':
if ("x-datadog-trace-id".equalsIgnoreCase(key)) {
classification = 0;
} else if ("x-datadog-parent-id".equalsIgnoreCase(key)) {
classification = 1;
} else if ("x-datadog-sampling-priority".equalsIgnoreCase(key)) {
classification = 3;
} else if ("x-datadog-origin".equalsIgnoreCase(key)) {
classification = 2;
....
switch(classification) {
case 0:
this.traceId = DDId.from(firstValue);
break;
case 1:
this.spanId = DDId.from(firstValue);
break;
case 2:
this.origin = firstValue;
break;
case 3:
this.samplingPriority = Integer.parseInt(firstValue);
break;
....
}
Code Implementation¶
/***
* Custom traceId related information to implement custom trace
* @param traceId
* @param parentId
* @param treeLength
* @return
*/
@GetMapping("/customTrace")
@ResponseBody
public String customTrace(String traceId, String parentId, Integer treeLength) {
Tracer tracer = GlobalTracer.get();
traceId = StringUtils.isEmpty(traceId) ? IdGenerationStrategy.RANDOM.generate().toString() : traceId;
parentId = StringUtils.isEmpty(parentId) ? DDId.ZERO.toString() : parentId;
treeLength = treeLength == null ? 3 : treeLength;
for (int i = 0; i < treeLength; i++) {
Map<String, String> data = new HashMap<>();
data.put("x-datadog-trace-id", traceId);
data.put("x-datadog-parent-id", parentId);
SpanContext extractedContext = tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(data));
Span serverSpan = tracer.buildSpan("opt" + i)
.withTag("service_name", "someService" + i)
.asChildOf(extractedContext)
.start();
tracer.activateSpan(serverSpan).close();
serverSpan.finish();
parentId = serverSpan.context().toSpanId();
}
return "build success!";
}
Implementing Custom traceId with the B3 Propagator¶
B3 has two encoding formats: Single Header and Multiple Header.
- Multiple Header encoding uses individual headers prefixed with
X-B3-for each item in the trace context. - Single Header encoding combines the context into a single header named
b3. When extracting fields, the single header variant takes precedence over the multiple header variant.
Here is an example flow using Multiple Header encoding, assuming an HTTP request carries the propagated trace:
Enabling the B3 Propagator¶
Mechanism Source Code Overview¶
public boolean accept(String key, String value) {
...
char first = Character.toLowerCase(key.charAt(0));
switch (first) {
case 'f':
if (this.handledForwarding(key, value)) {
return true;
}
break;
case 'u':
if (this.handledUserAgent(key, value)) {
return true;
}
break;
case 'x':
if ((this.traceId == null || this.traceId == DDId.ZERO) && "X-B3-TraceId".equalsIgnoreCase(key)) {
classification = 0;
} else if ((this.spanId == null || this.spanId == DDId.ZERO) && "X-B3-SpanId".equalsIgnoreCase(key)) {
classification = 1;
} else if (this.samplingPriority == this.defaultSamplingPriority() && "X-B3-Sampled".equalsIgnoreCase(key)) {
classification = 3;
} else if (this.handledXForwarding(key, value)) {
return true;
}
}
...
String firstValue = HttpCodec.firstHeaderValue(value);
if (null != firstValue) {
switch (classification) {
case 0:
if (this.setTraceId(firstValue)) {
return true;
}
break;
case 1:
this.setSpanId(firstValue);
break;
case 2:
String mappedKey = (String)this.taggedHeaders.get(lowerCaseKey);
if (null != mappedKey) {
if (this.tags.isEmpty()) {
this.tags = new TreeMap();
}
this.tags.put(mappedKey, HttpCodec.decode(firstValue));
}
break;
case 3:
this.samplingPriority = this.convertSamplingPriority(firstValue);
break;
case 4:
if (this.extractB3(firstValue)) {
return true;
}
}
}
...
The following method handles the Single Header format:
private boolean extractB3(String firstValue) {
if (firstValue.length() == 1) {
this.samplingPriority = this.convertSamplingPriority(firstValue);
} else {
int firstIndex = firstValue.indexOf("-");
int secondIndex = firstValue.indexOf("-", firstIndex + 1);
String b3SpanId;
if (firstIndex != -1) {
b3SpanId = firstValue.substring(0, firstIndex);
if (this.setTraceId(b3SpanId)) {
return true;
}
}
if (secondIndex == -1) {
b3SpanId = firstValue.substring(firstIndex + 1);
this.setSpanId(b3SpanId);
} else {
b3SpanId = firstValue.substring(firstIndex + 1, secondIndex);
this.setSpanId(b3SpanId);
String b3SamplingId = firstValue.substring(secondIndex + 1);
this.samplingPriority = this.convertSamplingPriority(b3SamplingId);
}
}
return false;
}
Multiple Header Code Implementation¶
private static void b3TraceByMultiple(){
String traceId = DDId.from("6917954032704516265").toHexStringOrOriginal();
Tracer tracer = GlobalTracer.get();
String parentId = DDId.from("4025816492133344807").toHexStringOrOriginal();
for (int i = 0; i < 3; i++) {
Map<String, String> data = new HashMap<>();
data.put("X-B3-TraceId", traceId);
data.put("X-B3-SpanId", parentId);
SpanContext extractedContext = tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(data));
Span serverSpan = tracer.buildSpan("opt"+i)
.withTag("service","someService"+i)
.asChildOf(extractedContext)
.start();
serverSpan.setTag("code","200");
tracer.activateSpan(serverSpan).close();
serverSpan.finish();
parentId = DDId.from(serverSpan.context().toSpanId()).toHexStringOrOriginal();
System.out.println( traceId+"\t"+serverSpan.context().toTraceId()+"\t"+parentId);
}
}
Note: Multiple Header requires passing two headers:
X-B3-TraceIdandX-B3-SpanId. According to the interceptor analysis, the headers are case-insensitive.
6001828a33d570a9 6917954032704516265 58c4b35f113ee353
6001828a33d570a9 6917954032704516265 330359b7aaea9d6b
6001828a33d570a9 6917954032704516265 1ac0dcd332f9262f
Single Header Code Implementation¶
private static void b3TraceBySingle(){
String traceId = DDId.from("6917954032704516265").toHexStringOrOriginal();
Tracer tracer = GlobalTracer.get();
String parentId = DDId.from("4025816492133344807").toHexStringOrOriginal();
for (int i = 0; i < 3; i++) {
String b3 = traceId+ "-"+parentId+"-1";
Map<String, String> data = new HashMap<>();
data.put("b3",b3);
SpanContext extractedContext = tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(data));
Span serverSpan = tracer.buildSpan("opt"+i)
.withTag("service","someService"+i)
.asChildOf(extractedContext)
.start();
serverSpan.setTag("code","200");
tracer.activateSpan(serverSpan).close();
serverSpan.finish();
parentId = DDId.from(serverSpan.context().toSpanId()).toHexStringOrOriginal();
System.out.println( traceId+"\t"+serverSpan.context().toTraceId()+"\t"+parentId);
System.out.println("b3="+b3);
}
}
6001828a33d570a9 6917954032704516265 308287d022272ed9
b3=6001828a33d570a9-37de92c518846627-1
6001828a33d570a9 6917954032704516265 5e6fbaad91daef5c
b3=6001828a33d570a9-308287d022272ed9-1
6001828a33d570a9 6917954032704516265 2cfbc225bddf5e6d
b3=6001828a33d570a9-5e6fbaad91daef5c-1
Note: Single Header only requires passing the header
b3, with the formattraceId-parentId-Sampled.
Enabling Multiple Propagators¶
Choose either of the following two methods:
- System Property:
- Environment Variable:
