Claude
Skills
Sign in
Back

sentry-span-streaming

Included with Lifetime
$97 forever

Migrate to Sentry span streaming (span-first trace lifecycle). Use when asked to "enable span streaming", "migrate to span streaming", "use traceLifecycle stream", "add spanStreamingIntegration", or switch from transaction-based to streamed span delivery.

feature-setup

What this skill does


> [All Skills](../../SKILL_TREE.md) > [Feature Setup](../sentry-feature-setup/SKILL.md) > Span Streaming

# Sentry Span Streaming Migration

Migrate from the default transaction-based trace lifecycle (`static`) to span streaming (`stream`), where spans are sent in multiple batches as they complete instead of being batched into one transaction at the end.

## Invoke This Skill When

- User asks to "enable span streaming" or "migrate to span streaming"
- User wants to switch from transaction-based to streamed span delivery
- User mentions `traceLifecycle`, `spanStreamingIntegration`, or `withStreamedSpan`
- User wants lower latency span delivery or per-span processing

## Supported Platforms

| Platform | Status |
|---|---|
| JavaScript (Browser, Node.js, Bun, Deno, Cloudflare) | Supported |
| Python | Not yet available |
| Ruby | Not yet available |
| Go | Not yet available |
| Other SDKs | Not yet available |

If the user's project does not use a supported SDK, inform them that span streaming is currently only available for JavaScript SDKs and stop here.

---

## Phase 1: Detect

Identify the user's platform, SDK version, and current tracing configuration.

### 1.1 Detect Platform and SDK

```bash
# Check for JavaScript Sentry packages
cat package.json 2>/dev/null | grep -E '"@sentry/'

# Check for Python Sentry
cat requirements.txt setup.py pyproject.toml 2>/dev/null | grep -i sentry

# Check for Ruby Sentry
cat Gemfile 2>/dev/null | grep sentry

# Check for Go Sentry
cat go.mod 2>/dev/null | grep sentry
```

If an unsupported Sentry SDK is detected, inform the user that span streaming is not yet available for their platform.

### 1.2 Detect JavaScript Environment

```bash
# Detect if browser, server, or both
grep -rn "from '@sentry/browser'\|from '@sentry/react'\|from '@sentry/vue'\|from '@sentry/angular'\|from '@sentry/svelte'\|from '@sentry/nextjs'\|from '@sentry/nuxt'\|from '@sentry/sveltekit'\|from '@sentry/remix'\|from '@sentry/solidstart'\|from '@sentry/astro'\|from '@sentry/react-router'" --include="*.ts" --include="*.js" --include="*.tsx" --include="*.jsx" --include="*.mjs" -l 2>/dev/null | head -20

grep -rn "from '@sentry/node'\|from '@sentry/bun'\|from '@sentry/deno'\|from '@sentry/cloudflare'" --include="*.ts" --include="*.js" --include="*.tsx" --include="*.jsx" --include="*.mjs" -l 2>/dev/null | head -20
```

### 1.3 Find Existing Sentry Config

```bash
# Find Sentry.init calls
grep -rn "Sentry\.init\|init({" --include="*.ts" --include="*.js" --include="*.tsx" --include="*.jsx" --include="*.mjs" -l 2>/dev/null | head -20

# Find beforeSendSpan usage
grep -rn "beforeSendSpan" --include="*.ts" --include="*.js" --include="*.tsx" --include="*.jsx" --include="*.mjs" -l 2>/dev/null

# Find beforeSendTransaction usage
grep -rn "beforeSendTransaction" --include="*.ts" --include="*.js" --include="*.tsx" --include="*.jsx" --include="*.mjs" -l 2>/dev/null

# Find ignoreSpans usage
grep -rn "ignoreSpans" --include="*.ts" --include="*.js" --include="*.tsx" --include="*.jsx" --include="*.mjs" -l 2>/dev/null
```

### 1.4 Classify Environment

Based on detection results, classify each `Sentry.init` call as:

| Environment | Packages | Migration Path |
|---|---|---|
| **Browser** | `@sentry/browser`, `@sentry/react`, `@sentry/vue`, `@sentry/angular`, `@sentry/svelte` | Add `spanStreamingIntegration()` |
| **Server** | `@sentry/node`, `@sentry/bun`, `@sentry/deno`, `@sentry/cloudflare` | Add `traceLifecycle: 'stream'` |
| **Framework (both)** | `@sentry/nextjs`, `@sentry/nuxt`, `@sentry/sveltekit`, `@sentry/remix`, `@sentry/astro`, `@sentry/solidstart`, `@sentry/react-router` | Migrate both client and server configs separately |

---

