Skip to content

Commit 485758a

Browse files
committed
update specification with new span API design
1 parent 9b542e5 commit 485758a

File tree

1 file changed

+116
-34
lines changed

1 file changed

+116
-34
lines changed

develop-docs/sdk/telemetry/spans/span-api.mdx

Lines changed: 116 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,59 +3,141 @@ title: Span API
33
---
44

55
<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.
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.
712
</Alert>
813

914
Spans are measuring the duration of certain operations in an application.
10-
The topmost member of a span tree is called the root span. 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.
1115

12-
## Creating a root span
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.
1318

14-
The SDK must expose a method for creating a root span. The user must be able to set certain properties on this root span, such as its name, the type of operation (`op`) and others.
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.
1521

16-
```js
17-
span = sentry.tracing.startSpan()
18-
->setName('GET /')
19-
->setOp('http.server')
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.
2024

21-
span.end()
22-
```
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.
2326

24-
## Creating nested spans
27+
## Span Interface
2528

26-
To create nested spans, the SDK must expose an explicit way for a user to perform this task.
29+
SDKs' span implementations MUST at minimum implement the following span interface.
2730

28-
Additionally, the SDK may expose alternative APIs to create nested spans, such as allowing a user to wrap an operation into a callback or apply a decorator to certain blocks. These alternative APIs must never create a root span and no-op if no parent span is present.
29-
30-
```js
31-
childSpan = span.startChild()
32-
->setName('authentication middleware')
33-
->setOp('middleware.handle')
31+
```ts
32+
interface Span {
33+
private _spanId: string;
3434

35-
childSpan.end()
36-
```
35+
end(endTimestamp?: SpanTimeInput): void;
36+
37+
setAttribute(key: string, value: SpanAttributeValue | undefined): this;
38+
setAttributes(attributes: SpanAttributes): this;
3739

38-
## Setting the span status
40+
setStatus(status: 'ok' | 'error'): this;
3941

40-
A span has two statuses, `ok` and `error`. By default, the status of a span is set to `ok`.
41-
The SDK must allow a user to modify the status of a span.
42+
setName(name: string): this;
4243

43-
```js
44-
span.setStatus('error')
44+
addLink(link: SpanLink): this;
45+
addLinks(links: SpanLink[]): this;
46+
47+
getName(): string;
48+
getAttributes(): Record<string, SpanAttributeValue>
49+
}
4550
```
4651

47-
## Setting span attributes
52+
When implementing the span interface, consider the following guidelines:
4853

49-
The SDK must expose a method to allow a user to set data attributes onto a span.
50-
These attributes should use pre-defined keys whenever possible.
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.
5157

52-
```js
53-
span.setAttribute(SpanAttributes.HTTP_METHOD, 'GET')
54-
span.setAttribute(SpanAttributes.HTTP_RESPONSE_STATUS_CODE, 200)
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+
}
5575
```
5676

57-
## Additional, optional span APIs
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+
});
58135

59-
`span.setStartTimestamp()` - overwrite the span's start time
136+
const unrelatedSpan = Sentry.startSpan({ name: 'log-order', parentSpan: null});
137+
logOrder()
138+
unrelatedSpan.end();
60139

61-
`span.setEndTimestamp()` - overwrites the span's end time
140+
on('checkout-finished', ({ timestamp }) => {
141+
checkoutSpan.end(timestamp);
142+
})
143+
```

0 commit comments

Comments
 (0)