Link
Link
A link describes a cross-relationship between spans in either the same or different trace. For example if a batch operation were performed, comprising of different traces or different processes, a link from one span to another can help correlate related spans.
Constituents
It consists of fields:
- TraceID
- SpanID
- Type which can either be
CHILD
,PARENT
orUNKNOWN
- Attributes
Uses and advisory
Linking spans is especially useful for RPCs that might cross trust boundaries. For example, if your API client libraries in your customers’ applications and in the wild make calls to your cloud, you could choose to create a span when the request hits your cloud’s frontend server but not show it to the user, or at least prevent the trace from the wild from being the parent span to your internal spans
Links can also be used in tracing streaming requests such as with gRPC where tracing a long-lived request doesn’t provide valuable insight but per-message streaming will provide information about the latencies. On receiving a streamed message, you’ll need to link the trace that started this message
Links can also be used in batching operations or with asynchronous processing whereby a single trace spawned work that gets performed later. A link is important to help find the source of the trace that started the long-lived operations
Please also take a look at specs/utils.HandlingUntrustedRequests
Source code samples
In these source code samples, we have two spans:
Name | Description |
---|---|
SpanA | Started from an untrusted boundary e.g. a client request to your cloud |
SpanB | Started from a trusted boundary e.g. from the frontend server of your service/cloud |
// SpanA started say on the client side
_, spanA := trace.StartSpan(context.Background(), "SpanA")
spanASC := spanA.SpanContext()
_, spanB := trace.StartSpan(context.Background(), "SpanB")
spanB.AddLink(trace.Link{
TraceID: spanASC.TraceID,
SpanID: spanASC.SpanID,
Type: trace.LinkTypeChild,
Attributes: map[string]interface{}{
"reason": "client-RPC unverified source",
},
})
// SpanA started say on the client side
// SpanB started on the server side
Scope s1 = tracer.spanBuilder("SpanA").startScopedSpan();
SpanContext spanASC = tracer.getCurrentSpan().getContext();
s1.close();
Scope s2 = tracer.spanBuilder("SpanB").startScopedSpan();
Map<String, AttributeValue> linkAttributes = new HashMap<>();
linkAttributes.put("reason", AttributeValue.stringAttribute("client-RPC unverified source"));
tracer.getCurrentSpan().addLink(
Link.fromSpanContext(spanASC, Link.Type.CHILD_LINKED_SPAN, linkAttributes));
s2.close();
# SpanA started say on the client side
# SpanB started on the server side
spanA = tracer.start_span(name='SpanA')
tracer.end_span()
spanB = tracer.start_span(name='SpanB')
linkAttributes = attributes.Attributes(reason="client-RPC unverified source")
spanB.add_link(Link(
trace_id=spanA.context_tracer.trace_id,
span_id=spanA.span_id,
attributes=linkAttributes
))
tracer.end_span()
let spanATraceId;
let spanASpanId;
// SpanA started say on the client side
tracer.startRootSpan({name: 'SpanA', samplingRate: 1.0}, spanA => {
spanATraceId = spanA.traceId;
spanASpanId = spanA.id;
// Do something with spanA
spanA.end();
});
// SpanB started on the server side
tracer.startRootSpan({name: 'SpanB', samplingRate: 1.0}, spanB => {
linkAttributes = {'reason': 'client-RPC unverified source'};
spanB.addLink(spanATraceId, spanASpanId, linkAttributes);
// Do something with spanB
spanB.end()
});
Visuals
When examined after being exported
References
Resource | URL |
---|---|
Handling untrusted requests | specs/utils.HandlingUntrustedRequests |
Data model | proto/trace/v1.Link |
Go API reference | Span.AddLink and Link |
Java API reference | Span.addLink and Link |
Python API reference | Span.add_link and Link |
Node.js API reference | Span.addLink and Link |