Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ module.exports = [
path: createCDNPath('bundle.logs.metrics.min.js'),
gzip: false,
brotli: false,
limit: '100 KB',
limit: '101 KB',
disablePlugins: ['@size-limit/esbuild'],
},
{
Expand Down
149 changes: 142 additions & 7 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,150 @@ The same applies to the no-code entry points, e.g. `node --import=@sentry/node/i

Affected SDKs: All SDKs.

Each span is sent to Sentry the moment it finishes instead of being buffered until the root span completes. This means spans are no longer bound by the 1000-span per transaction limit and their individual payload-size limits have been increased.
Spans are now sent to Sentry in small batches instead of being buffered until the root span completes.
This means spans are no longer bound by the 1000-span per transaction limit and their individual payload-size limits have been increased.

The new model comes with some changes to Sentry hooks such as `beforeSendSpan` or options like `ignoreSpans` and requires manual migration. `beforeSendTransaction` and `ignoreTransactions` will **no-op**. Users who cannot migrate yet can opt into the previous transaction-based static model.
The new model comes with some changes to Sentry hooks such as `beforeSendSpan` or options like `ignoreSpans` and requires manual migration.
The `beforeSendTransaction` and `ignoreTransactions` options will **no-op**.
If you cannot migrate to span streaming yet, you can opt into the previous transaction-based static model.

> **TODO(v11):** The migration path for span streaming is still being defined. Document:
>
> - the concrete before/after for `beforeSendSpan` and `ignoreSpans`,
> - the exact replacement for `beforeSendTransaction` / `ignoreTransactions`,
> - how to opt back into the transaction-based model (option name + example).
#### `beforeSendSpan` receives the streamed span format

Your `beforeSendSpan` callback now receives a `StreamedSpanJSON` object and is invoked as each span finishes, rather than for all spans of a transaction right before that transaction is sent. As in v10, it is invoked for the root span as well as for child spans.

The payload fields were renamed:

| Before (`SpanJSON`) | After (`StreamedSpanJSON`) |
| ------------------- | ------------------------------ |
| `description` | `name` |
| `data` | `attributes` |
| `op` | `attributes['sentry.op']` |
| `timestamp` | `end_timestamp` |
| `status` (`string`) | `status` (`'ok'` or `'error'`) |

```js
// Before
Sentry.init({
beforeSendSpan: span => {
if (span.op === 'db.query') {
span.description = scrub(span.description);
span.data['db.statement'] = scrub(span.data['db.statement']);
}
return span;
},
});

// After
Sentry.init({
beforeSendSpan: span => {
if (span.attributes?.['sentry.op'] === 'db.query') {
span.name = scrub(span.name);
span.attributes['db.statement'] = scrub(span.attributes['db.statement']);
}
return span;
},
});
```

Returning `null` to drop a span was already disallowed in v9 and remains a no-op. Use `ignoreSpans` to filter spans.

If you cannot migrate a callback yet, wrap it with `Sentry.withStaticSpan()` and opt out of span streaming (see below). `withStaticSpan` marks the callback as expecting the legacy `SpanJSON` format:

```js
Sentry.init({
traceLifecycle: 'static',
beforeSendSpan: Sentry.withStaticSpan(span => {
span.description = scrub(span.description);
return span;
}),
});
```

A `beforeSendSpan` callback that does not match the configured `traceLifecycle` is **never invoked** — an unwrapped callback is ignored in `'static'` mode, and a `withStaticSpan`-wrapped callback is ignored in `'stream'` mode. Enable debug logging to surface a warning about the mismatch. Previously, an incompatible callback silently downgraded the SDK to the static lifecycle instead.

The `withStreamedSpan()` helper is now a no-op, since streamed payloads are the default. It is deprecated and will be removed in v12. You can remove the wrapper:

```js
// Before
beforeSendSpan: Sentry.withStreamedSpan(span => span);

// After
beforeSendSpan: span => span;
```

The internal `isStreamedBeforeSendSpanCallback()` function from `@sentry/core` is no longer exported.

#### Replacing `beforeSendTransaction`

`beforeSendTransaction` no-ops because no transaction events are produced. For **scrubbing and data modification**, move the logic to `beforeSendSpan` and guard on `is_segment` to target what used to be the transaction:

```js
// Before
Sentry.init({
beforeSendTransaction: event => {
event.transaction = scrubIds(event.transaction);
return event;
},
});

// After
Sentry.init({
beforeSendSpan: span => {
if (span.is_segment) {
span.name = scrubIds(span.name);
}
return span;
},
});
```

Note that scope `tags` and `extra` are not carried over to streamed spans, since spans only have attributes. Use `Sentry.setAttribute()` / `Sentry.setAttributes()` instead.

For **dropping** a transaction, use `ignoreSpans` (see below). The `beforeSendSpan` callback cannot drop spans.

#### Replacing `ignoreTransactions` with `ignoreSpans`

`ignoreTransactions` no-ops. Use `ignoreSpans` to match the segment span instead: when a segment span is ignored, all of its child spans are dropped with it, which is equivalent to dropping the whole transaction.

```js
// Before
Sentry.init({
ignoreTransactions: ['GET /health'],
});

// After
Sentry.init({
ignoreSpans: ['GET /health'],
});
```

`ignoreSpans` matches on the span `name` (formerly `description`). Because it applies to every span rather than just to root spans, consider narrowing the filter with the object form so that child spans sharing a name are not dropped as collateral:

```js
Sentry.init({
ignoreSpans: [{ name: 'GET /health', attributes: { 'sentry.op': 'http.server' } }],
});
```

`ignoreSpans` itself is unchanged in shape, but it now takes effect when a span **starts** rather than when the transaction is sent. Matched spans are never recorded at all, which means a matched non-segment span's children are re-parented to its parent instead of being dropped.

#### Opting out of span streaming

To keep the previous transaction-based model, set `traceLifecycle: 'static'`:

```js
Sentry.init({
traceLifecycle: 'static',

// `beforeSendSpan` MUST be wrapped with Sentry.withStaticSpan:
beforeSendSpan: Sentry.withStaticSpan(span => {
span.description = scrub(span.description);
return span;
}),
});
```

In Node, Vercel Edge and Cloudflare you can also set the `SENTRY_TRACE_LIFECYCLE=static` environment variable instead. The static lifecycle only exists for backwards compatibility and is planned for removal in a future major version, so treat this as a temporary measure.

### Logs are enabled by default

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()],
integrations: [Sentry.browserTracingIntegration()],
tracesSampleRate: 1,
beforeSendSpan: Sentry.withStreamedSpan(span => {
beforeSendSpan: span => {
if (span.attributes['sentry.op'] === 'pageload') {
span.name = 'customPageloadSpanName';
span.links = [
Expand All @@ -24,5 +24,5 @@ Sentry.init({
span.status = 'something';
}
return span;
}),
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { SpanJSON } from '@sentry/core';
import type { NodeOptions } from '@sentry/node';
import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

// Simulates a callback that was not migrated to `withStaticSpan`. The cast stands in for the
// JavaScript users who don't get a type error here.
const unmigratedBeforeSendSpan = ((span: SpanJSON) => {
span.description = 'thisShouldNotBeApplied';
return span;
}) as unknown as NodeOptions['beforeSendSpan'];

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
tracesSampleRate: 1.0,
transport: loggingTransport,
release: '1.0.0',
traceLifecycle: 'static',
beforeSendSpan: unmigratedBeforeSendSpan,
});

Sentry.startSpan({ name: 'test-span', op: 'test' }, () => {
Sentry.startSpan({ name: 'test-child-span', op: 'test-child' }, () => {
// noop
});
});

void Sentry.flush();
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
tracesSampleRate: 1.0,
transport: loggingTransport,
release: '1.0.0',
traceLifecycle: 'static',
beforeSendSpan: Sentry.withStaticSpan(span => {
if (span.description === 'test-child-span') {
span.description = 'customChildSpanName';
span.data['sentry.custom_attribute'] = 'customAttributeValue';
}

if (span.is_segment) {
span.description = 'customRootSpanName';
span.data['sentry.custom_root_attribute'] = 'customRootAttributeValue';
}

return span;
}),
});

Sentry.startSpan({ name: 'test-span', op: 'test' }, () => {
Sentry.startSpan({ name: 'test-child-span', op: 'test-child' }, () => {
// noop
});
});

void Sentry.flush();
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { afterAll, expect, test } from 'vitest';
import { cleanupChildProcesses, createRunner } from '../../../utils/runner';

afterAll(() => {
cleanupChildProcesses();
});

test('withStaticSpan applies changes to child spans', async () => {
await createRunner(__dirname, 'scenario.ts')
.expect({
transaction: event => {
expect(event.spans).toHaveLength(1);

const childSpan = event.spans![0]!;
expect(childSpan.description).toBe('customChildSpanName');
expect(childSpan.data['sentry.custom_attribute']).toBe('customAttributeValue');
},
})
.start()
.completed();
});

test('withStaticSpan applies changes to the root span', async () => {
await createRunner(__dirname, 'scenario.ts')
.expect({
transaction: event => {
expect(event.transaction).toBe('customRootSpanName');
expect(event.contexts?.trace?.data?.['sentry.custom_root_attribute']).toBe('customRootAttributeValue');
},
})
.start()
.completed();
});

test('a beforeSendSpan callback without withStaticSpan is not invoked in the static trace lifecycle', async () => {
await createRunner(__dirname, 'scenario-unwrapped.ts')
.expect({
transaction: event => {
expect(event.transaction).toBe('test-span');
expect(event.spans).toHaveLength(1);
expect(event.spans![0]!.description).toBe('test-child-span');
},
})
.start()
.completed();
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import { loggingTransport } from '@sentry-internal/node-integration-tests';
Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
tracesSampleRate: 1.0,
traceLifecycle: 'stream',
transport: loggingTransport,
release: '1.0.0',
beforeSendSpan: Sentry.withStreamedSpan(span => {
beforeSendSpan: span => {
if (span.name === 'test-child-span') {
span.name = 'customChildSpanName';
if (!span.attributes) {
Expand All @@ -27,7 +26,7 @@ Sentry.init({
];
}
return span;
}),
},
});

Sentry.startSpan({ name: 'test-span', op: 'test' }, () => {
Expand Down
4 changes: 2 additions & 2 deletions dev-packages/rollup-utils/plugins/bundlePlugins.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ export function makeTerserPlugin() {
'_resolveFilename',
// Set on e.g. the shim feedbackIntegration to be able to detect it
'_isShim',
// Marker set by `withStreamedSpan()` to tag streamed `beforeSendSpan` callbacks
'_streamed',
// Marker used to detect `beforeSendSpan` callbacks expecting the static span format
'_static',
// This is used in metadata integration
'_sentryModuleMetadata',
],
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/index.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ export {
unleashIntegration,
growthbookIntegration,
spanStreamingIntegration,
withStaticSpan,
// oxlint-disable-next-line typescript/no-deprecated
withStreamedSpan,
metrics,
} from '@sentry/node';
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export declare function init(options: Options | clientSdk.BrowserOptions | NodeO
export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration;
export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration;
export declare const spanStreamingIntegration: typeof clientSdk.spanStreamingIntegration;
export declare const withStaticSpan: typeof clientSdk.withStaticSpan;
// oxlint-disable-next-line typescript/no-deprecated
export declare const withStreamedSpan: typeof clientSdk.withStreamedSpan;

export declare const getDefaultIntegrations: (options: Options) => Integration[];
Expand Down
2 changes: 2 additions & 0 deletions packages/aws-serverless/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ export {
growthbookIntegration,
metrics,
spanStreamingIntegration,
withStaticSpan,
// oxlint-disable-next-line typescript/no-deprecated
withStreamedSpan,
} from '@sentry/node';

Expand Down
2 changes: 2 additions & 0 deletions packages/browser/src/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ export {
spanToTraceHeader,
spanToBaggageHeader,
updateSpanName,
withStaticSpan,
// oxlint-disable-next-line typescript/no-deprecated
withStreamedSpan,
metrics,
} from '@sentry/core/browser';
Expand Down
Loading
Loading