---
title: "Implementation Guidelines"
url: https://develop.sentry.dev/sdk/telemetry/spans/implementation/
---

# Implementation Guidelines

🚧 This document is work in progress. The steps and suggestions in this document primarily serve as a means to document what SDKs so far have been doing when implementing Span-First. This page also serves as a place to document (temporary) decisions, trade-offs, considerations, etc.

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.

This document provides guidelines for implementing Span-First in SDKs. This is purposefully NOT a full specification. For exact specifications, refer to the other pages under [Spans](https://develop.sentry.dev/sdk/telemetry/spans.md).

## [How To Approach Span-First in SDKs](https://develop.sentry.dev/sdk/telemetry/spans/implementation.md#how-to-approach-span-first-in-sdks)

If you're implementing Span-First (as a PoC) in your SDK, take an iterative approach in which you implement the functionality incrementally. Here's a rough suggestion for iterations.

1. Add the Span v2 Envelope (type), serialization logic and any utilities necessary to support sending a new envelope. See [Span Protocol](https://develop.sentry.dev/sdk/telemetry/spans/span-protocol.md) for more details.

2. Add the top-level `traceLifecycle` (or `trace_lifecycle`) SDK init option which controls if traces should be sent as transactions or as spans (v2).

   * The allowed values for this option MUST be `'static'` and `'stream'`.
   * By default, the SDK MUST send traces as transactions (`'static'`). Span-First MUST be an opt-in feature.
   * Continue with adding Span-First logic which MUST only be applied if `traceLifecycle` is set to `'stream'`.

3. As an initial PoC, leave your current transaction APIs in place and convert the transaction event to a v2 spans array to be sent in the new envelope.
   * At this point, you can already start sending spans in batches (i.e. in multiple envelopes) to send more than 1000 spans at once. The maximum number of spans per envelope MUST be limited to 1000 and an envelope MUST only contain spans from one trace (as the trace envelope header is shared).

4. If applicable to your SDK, add new Span APIs to start spans. See [Span API](https://develop.sentry.dev/sdk/telemetry/spans/span-api.md) for more details.

   * Most importantly, add the simplest possible `start_span` API that leaves much control to users.
   * Follow up with optional, more convenient APIs later.
   * This new API MUST only be used in conjunction with the new `traceLifecycle` option and therefore only emit new spans (no transactions).
   * This new API MUST NOT expose any old transaction properties or concepts like (`op`, `description`, `tags`, etc).
   * TBD: Some SDKs already have `startSpan` or similar APIs. The migration path is still TBD but a decision can be made at a later stage.

5. Implement the `captureSpan` [single-span processing pipeline](https://develop.sentry.dev/sdk/telemetry/spans/implementation.md#single-span-processing-pipeline)

   * Either reuse existing heuristics (e.g. flush when segment span ends) or build a simple span buffer to flush spans (e.g. similar to the existing buffers for logs or metrics).
   * Implementing the more complex [Telemetry Processor](https://develop.sentry.dev/sdk/foundations/processing/telemetry-processor.md) buffer and scheduler can happen at a later stage.

6. Achieve data parity with the existing transaction events.

   * Ensure that the data added by SDK integrations, event processors, etc. to transaction events is also added to the spans (see [Event Processors](https://develop.sentry.dev/sdk/telemetry/spans/implementation.md#replacing-event-processors)).
   * Most additional data MUST only be added to the segment span. See [Common Attributes](https://develop.sentry.dev/sdk/telemetry/spans/span-protocol.md#common-attribute-keys) for attributes that MUST be added to every span.
   * Mental model: All data our SDKs *automatically* add to a transaction, MUST also be added to the segment span.

7. Implement the span telemetry buffer for proper, weighted span flushing. See [Span Buffer](https://develop.sentry.dev/sdk/telemetry/spans/span-buffer.md) for more details.

8. (Optional) Depending on necessity, drop support for sending traces as transactions in the next major release. From this point on, the SDK will by default send spans (v2) only and therefore will no longer be compatible with current self-hosted Sentry installations.

## [Span APIs](https://develop.sentry.dev/sdk/telemetry/spans/implementation.md#span-apis)

To do: This section needs a few guidelines and implementation hints, including:

* languages having to deal with async context management

### [`parentSpan` option for `startSpan`](https://develop.sentry.dev/sdk/telemetry/spans/implementation.md#parentspan-option-for-startspan)

SDKs MUST expose a `parentSpan` option on the `startSpan` API which allows users to explicitly set the parent span of the new span.

The `parentSpan` parameter has three distinct states: `undefined`, `null` and a span instance. See the [Span API documentation](https://develop.sentry.dev/sdk/telemetry/spans/span-api.md) for the semantics.

For languages that do not support an `undefined` state, SDKs SHOULD model this three-state behavior using platform-appropriate mechanisms. Prefer solutions that preserve the semantic distinction between `undefined` and `null`, such as:

* method/constructor overloading (e.g., an overload without `parentSpan`, and another accepting `parentSpan: Span?`),
* a default sentinel value/object representing `undefined`,
* or other idiomatic platform mechanisms (e.g., enum types).

### [Edge Cases](https://develop.sentry.dev/sdk/telemetry/spans/implementation.md#edge-cases)

* If `parentSpan` references a span that has already ended, the SDK SHOULD still create the new span and send it as a child of `parentSpan`.
  * Handling and presentation of these relationships is deferred to downstream processing and the frontend/UI.

### [Setting a Span as Active](https://develop.sentry.dev/sdk/telemetry/spans/implementation.md#setting-a-span-as-active)

It MUST be possible to attach a span to a scope. In SDKs that implement the three-scope model, the span SHOULD be set on the current scope.

When a span is attached to a scope, a reference to the previous span MUST be stored. The previous span MUST be re-attached to the scope as soon as the currently active span ends.

If a span is started with `active: true`, it MUST be attached to the scope. If a span is started with `active: false`, it SHOULD NOT be attached to the scope so that any spans started while the span is still running don't become its children.

If a span is unsampled (for example, because it has a negative sampling decision or because it matches `ignore_spans`), a [Noop span is created](https://develop.sentry.dev/sdk/telemetry/spans/span-api.md#noop-span). If a Noop span would be a segment (i.e., there is no currently running active span, or the user explicitly promoted the span to a segment via `parentSpan: null`), it MAY be set on the scope so that its children easily inherit its negative sampling decision.

## [Single-Span Processing Pipeline](https://develop.sentry.dev/sdk/telemetry/spans/implementation.md#single-span-processing-pipeline)

SDKs MUST implement a `captureSpan` API that takes a single span once it ends, and then processes and enqueues it into the span buffer. In most cases, this API SHOULD be exposed as a method on the `Client`. SDKs (e.g. JS Browser) MAY chose a different location if necessary.

Here's a rough overview of what `captureSpan` should do in which order:

1. Accept any span that already ended (i.e. has an `end_timestamp`)
2. Obtain the current, isolation and global scopes and merge the scope data.
3. Apply [common span attributes](https://develop.sentry.dev/sdk/telemetry/spans/span-protocol.md#common-attribute-keys) from the client and the merged scope data to every span.
4. Apply [scope attributes](https://develop.sentry.dev/sdk/telemetry/scopes.md#setting-attributes) from the merged scope data to every span.
5. Apply `contexts` and `request` data from the merged scopes to the segment span only.
6. Apply any span processing hooks (i.e. [event processor replacements](https://develop.sentry.dev/sdk/telemetry/spans/implementation.md#replacing-event-processors)) to the span.
7. Apply the `before_send_span` callback to the span.
8. Enqueue the span into the span buffer.

The `captureSpan` pipeline MUST NOT

* drop any span
* buffer spans before enqueuing them
* modify span relationships

### [Span Filtering](https://develop.sentry.dev/sdk/telemetry/spans/implementation.md#span-filtering)

For details and specifications, see [Span Filtering](https://develop.sentry.dev/sdk/telemetry/spans/filtering.md).

We settled on `ignore_spans` being applied prior to span start. This means that the `captureSpan` pipeline doesn't have to handle filtering spans. However, there are some drawbacks with this approach, most prominently:

* Not being able to filter on span names or data that is added/updated post span start
* Not being able to filter entire segments (e.g. `http.server` segments for bot requests resulting in 404 errors)

We might revisit this, which could require changes to the single-span processing pipeline.

### [Replacing Event Processors](https://develop.sentry.dev/sdk/telemetry/spans/implementation.md#replacing-event-processors)

Given that spans no longer are events (as opposed to transactions), they don't go through SDK event processors, which are extensively used throughout the SDKs (clients, integrations) but also by users. Instead, we defined replacement APIs for users and SDK-internal use cases.

For user-facing migration, we should aim to solve every use case with [`ignore_spans`](https://develop.sentry.dev/sdk/telemetry/spans/filtering.md#filter-with-ignorespans) for filtering and [`before_send_span`](https://develop.sentry.dev/sdk/telemetry/spans/scrubbing-data.md#scrubbing-data-with-beforesendspan) for span data enrichment and scrubbing.

For SDK-internal processing, SDKs are free to implement further processing mechanisms as they see fit. It's strongly recommended to implement client [lifecycle hooks](https://github.com/getsentry/rfcs/blob/main/text/0034-sdk-lifecycle-hooks.md). The `captureSpan` pipeline emits a `process_span` message that any consumer (e.g. an integration) can subscribe to. The consumer can then apply its logic to spans, similarly to how it might have applied it in event processors before. SDKs having alternative processing patterns established can also use them.

```txt
// in captureSpan:
processed_span = client.emit("process_span", captured_span)

// Somewhere in e.g. an integration:
client.on("process_span", (span) => {
   span.attributes["sentry.origin"] = "auto.http.server"
})
```

SDK authors working on Span-First are encouraged to evaluate this approach and provide feedback and perspective.

**Out of scope**: Event processors could be registered on scopes. For now, we don't see a need to continue supporting scoped processing. We can re-evaluate if a use case comes up that can't be solved with client-wide processing hooks.

**To sum up**: Spans no longer going through event processors is a behaviour-breaking change. For users, `ignore_spans` and `before_send_span` should be the way forward. Internally, SDKs may use further processing mechanisms.

## [Span Buffer](https://develop.sentry.dev/sdk/telemetry/spans/implementation.md#span-buffer)

See [Span Buffer specification](https://develop.sentry.dev/sdk/telemetry/spans/span-buffer.md) for more details.

## [Release](https://develop.sentry.dev/sdk/telemetry/spans/implementation.md#release)

The initial PoC implementation of Span-First SHOULD be released in a minor version of the SDK.

* This feature is entirely opt-in via `traceLifecycle = 'stream'` and therefore does not introduce breaking changes to existing users.

* The default tracing behavior (transaction-based) MUST remain unchanged until Span-First becomes the default in a future major release.

* Release notes and user facing documentation SHOULD clearly describe:

  * the availability of Span-First behind the opt-in flag
  * any known limitations
