Skip to content
Draft
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
8 changes: 7 additions & 1 deletion MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ Sentry.init({
// After
Sentry.init({
beforeSendSpan: span => {
if (span.attributes?.['sentry.op'] === 'db.query') {
if (span.attributes['sentry.op'] === 'db.query') {
span.name = scrub(span.name);
span.attributes['db.statement'] = scrub(span.attributes['db.statement']);
}
Expand Down Expand Up @@ -459,6 +459,12 @@ Sentry.init({

In Node, Bun, 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.

#### `Sentry.spanToJSON` returns streamed span format

The `spanToJSON` helper previously returned a `SpanJSON` object. In v11, the return type was changed to `StreamedSpanJSON`, meaning the object shape is now the [same as in `beforeSendSpan`](#beforeSendSpan-receives-the-streamed-span-format).

If you're [opting out of span streaming](#opting-out-of-span-streaming), you can replace your `spanToJSON` calls with `spanToStaticSpanJSON`, which still returns the static `SpanJSON` object format.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing streamed helper migration note

Low Severity

This PR removes the publicly exported spanToStreamedSpanJSON helper, but the migration section only documents the spanToJSON return-type change and the new spanToStaticSpanJSON API. Callers of the removed export do not get an explicit migration path. Flagged because the review rules call out public API removals and signature changes without proper notices.

Additional Locations (1)
Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Reviewed by Cursor Bugbot for commit 3e9e966. Configure here.


### Logs are enabled by default

Affected SDKs: All SDKs.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,12 @@ sentryTest(
expect(envelope).toBeDefined();

const firstWaitingSpanValue = await page.evaluate(
() => (window as unknown as WindowWithSpan).firstWaitingSpan.description,
() => (window as unknown as WindowWithSpan).firstWaitingSpan.name,
);
const secondWaitingSpanName = await page.evaluate(
() => (window as unknown as WindowWithSpan).secondWaitingSpan.description,
);
const thirdWaitingSpanName = await page.evaluate(
() => (window as unknown as WindowWithSpan).thirdWaitingSpan.description,
() => (window as unknown as WindowWithSpan).secondWaitingSpan.name,
);
const thirdWaitingSpanName = await page.evaluate(() => (window as unknown as WindowWithSpan).thirdWaitingSpan.name);

expect(firstWaitingSpanValue).toBe('span 2');
expect(secondWaitingSpanName).toBe('span 1');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from '@playwright/test';
import type { SpanJSON } from '@sentry/core';
import type { StreamedSpanJSON } from '@sentry/core';
import { sentryTest } from '../../../../utils/fixtures';
import { shouldSkipTracingTest } from '../../../../utils/helpers';

Expand All @@ -12,25 +12,26 @@ sentryTest('should finish a custom transaction when the page goes background', a
await page.goto(url);

await page.locator('#start-span').click();
const spanJsonBefore: SpanJSON = await page.evaluate('window.getSpanJson()');
const spanJsonBefore: StreamedSpanJSON = await page.evaluate('window.getSpanJson()');

const id_before = spanJsonBefore.span_id;
const description_before = spanJsonBefore.description;
const name_before = spanJsonBefore.name;
const status_before = spanJsonBefore.status;

expect(description_before).toBe('test-span');
expect(name_before).toBe('test-span');
expect(status_before).toBe('ok');

await page.locator('#go-background').click();
const spanJsonAfter: SpanJSON = await page.evaluate('window.getSpanJson()');
const spanJsonAfter: StreamedSpanJSON = await page.evaluate('window.getSpanJson()');

const id_after = spanJsonAfter.span_id;
const description_after = spanJsonAfter.description;
const status_after = spanJsonAfter.status;
const data_after = spanJsonAfter.data;
const name_after = spanJsonAfter.name;
const attributes_after = spanJsonAfter.attributes;

expect(id_before).toBe(id_after);
expect(description_after).toBe(description_before);
expect(status_after).toBe('cancelled');
expect(data_after?.['sentry.cancellation_reason']).toBe('document.hidden');
expect(name_after).toBe(name_before);
// a cancelled span is reported as `ok`, with the raw status kept as an attribute
expect(spanJsonAfter.status).toBe('ok');
expect(attributes_after['sentry.status.message']).toBeUndefined();
expect(attributes_after['sentry.cancellation_reason']).toBe('document.hidden');
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { assert } from 'https://deno.land/std@0.212.0/assert/assert.ts';
import { assertExists } from 'https://deno.land/std@0.212.0/assert/assert_exists.ts';
import { assertEquals } from 'https://deno.land/std@0.212.0/assert/assert_equals.ts';
import { resetGlobals, transactionSink, withTimeout } from '../../src/index.ts';
import { SENTRY_OP } from '@sentry/conventions/attributes';

Deno.test('aws-sdk instrumentation: included in default integrations (Deno 2.8.0+)', () => {
resetGlobals();
Expand Down Expand Up @@ -37,7 +38,7 @@ Deno.test('aws-sdk instrumentation: orchestrion @smithy/smithy-client:send chann
// child has actually ended and can be captured on the transaction.
const rpcSpanEnded = new Promise<void>(resolve => {
client.on('spanEnd', (span: Span) => {
if (spanToJSON(span).op === 'rpc') {
if (spanToJSON(span).attributes[SENTRY_OP] === 'rpc') {
resolve();
}
});
Expand Down
6 changes: 3 additions & 3 deletions packages/angular/src/tracing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,15 @@ export function _updateSpanAttributesForParametrizedUrl(route: string, url: stri
return;
}

const { data: attributes, op } = spanToJSON(span);
const attributes = spanToJSON(span).attributes;

if (!attributes || attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] === 'url') {
span.updateName(route);

const absoluteUrl = getAbsoluteUrl(url);

span.setAttributes({
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.${op}.angular`,
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.${attributes[SENTRY_OP]}.angular`,
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
[URL_FULL]: filterCollectedUrl(absoluteUrl),
[URL_PATH]: parseStringToURLObject(absoluteUrl)?.pathname,
Expand Down Expand Up @@ -259,7 +259,7 @@ export class TraceService implements OnDestroy {

const rootSpan = getRootSpan(activeSpan);

this._pageloadOngoing = spanToJSON(rootSpan).op === 'pageload';
this._pageloadOngoing = spanToJSON(rootSpan).attributes[SENTRY_OP] === 'pageload';
return this._pageloadOngoing;
}
}
Expand Down
8 changes: 4 additions & 4 deletions packages/angular/test/tracing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,15 @@ describe('Angular Tracing', () => {

expect(spanToJSON(span)).toEqual(
expect.objectContaining({
data: expect.objectContaining({
attributes: expect.objectContaining({
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.undefined.angular',
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
[URL_TEMPLATE]: route,
// URL_FULL is resolved against jsdom's http://localhost origin
[URL_FULL]: expect.stringContaining('/users/123/'),
[URL_PATH]: '/users/123/',
}),
description: route,
name: route,
}),
);
});
Expand All @@ -109,11 +109,11 @@ describe('Angular Tracing', () => {

expect(spanToJSON(span)).toEqual(
expect.objectContaining({
data: {
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'manual',
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'sample-source',
},
description: 'initial-span-name',
name: 'initial-span-name',
}),
);
});
Expand Down
4 changes: 2 additions & 2 deletions packages/astro/src/server/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable max-lines */
import { HTTP_ROUTE, URL_FRAGMENT, URL_FULL, URL_PATH, URL_QUERY } from '@sentry/conventions/attributes';
import { HTTP_ROUTE, SENTRY_OP, URL_FRAGMENT, URL_FULL, URL_PATH, URL_QUERY } from '@sentry/conventions/attributes';
import type { Span, SpanAttributes } from '@sentry/core';
import {
addNonEnumerableProperty,
Expand Down Expand Up @@ -98,7 +98,7 @@ export const handleRequest: (options?: MiddlewareOptions) => MiddlewareHandler =
const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined;

// if there is an active span, we just want to enhance it with routing data etc.
if (rootSpan && spanToJSON(rootSpan).op === 'http.server') {
if (rootSpan && spanToJSON(rootSpan).attributes[SENTRY_OP] === 'http.server') {
return enhanceHttpServerSpan(ctx, next, rootSpan);
}

Expand Down
19 changes: 13 additions & 6 deletions packages/browser-utils/src/performance/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,13 @@ export function startTrackingLongTasks(): void {
return;
}

const { op: parentOp, start_timestamp: parentStartTimestamp } = spanToJSON(parent);
const { attributes: parentAttributes, start_timestamp: parentStartTimestamp } = spanToJSON(parent);

for (const entry of entries) {
const startTime = msToSec((browserPerformanceTimeOrigin() as number) + entry.startTime);
const duration = msToSec(entry.duration);

if (parentOp === 'navigation' && parentStartTimestamp && startTime < parentStartTimestamp) {
if (parentAttributes[SENTRY_OP] === 'navigation' && parentStartTimestamp && startTime < parentStartTimestamp) {
// Skip adding a span if the long task started before the navigation started.
// `startAndEndSpan` will otherwise adjust the parent's start time to the span's start
// time, potentially skewing the duration of the actual navigation as reported via our
Expand Down Expand Up @@ -117,7 +117,10 @@ export function startTrackingLongAnimationFrames(): void {

const startTime = msToSec((browserPerformanceTimeOrigin() as number) + entry.startTime);

const { start_timestamp: parentStartTimestamp, op: parentOp } = spanToJSON(parent);
const {
start_timestamp: parentStartTimestamp,
attributes: { [SENTRY_OP]: parentOp },
} = spanToJSON(parent);

if (parentOp === 'navigation' && parentStartTimestamp && startTime < parentStartTimestamp) {
// Skip adding the span if the long animation frame started before the navigation started.
Expand Down Expand Up @@ -220,7 +223,7 @@ export function addPerformanceEntries(span: Span, options: AddPerformanceEntries

const performanceEntries = performance.getEntries();

const { op, start_timestamp: transactionStartTime } = spanToJSON(span);
const { attributes, start_timestamp: transactionStartTime } = spanToJSON(span);

performanceEntries.slice(_performanceCursor).forEach(entry => {
const startTime = msToSec(entry.startTime);
Expand All @@ -232,7 +235,11 @@ export function addPerformanceEntries(span: Span, options: AddPerformanceEntries
Math.max(0, entry.duration),
);

if (op === 'navigation' && transactionStartTime && timeOrigin + startTime < transactionStartTime) {
if (
attributes[SENTRY_OP] === 'navigation' &&
transactionStartTime &&
timeOrigin + startTime < transactionStartTime
) {
return;
}

Expand Down Expand Up @@ -479,7 +486,7 @@ function _trackNavigator(span: Span, spanStreamingEnabled: boolean | undefined):
if (isMeasurementValue(connection.rtt)) {
if (spanStreamingEnabled) {
span.setAttribute('network.connection.rtt', connection.rtt);
} else if (spanToJSON(span).op === 'pageload') {
} else if (spanToJSON(span).attributes[SENTRY_OP] === 'pageload') {
// Measurements are only recorded on the pageload span, matching the historical
// behavior where `connection.rtt` was only flushed for pageload transactions.
setMeasurement('connection.rtt', connection.rtt, 'millisecond');
Expand Down
6 changes: 4 additions & 2 deletions packages/browser-utils/src/performance/userTiming.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SENTRY_ORIGIN } from '@sentry/conventions/attributes';
import { SENTRY_OP, SENTRY_ORIGIN } from '@sentry/conventions/attributes';
import type { IntegrationFn, Span, SpanAttributes, SpanAttributeValue } from '@sentry/core';
import {
browserPerformanceTimeOrigin,
Expand Down Expand Up @@ -34,7 +34,9 @@ const _userTimingIntegration = ((options: UserTimingOptions = {}) => {
let performanceCursor = 0;

client.on('beforeIdleSpanEnd', idleSpan => {
const { op: parentOp, start_timestamp: parentStartTimestamp } = spanToJSON(idleSpan);
const { attributes, start_timestamp: parentStartTimestamp } = spanToJSON(idleSpan);
const parentOp = attributes[SENTRY_OP];

if (parentOp !== 'pageload' && parentOp !== 'navigation') {
return;
}
Expand Down
8 changes: 3 additions & 5 deletions packages/browser-utils/src/web-vitals/spans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME,
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
spanToStreamedSpanJSON,
spanToJSON,
startInactiveSpan,
timestampInSeconds,
} from '@sentry/core';
Expand Down Expand Up @@ -105,7 +105,7 @@ export function _emitWebVitalSpan(options: WebVitalSpanOptions): void {
...passedAttributes,
};

if (parentSpan && spanToStreamedSpanJSON(parentSpan).attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_OP] === 'pageload') {
if (parentSpan && spanToJSON(parentSpan).attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] === 'pageload') {
// for LCP and CLS, we collect the pageload span id as an attribute
attributes['sentry.pageload.span_id'] = parentSpan.spanContext().spanId;
}
Expand Down Expand Up @@ -343,9 +343,7 @@ export function _sendInpSpan(inpValue: number, entry: PerformanceEventTiming, st
const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined;

const spanToUse = cachedContext?.span || rootSpan;
const routeName = spanToUse
? spanToStreamedSpanJSON(spanToUse).name
: getCurrentScope().getScopeData().transactionName;
const routeName = spanToUse ? spanToJSON(spanToUse).name : getCurrentScope().getScopeData().transactionName;
const name = cachedContext?.elementName || htmlTreeAsString(entry.target);

_emitWebVitalSpan({
Expand Down
3 changes: 2 additions & 1 deletion packages/browser-utils/src/web-vitals/tracking.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Client, Measurements, Span } from '@sentry/core';
import { browserPerformanceTimeOrigin, debug, setMeasurement, spanToJSON } from '@sentry/core';
import { SENTRY_OP } from '@sentry/conventions/attributes';
import { DEBUG_BUILD } from '../debug-build';
import { htmlTreeAsString } from '../htmlTreeAsString';
import {
Expand Down Expand Up @@ -150,7 +151,7 @@ export function addWebVitalsToSpan(span: Span, options: AddWebVitalsToSpanOption
const timeOrigin = msToSec(origin);

// Measurements are only available for pageload transactions
if (spanToJSON(span).op === 'pageload') {
if (spanToJSON(span).attributes[SENTRY_OP] === 'pageload') {
_addTtfbRequestTimeToMeasurements(_measurements);

if (spanStreamingEnabled) {
Expand Down
6 changes: 3 additions & 3 deletions packages/browser-utils/test/browser/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ describe('startAndEndSpan()', () => {

expect(span).toBeDefined();
expect(span).toBeInstanceOf(SentrySpan);
expect(spanToJSON(span).description).toBe('evaluation');
expect(spanToJSON(span).op).toBe('script');
expect(spanToJSON(span).op).toBe('script');
expect(spanToJSON(span).name).toBe('evaluation');
expect(spanToJSON(span).attributes['sentry.op']).toBe('script');
expect(spanToJSON(span).attributes['sentry.op']).toBe('script');
});

it('adjusts the start timestamp if child span starts before transaction', () => {
Expand Down
Loading
Loading