|
| 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 document 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 frontend. |
| 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 disallow direct mutation (without setters) of span properties such as the span name, depending on the platform 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 | +``` |
0 commit comments