Skip to content

Commit 04c324b

Browse files
cleptricLms24
andauthored
docs(sdks): New Span API (#11939)
Co-authored-by: Lukas Stracke <[email protected]>
1 parent 402483b commit 04c324b

File tree

2 files changed

+171
-0
lines changed

2 files changed

+171
-0
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
---
2+
title: Span API
3+
---
4+
5+
<Alert level="info">
6+
This document uses key words such as "MUST", "SHOULD", and "MAY" as defined in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) to indicate requirement levels.
7+
</Alert>
8+
9+
<Alert level="info">
10+
The APIs specified in this documents MUST be implemented by all SDKs that don't use OpenTelemetry as their underlying tracing implementation.
11+
SDKs using OTel SHOULD follow their own already established span APIs but MAY orient themselves on this document if applicable.
12+
</Alert>
13+
14+
Spans are measuring the duration of certain operations in an application.
15+
16+
The topmost member of a (distributed) span tree is called the "Root Span".
17+
This span has no parent span and groups together its children with a representative name for the entire operation, such as `GET /` in case of a request to a backend application.
18+
19+
The topmost span within a service boundary is called the "Segment Span".
20+
Segment spans have a `parent_span_id` pointing to a "remote" span from the parent service.
21+
22+
For example, a distributed trace from backend to frontend, would have a segment span for the backend, and a segment span for the frotnend.
23+
The frontend segment span is also the root span of the entire span tree.
24+
25+
SDKs MUST NOT expose names like "segment span" (e.g. in APIs) to users and SHOULD NOT (read "avoid") exposing "root span" if possible.
26+
27+
## Span Interface
28+
29+
SDKs' span implementations MUST at minimum implement the following span interface.
30+
31+
```ts
32+
interface Span {
33+
private _spanId: string;
34+
35+
end(endTimestamp?: SpanTimeInput): void;
36+
37+
setAttribute(key: string, value: SpanAttributeValue | undefined): this;
38+
setAttributes(attributes: SpanAttributes): this;
39+
40+
setStatus(status: 'ok' | 'error'): this;
41+
42+
setName(name: string): this;
43+
44+
addLink(link: SpanLink): this;
45+
addLinks(links: SpanLink[]): this;
46+
47+
getName(): string;
48+
getAttributes(): Record<string, SpanAttributeValue>
49+
}
50+
```
51+
52+
When implementing the span interface, consider the following guidelines:
53+
54+
- SDKs MAY implement additional APIs, such as getters/setters for properties (e.g. `span.getStatus()`), or additional methods for convenience (e.g. `Span::spanContext()`).
55+
- SDK implementers SHOULD dissalow direct mutation (without setters) of span properties such as the span name, depending on the plaform and the challenges involved.
56+
- SDK implementers MAY disallow direct read access to span properties, depending on the platform and the challenges involved.
57+
58+
## Span Starting APIs
59+
60+
SDKs MUST expose at least one API to start a span. SDKs MAY expose additional APIs, depending on the platform, language conventions and requirements.
61+
62+
### Default `startSpan` API
63+
64+
SDKs MUST expose a default `startSpan` API that takes options and returns a span:
65+
66+
```ts
67+
function startSpan(options: StartSpanOptions): Span;
68+
69+
interface StartSpanOptions {
70+
name: string;
71+
attributes?: Record<string, SpanAttributeValue>;
72+
parentSpan?: Span | null;
73+
active?: boolean;
74+
}
75+
```
76+
77+
SDKs MUST allow specifying the following options to be passed to `startSpan`:
78+
79+
| Option | Required | Description |
80+
|---------------|----------|----------------------------------------------|
81+
| `name` | Yes | The name of the span. MUST be set by users |
82+
| `attributes` | No | Attributes to attach to the span. |
83+
| `parentSpan` | No | The parent span. See description below for implications of allowed values |
84+
| `active` | No | Whether the started span should be _active_ (i.e. if spans started while this span is active should become children of the started span). |
85+
86+
Behaviour:
87+
- Spans MUST be started as active by default. This means that any span started, while the initial span is active, MUST be attached as a child span of the active span.
88+
- Only if users set `active: false`, the span will be started as inactive, meaning spans started while this span is not yet ended, will not become children, but siblings of the started span.
89+
- If a `Span` is passed via `parentSpan`, the span will be started as the child of the passed parent span. This has precedence over the currently active span.
90+
- If `null` is passed via `parentSpan`, the new span will be started as a root/segment span.
91+
- SDKs MUST NOT end the span automatically. This is the responsibility of the user.
92+
- `startSpan` MUST always return a span instance, even if the started span's trace is negatively sampled.
93+
94+
95+
### Additional Span Starting APIs
96+
97+
SDKs MAY expose additional span starting APIs or variants of `startSpan` that make sense for the platform.
98+
These could be decorators, annotations, or closure- or callback-based APIs.
99+
Additional APIs MAY e.g. end spans automatically (for example, when a callback terminates, the span is ended automatically).
100+
Likewise, additional APIs MAY also adjust the span status based on errors thrown.
101+
102+
### Explicitly creating a child span
103+
104+
At this time, SDKs MUST NOT expose APIs like `Span::startChild` or similar functionality that explicitly creates a child span.
105+
This is still TBD but the `parentSpan` option should suffice to serve this use case.
106+
107+
## Utility APIs
108+
109+
SDKs MAY expose additional utility APIs for users, or internal usage to access certain spans. For example,
110+
111+
- `Scope::getSpan()` - returns the currently active span.
112+
- `Scope::_INTERNAL_getSegmentSpan()` - returns the segment span of the currently active span (MUST NOT be documented for users)
113+
114+
## Example
115+
116+
```ts
117+
118+
const checkoutSpan = Sentry.startSpan({ name: 'on-checkout-click', attributes: { 'user.id': '123' } })
119+
120+
const validationSpan = Sentry.startSpan({ name: 'validate-shopping-cart'})
121+
startFormValidation().then((result) => {
122+
validationSpan.setAttribute('valid-form-data', result.success);
123+
processSpan.end();
124+
})
125+
126+
const processSpan = Sentry.startSpan({ name: 'process-order', parentSpan: checkoutSpan});
127+
processOrder().then((result) => {
128+
processSpan.setAttribute('order-processed', result.success);
129+
processSpan.end();
130+
}).catch((error) => {
131+
processSpan.setStatus('error');
132+
processSpan.setAttribute('error', error.message);
133+
processSpan.end();
134+
});
135+
136+
const unrelatedSpan = Sentry.startSpan({ name: 'log-order', parentSpan: null});
137+
logOrder()
138+
unrelatedSpan.end();
139+
140+
on('checkout-finished', ({ timestamp }) => {
141+
checkoutSpan.end(timestamp);
142+
})
143+
```
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
title: Span Trace Propagation
3+
---
4+
5+
<Alert level="info">
6+
This document uses key words such as "MUST", "SHOULD", and "MAY" as defined in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) to indicate requirement levels.
7+
</Alert>
8+
9+
## Continue an incoming trace
10+
11+
To continue a trace from an upstream service, the SDK must expose a method to extract the traceparent and baggage information and apply these to the applicable scope.
12+
13+
```js
14+
scope.setPropagationContext({
15+
traceparent: request.headers.SENTRY_TRACE,
16+
baggage: request.headers.SENTRY_BAGGAGE,
17+
})
18+
```
19+
Newly created root spans should now contain these properties, such as `trace_id` and `parent_span_id`.
20+
21+
## Continue an outgoing trace
22+
23+
To propagate a trace to a downstream service, the SDK must expose methods to fetch the required information to allow the next service to continue the trace.
24+
25+
```js
26+
traceparent = scope.getTraceparent()
27+
baggage = scope.getBaggage()
28+
```

0 commit comments

Comments
 (0)