## Phase 2: Migrate (JavaScript)

**Prerequisites:** Sentry JavaScript SDK `>=10.53.1` with tracing enabled (`tracesSampleRate` or `tracesSampler` configured).

Apply changes to each `Sentry.init` call. Work through each file identified in Phase 1.

### 2.1 Enable Span Streaming

#### Server-Side SDKs

Add `traceLifecycle: 'stream'` to `Sentry.init()`:

```js
// Before
Sentry.init({
  dsn: '...',
  tracesSampleRate: 1.0,
});

// After
Sentry.init({
  dsn: '...',
  tracesSampleRate: 1.0,
  traceLifecycle: 'stream',
});
```

#### Browser-Side SDKs

Add `Sentry.spanStreamingIntegration()` to the `integrations` array. The integration automatically enables `traceLifecycle: 'stream'` — you do not need to set it manually.

```js
// Before
Sentry.init({
  dsn: '...',
  integrations: [
    Sentry.browserTracingIntegration(),
  ],
  tracesSampleRate: 1.0,
});

// After
Sentry.init({
  dsn: '...',
  integrations: [
    Sentry.spanStreamingIntegration(),
    Sentry.browserTracingIntegration(),
  ],
  tracesSampleRate: 1.0,
});
```

The order of `spanStreamingIntegration()` relative to other integrations does not matter.

#### Framework SDKs (Client + Server)

Apply the browser migration to client config files and the server migration to server config files. Common patterns:

| Framework | Client Config | Server Config |
|---|---|---|
| Next.js | `sentry.client.config.ts` | `sentry.server.config.ts`, `sentry.edge.config.ts` |
| Nuxt | Client-side `Sentry.init` in module | Server-side `Sentry.init` in module |
| SvelteKit | `src/hooks.client.ts` | `src/hooks.server.ts` |
| Remix | `entry.client.tsx` | `entry.server.tsx` |
| Astro | Client-side init | Server-side init |

### 2.2 Migrate `beforeSendSpan`

If the user has a `beforeSendSpan` callback, it **must** be wrapped with `Sentry.withStreamedSpan()` to work in streaming mode. Without this wrapper, the SDK falls back to static mode.

The callback shape also changes:
- `description` is now `name`
- `data` is now `attributes`
- The span object is `StreamedSpanJSON` instead of `SpanJSON`

```js
// Before (static mode)
Sentry.init({
  beforeSendSpan: (span) => {
    if (span.description?.includes('/health')) {
      span.description = '[filtered]';
    }
    // 'data' contains span attributes
    delete span.data?.['http.request.body'];
    return span;
  },
});

// After (streaming mode)
Sentry.init({
  beforeSendSpan: Sentry.withStreamedSpan((span) => {
    if (span.name?.includes('/health')) {
      span.name = '[filtered]';
    }
    // 'attributes' replaces 'data'
    if (span.attributes) {
      delete span.attributes['http.request.body'];
    }
    return span;
  }),
});
```

**Key differences in the callback:**

| Static (`SpanJSON`) | Streaming (`StreamedSpanJSON`) |
|---|---|
| `span.description` | `span.name` |
| `span.data` (processed attributes) | `span.attributes` (raw attributes) |
| `span.timestamp` (end time) | `span.end_timestamp` |
| `span.status` (optional string) | `span.status` (`'ok'` or `'error'`) |
| `span.op` | `span.attributes['sentry.op']` |

Returning `null` from `beforeSendSpan` does **not** drop the span — it is ignored and a warning is logged.

### 2.3 Remove or Replace `beforeSendTransaction`

`beforeSendTransaction` has **no effect** in streaming mode. Spans are sent individually, not batched into transactions.

```js
// Before
Sentry.init({
  beforeSendTransaction: (event) => {
    // This entire callback is ignored in streaming mode
    if (event.transaction === '/health') {
      return null;
    }
    return event;
  },
});
```

**Migration paths depending on what `beforeSendTransaction` was used for:**

| Use Case | Streaming Replacement |
|---|---|
| Drop spans by name/route | Use `ignoreSpans` option |
| Modify span data before send | Use `beforeSendSpan` with `withStreamedSpan` |
| Filter by transaction name | Use `ignoreSpans` with string/RegExp pattern |
| Add tags/context to transaction | Use `beforeSendSpan` with `withStreamedSpan` |

Remove the `beforeSendTransaction` option from `Sentry.init()` after migrating its logic.

### 2.4 Configure `ignoreSpans` (Optional)

`ignoreSpans` works in both static and streaming modes, but the filter is evaluated at different points in the span lifecycle:

- **Streaming mode:** evaluated when the span **

Related in feature-setup