-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
docs(sdks): New Span API #11939
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
docs(sdks): New Span API #11939
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| --- | ||
| title: Span API | ||
| --- | ||
|
|
||
| <Alert level="info"> | ||
| 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. | ||
| </Alert> | ||
|
|
||
| <Alert level="info"> | ||
| The APIs specified in this documents MUST be implemented by all SDKs that don't use OpenTelemetry as their underlying tracing implementation. | ||
| SDKs using OTel SHOULD follow their own already established span APIs but MAY orient themselves on this document if applicable. | ||
| </Alert> | ||
|
|
||
| Spans are measuring the duration of certain operations in an application. | ||
|
|
||
| The topmost member of a (distributed) 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. | ||
|
|
||
| The topmost span within a service boundary is called the "Segment Span". | ||
| Segment spans have a `parent_span_id` pointing to a "remote" span from the parent service. | ||
|
|
||
| For example, a distributed trace from backend to frontend, would have a segment span for the backend, and a segment span for the frotnend. | ||
| The frontend segment span is also the root span of the entire span tree. | ||
|
|
||
| SDKs MUST NOT expose names like "segment span" (e.g. in APIs) to users and SHOULD NOT (read "avoid") exposing "root span" if possible. | ||
|
|
||
| ## Span Interface | ||
|
|
||
| SDKs' span implementations MUST at minimum implement the following span interface. | ||
|
|
||
| ```ts | ||
| interface Span { | ||
| private _spanId: string; | ||
|
|
||
| end(endTimestamp?: SpanTimeInput): void; | ||
|
|
||
| setAttribute(key: string, value: SpanAttributeValue | undefined): this; | ||
| setAttributes(attributes: SpanAttributes): this; | ||
|
|
||
| setStatus(status: 'ok' | 'error'): this; | ||
|
|
||
| setName(name: string): this; | ||
|
|
||
| addLink(link: SpanLink): this; | ||
| addLinks(links: SpanLink[]): this; | ||
|
|
||
| getName(): string; | ||
| getAttributes(): Record<string, SpanAttributeValue> | ||
| } | ||
| ``` | ||
|
|
||
| When implementing the span interface, consider the following guidelines: | ||
|
|
||
| - SDKs MAY implement additional APIs, such as getters/setters for properties (e.g. `span.getStatus()`), or additional methods for convenience (e.g. `Span::spanContext()`). | ||
| - SDK implementers SHOULD dissalow direct mutation (without setters) of span properties such as the span name, depending on the plaform and the challenges involved. | ||
| - SDK implementers MAY disallow direct read access to span properties, depending on the platform and the challenges involved. | ||
|
|
||
| ## Span Starting APIs | ||
|
|
||
| SDKs MUST expose at least one API to start a span. SDKs MAY expose additional APIs, depending on the platform, language conventions and requirements. | ||
|
|
||
| ### Default `startSpan` API | ||
|
|
||
| SDKs MUST expose a default `startSpan` API that takes options and returns a span: | ||
|
|
||
| ```ts | ||
| function startSpan(options: StartSpanOptions): Span; | ||
|
|
||
| interface StartSpanOptions { | ||
| name: string; | ||
| attributes?: Record<string, SpanAttributeValue>; | ||
| parentSpan?: Span | null; | ||
| active?: boolean; | ||
| } | ||
| ``` | ||
|
|
||
| SDKs MUST allow specifying the following options to be passed to `startSpan`: | ||
|
|
||
| | Option | Required | Description | | ||
| |---------------|----------|----------------------------------------------| | ||
| | `name` | Yes | The name of the span. MUST be set by users | | ||
| | `attributes` | No | Attributes to attach to the span. | | ||
| | `parentSpan` | No | The parent span. See description below for implications of allowed values | | ||
| | `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). | | ||
|
|
||
| Behaviour: | ||
| - 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. | ||
| - 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. | ||
| - 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. | ||
| - If `null` is passed via `parentSpan`, the new span will be started as a root/segment span. | ||
| - SDKs MUST NOT end the span automatically. This is the responsibility of the user. | ||
| - `startSpan` MUST always return a span instance, even if the started span's trace is negatively sampled. | ||
|
|
||
|
|
||
| ### Additional Span Starting APIs | ||
|
|
||
| SDKs MAY expose additional span starting APIs or variants of `startSpan` that make sense for the platform. | ||
| These could be decorators, annotations, or closure- or callback-based APIs. | ||
| Additional APIs MAY e.g. end spans automatically (for example, when a callback terminates, the span is ended automatically). | ||
| Likewise, additional APIs MAY also adjust the span status based on errors thrown. | ||
|
|
||
| ### Explicitly creating a child span | ||
|
|
||
| At this time, SDKs MUST NOT expose APIs like `Span::startChild` or similar functionality that explicitly creates a child span. | ||
| This is still TBD but the `parentSpan` option should suffice to serve this use case. | ||
|
|
||
| ## Utility APIs | ||
|
|
||
| SDKs MAY expose additional utility APIs for users, or internal usage to access certain spans. For example, | ||
|
|
||
| - `Scope::getSpan()` - returns the currently active span. | ||
| - `Scope::_INTERNAL_getSegmentSpan()` - returns the segment span of the currently active span (MUST NOT be documented for users) | ||
|
|
||
| ## Example | ||
|
|
||
| ```ts | ||
|
|
||
| const checkoutSpan = Sentry.startSpan({ name: 'on-checkout-click', attributes: { 'user.id': '123' } }) | ||
|
|
||
| const validationSpan = Sentry.startSpan({ name: 'validate-shopping-cart'}) | ||
| startFormValidation().then((result) => { | ||
| validationSpan.setAttribute('valid-form-data', result.success); | ||
| processSpan.end(); | ||
| }) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| const processSpan = Sentry.startSpan({ name: 'process-order', parentSpan: checkoutSpan}); | ||
| processOrder().then((result) => { | ||
| processSpan.setAttribute('order-processed', result.success); | ||
| processSpan.end(); | ||
| }).catch((error) => { | ||
| processSpan.setStatus('error'); | ||
| processSpan.setAttribute('error', error.message); | ||
| processSpan.end(); | ||
| }); | ||
|
|
||
| const unrelatedSpan = Sentry.startSpan({ name: 'log-order', parentSpan: null}); | ||
| logOrder() | ||
| unrelatedSpan.end(); | ||
|
|
||
| on('checkout-finished', ({ timestamp }) => { | ||
| checkoutSpan.end(timestamp); | ||
| }) | ||
| ``` | ||
28 changes: 28 additions & 0 deletions
28
develop-docs/sdk/telemetry/spans/span-trace-propagation.mdx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| --- | ||
| title: Span Trace Propagation | ||
| --- | ||
|
|
||
| <Alert level="info"> | ||
| 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. | ||
| </Alert> | ||
|
|
||
| ## Continue an incoming trace | ||
|
|
||
| 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. | ||
|
|
||
| ```js | ||
| scope.setPropagationContext({ | ||
| traceparent: request.headers.SENTRY_TRACE, | ||
| baggage: request.headers.SENTRY_BAGGAGE, | ||
| }) | ||
| ``` | ||
| Newly created root spans should now contain these properties, such as `trace_id` and `parent_span_id`. | ||
|
|
||
| ## Continue an outgoing trace | ||
|
|
||
| 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. | ||
|
|
||
| ```js | ||
| traceparent = scope.getTraceparent() | ||
| baggage = scope.getBaggage() | ||
| ``` |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: The example code incorrectly manages span lifecycles, failing to end
validationSpanand prematurely callingprocessSpan.end()beforeprocessSpanis declared.Severity: HIGH | Confidence: 1.00
🔍 Detailed Analysis
The example code incorrectly manages span lifecycles. The
validationSpancreated on line 120 is never explicitly ended, leading to a resource leak. Additionally,processSpan.end()is called inside thestartFormValidation().then()callback, which is logically incorrect as this callback is associated withvalidationSpan. Furthermore,processSpanis referenced at line 123 before its declaration on line 126, which, while technically working due to hoisting, is semantically confusing and poor practice. This example contradicts the specification's requirement for explicit span ending.💡 Suggested Fix
Ensure
validationSpanis explicitly ended after its asynchronous operation completes. DeclareprocessSpanbefore it is referenced, and callprocessSpan.end()at the appropriate point in its lifecycle, not within thevalidationSpan's callback.🤖 Prompt for AI Agent
Did we get this right? 👍 / 👎 to inform future reviews.