Skip to content

Commit a6f1f9e

Browse files
authored
docs(span-links): Add develop docs for span links (#12829)
* docs(span-links): Add develop docs for span links * review suggestions
1 parent 9f0f8e7 commit a6f1f9e

File tree

3 files changed

+217
-0
lines changed

3 files changed

+217
-0
lines changed
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
---
2+
title: Span Links
3+
---
4+
5+
Span links associate one span with one or more other spans. The most important application of linking traces are frontend applications.
6+
It can also be useful in settings with producer/consumer patterns or batch operations.
7+
By using span links, we are able to display a user journey in our tracing views to make debugging of issues easier as developers get more context on what happened before a specific issue.
8+
9+
A span link can store the context of (a.k.a. link to) any other span. When necessary, a special link type can be added (see [Link Types](#link-types)).
10+
11+
Learn more about Span Links in [the RFC 141](https://github.com/getsentry/rfcs/blob/main/text/0141-linking-traces.md) or in the [OpenTelemetry docs](https://opentelemetry.io/docs/concepts/signals/traces/#span-links).
12+
13+
## SDK Implementation Guideline
14+
15+
- Links are added on a span level, as defined and specified by [OpenTelemetry](https://opentelemetry.io/docs/specs/otel/trace/api/#link).
16+
- In addition to linking to span IDs, a span link also holds meta information about the link, collected via attributes.
17+
- Span links MUST only link to other spans. We do not support linking a span to an error or other Sentry events.
18+
- For SDKs still having public APIs around transactions, their respective Transaction interface and `startTransaction` function(s) should support the same APIs.
19+
20+
### Type Definitions
21+
22+
SDKs should follow the OpenTelemetry spec for the Link interface as defined by the platform.
23+
Non-OTel SDKs should orient themselves on OTel, resulting in the interface below, or a related version that applies to the terminology and philosophy of the respective SDK:
24+
25+
```ts {tabTitle:Types}
26+
// see https://github.com/open-telemetry/opentelemetry-js/blob/main/api/src/trace/link.ts
27+
// or interface of respective platform
28+
interface Link {
29+
// contains the SpanContext of the span to link to
30+
context: SpanContext;
31+
// key-value pair with primitive values
32+
attributes?: Attributes;
33+
}
34+
35+
36+
// see https://github.com/open-telemetry/opentelemetry-js/blob/main/api/src/trace/span_context.ts
37+
// or interface of respective platform
38+
interface SpanContext {
39+
traceId: string,
40+
spanId: string,
41+
traceFlags: number,
42+
}
43+
44+
type Attributes = Record<string, AttributeValues>
45+
type AttributeValues = string | boolean | number | Array<string> | Array<boolean> | Array<number>
46+
```
47+
48+
49+
### Required Span API
50+
51+
Ideally, the link is added when starting a span. An optional `links` attribute is added to the `startSpan` options.
52+
SDKs that don't offer span-centric APIs but e.g. transaction APIs, should ensure that their respective `start...` APIs (e.g. `startTransaction`) also offer a possibility to add links.
53+
54+
```ts {tabTitle:Span Options}
55+
function startSpan(options: StartSpanOptions);
56+
57+
interface StartSpanOptions: {
58+
//... other options (name, attributes, etc)
59+
links?: Link[];
60+
}
61+
```
62+
63+
Furthermore, the SDKs need to expose at least an `addLink` method on their respective Span interface. For completeness with OpenTelemetry, ideally they also expose `addLinks`:
64+
65+
```ts {tabTitle:Span API}
66+
interface Span {
67+
// return value can differ depending on platform
68+
addLink(link: Link): this;
69+
addLinks(links: Link[]): this;
70+
}
71+
```
72+
73+
## Envelope Item Payload
74+
75+
Span trees are serialized to transaction event envelopes in all Sentry SDKs. Therefore, the envelope item needs to accommodate span links
76+
in its payload. If the `links` entry is an empty array, it can be omitted from the envelope.
77+
78+
The serialized `links` objects should always contain:
79+
80+
- `span_id: string` - id of the span to link to
81+
- `trace_id: string` - trace id of the span to link to
82+
- `sampled: boolean` - required if sampling decision of the span to link to (corresponds to `traceFlags` in Otel span context converted to `boolean`) is available
83+
- `attributes:` - required if attributes were added to the link
84+
85+
Optionally, the serialized link object can contain further fields from OTel like `traceState`, `isRemote` or `droppedAttributesCount` which will be just forwarded unless we find a use case for them.
86+
87+
The OTel fields `spanId`, `traceId`, and `traceFlags` should be excluded from the links objects in the envelope to avoid duplicate data (e.g. `trace_id` vs. `traceId`).
88+
89+
If the root span (previously known as transaction) has span links, the links are stored in the trace context.
90+
91+
```ts {tabTitle:Example Trace Context}
92+
// event envelope item
93+
{
94+
type: "transaction";
95+
transaction: string;
96+
contexts: {
97+
trace: {
98+
span_id: string;
99+
parent_span_id: string;
100+
trace_id: string;
101+
// new field for links:
102+
links?: Array<{
103+
"span_id": string,
104+
"trace_id": string,
105+
sampled?: boolean, // traceFlags from Otel converted to boolean
106+
attributes?: Record<string, AttributeValue>,
107+
// + potentially more fields 1:1 from Otel. e.g. (traceState, droppedAttributesCount, isRemote)
108+
}>
109+
// ...
110+
}
111+
}
112+
// ...
113+
}
114+
```
115+
116+
Links that are stored in child spans are serialized in `spans[i].links`.
117+
118+
```ts {tabTitle:Example Span Link Data}
119+
// event envelope item
120+
{
121+
type: "transaction";
122+
transaction: string;
123+
spans: Array<{
124+
span_id: string;
125+
parent_span_id: string;
126+
trace_id: string;
127+
// new field for links:
128+
links?: Array<{
129+
"span_id": string,
130+
"trace_id": string,
131+
sampled?: boolean,
132+
attributes?: Record<string, AttributeValue>,
133+
}>
134+
// ...
135+
}>
136+
// ...
137+
}
138+
```
139+
140+
## Link Types
141+
142+
Links don't require a special meaning or type but if necessary (e.g. for identifying special links in the product), set the `sentry.link.type` attribute on the link to define the link type.
143+
Any string can be used, but these types have predefined meanings:
144+
145+
| `sentry.link.type` | Usage | UI Implications |
146+
|--------------------|----------------------------------------------------------------|---------------------------------------------------------------------------------------------------|
147+
| "previous_trace" | Linking e.g. a navigation span to the previous page load span. | Used to query linked traces. Shows a button to go to linked previous trace in the trace explorer. |
148+
| "next_trace" | Linking e.g. a page load span to the next navigation span. | Used to query linked traces. Shows a button to go to linked next trace in the trace explorer. |
149+
150+
### `previous_trace`
151+
152+
Frontend traces can be linked by adding span links between root spans. For example, a navigation span includes a link to the previous page load span.
153+
154+
The span context inside the link object should be stored in a storage mechanism of choice (e.g. in-memory or `sessionStorage` in the browser).
155+
156+
Therefore, on root span start:
157+
- Check if there is a previous span context stored. If yes, add the span link with the `'sentry.link.type': 'previous_trace'` attribute
158+
- Store the root span context as the previous root span in a storage mechanism of choice (e.g. in-memory or `sessionStorage` in the browser)
159+
160+
SDKs are free to implement heuristics around how long a previous trace span context should be considered (max time) and store additional necessary data.
161+
162+
![](./span-links-previous-trace-100.png)
163+
164+
#### Negatively Sampled Traces
165+
166+
In many cases, with lower sample rates, we will not be able to provide a full trace link chain, due to some or many traces being negatively sampled.
167+
168+
- Sampled root spans should still include a link to the previous, negatively sampled root span (`traceFlags` on the spanContext() carry
169+
the information that the previous trace root span was negatively sampled).
170+
171+
This helps our product to hint that there would have been a previous trace, but it was negatively sampled.
172+
173+
We will not link to the previous positively sampled trace if a negatively sampled trace is in-between (see Traces 2-4 in the diagram below).
174+
Furthermore, we will not show how many traces were negatively sampled in between two trace chains; only that there was at least one trace in between (see Trace 5-8).
175+
176+
![](./span-links-previous-trace-negative-sample.png)
177+
178+
## Usage Example
179+
180+
Adding span links should be possible at span start time, as well as when holding a reference to the span.
181+
182+
In the example below, by adding span links, we can link from the last navigation trace all the way back to the initial pageload trace.
183+
By passing the `'sentry.link.type': 'previous_trace'` attribute, we can identify the link as a previous trace link in Sentry and display the spans accordingly.
184+
185+
```ts {tabTitle:TypeScript}
186+
// 1st trace starts
187+
const pageloadSpan = startInactiveSpan(...)
188+
189+
// 2nd trace starts
190+
const navigation1Span = startInactiveSpan({
191+
name: '/users',
192+
links: [{
193+
context: pageloadSpan.spanContext(),
194+
attributes: {
195+
'sentry.link.type': 'previous_trace'
196+
}
197+
}]
198+
});
199+
200+
// 3rd trace starts
201+
const navigation2Span = startSpan({name: '/users/:id'}, (span) => {
202+
span.addLink({
203+
context: navigation1Span.spanContext(),
204+
attributes: {
205+
'sentry.link.type': 'previous_trace'
206+
}
207+
})
208+
})
209+
```
210+
211+
## Ingest/Relay
212+
213+
Relay forwards the span links in the format that is required for further processing and storage.
214+
Importantly, Relay doesn't require span links to be defined. They are completely optional.
215+
Relay handles passing span links in the root span as well as in any child span (see [envelope item payload](#envelope-item-payload)).
216+
217+
The expected type and structure of the links array and its contents is [specified in Relay](https://github.com/getsentry/relay/blob/master/relay-event-schema/src/protocol/span.rs#L753).

0 commit comments

Comments
 (0)