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
6 changes: 3 additions & 3 deletions packages/angular/src/tracing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,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]: absoluteUrl,
[URL_PATH]: parseStringToURLObject(absoluteUrl)?.pathname,
Expand Down Expand Up @@ -252,7 +252,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
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 @@ -96,7 +96,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
23 changes: 15 additions & 8 deletions packages/browser-utils/src/metrics/browserMetrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { getActivationStart } from './web-vitals/lib/getActivationStart';
import { getNavigationEntry } from './web-vitals/lib/getNavigationEntry';
import { getVisibilityWatcher } from './web-vitals/lib/getVisibilityWatcher';
import { DEBUG_BUILD } from '../debug-build';
import { URL_FULL } from '@sentry/conventions/attributes';
import { SENTRY_OP, URL_FULL } from '@sentry/conventions/attributes';
interface NavigatorNetworkInformation {
readonly connection?: NetworkInformation;
}
Expand Down Expand Up @@ -114,13 +114,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 @@ -158,7 +158,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 @@ -344,7 +347,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 @@ -356,7 +359,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 @@ -410,7 +417,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 Expand Up @@ -682,7 +689,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/metrics/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/metrics/webVitalSpans.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 @@ -100,7 +100,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 @@ -338,9 +338,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
9 changes: 5 additions & 4 deletions packages/browser/src/integrations/graphqlClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
isObjectLike,
isString,
SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD,
SEMANTIC_ATTRIBUTE_SENTRY_OP,

Check failure on line 7 in packages/browser/src/integrations/graphqlClient.ts

View workflow job for this annotation

GitHub Actions / Lint

eslint(no-unused-vars)

Identifier 'SEMANTIC_ATTRIBUTE_SENTRY_OP' is imported but never used.
spanToJSON,
stringMatchesSomePattern,
} from '@sentry/core/browser';
import type { FetchHint, XhrHint } from '@sentry/browser-utils';
import { getBodyString, getFetchRequestArgBody, SENTRY_XHR_DATA_KEY } from '@sentry/browser-utils';
import { GRAPHQL_DOCUMENT, URL_FULL } from '@sentry/conventions/attributes';
import { GRAPHQL_DOCUMENT, HTTP_METHOD, SENTRY_OP, URL_FULL } from '@sentry/conventions/attributes';

interface GraphQLClientOptions {
endpoints: Array<string | RegExp>;
Expand Down Expand Up @@ -59,8 +59,8 @@
client.on('beforeOutgoingRequestSpan', (span, hint) => {
const spanJSON = spanToJSON(span);

const spanAttributes = spanJSON.data || {};
const spanOp = spanAttributes[SEMANTIC_ATTRIBUTE_SENTRY_OP];
const spanAttributes = spanJSON.attributes || {};
const spanOp = spanAttributes[SENTRY_OP];

const isHttpClientSpan = spanOp === 'http.client';

Expand All @@ -69,7 +69,8 @@
}

const httpUrl = spanAttributes[URL_FULL];
const httpMethod = spanAttributes[SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD] || spanAttributes['http.method'];
// oxlint-disable-next-line typescript/no-deprecated
const httpMethod = spanAttributes[SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD] || spanAttributes[HTTP_METHOD];

if (!isString(httpUrl) || !isString(httpMethod)) {
return;
Expand Down
11 changes: 6 additions & 5 deletions packages/browser/src/profiling/startProfileForSpan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export function startProfileForSpan(span: Span): void {
startTimestamp = timestampInSeconds() * 1000;
}

const spanName = spanToJSON(span).name;
const profiler = startJSSelfProfile();

// We failed to construct the profiler, so we skip.
Expand All @@ -33,7 +34,7 @@ export function startProfileForSpan(span: Span): void {
}

if (DEBUG_BUILD) {
debug.log(`[Profiling] started profiling span: ${spanToJSON(span).description}`);
debug.log(`[Profiling] started profiling span: ${spanName}`);
}

// We create "unique" span names to avoid concurrent spans with same names
Expand Down Expand Up @@ -72,7 +73,7 @@ export function startProfileForSpan(span: Span): void {
}
if (processedProfile) {
if (DEBUG_BUILD) {
debug.log('[Profiling] profile for:', spanToJSON(span).description, 'already exists, returning early');
debug.log('[Profiling] profile for:', spanName, 'already exists, returning early');
}
return;
}
Expand All @@ -86,14 +87,14 @@ export function startProfileForSpan(span: Span): void {
}

if (DEBUG_BUILD) {
debug.log(`[Profiling] stopped profiling of span: ${spanToJSON(span).description}`);
debug.log(`[Profiling] stopped profiling of span: ${spanName}`);
}

// In case of an overlapping span, stopProfiling may return null and silently ignore the overlapping profile.
if (!profile) {
if (DEBUG_BUILD) {
debug.log(
`[Profiling] profiler returned null profile for: ${spanToJSON(span).description}`,
`[Profiling] profiler returned null profile for: ${spanName}`,
'this may indicate an overlapping span or a call to stopProfiling with a profile title that was never started',
);
}
Expand All @@ -113,7 +114,7 @@ export function startProfileForSpan(span: Span): void {
// Enqueue a timeout to prevent profiles from running over max duration.
let maxDurationTimeoutID: number | undefined = WINDOW.setTimeout(() => {
if (DEBUG_BUILD) {
debug.log('[Profiling] max profile duration elapsed, stopping profiling for:', spanToJSON(span).description);
debug.log('[Profiling] max profile duration elapsed, stopping profiling for:', spanName);
}
// If the timeout exceeds, we want to stop profiling, but not finish the span
// eslint-disable-next-line @typescript-eslint/no-floating-promises
Expand Down
3 changes: 2 additions & 1 deletion packages/browser/src/profiling/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import type { BrowserOptions } from '../client';
import { DEBUG_BUILD } from '../debug-build';
import { WINDOW } from '../helpers';
import type { JSSelfProfile, JSSelfProfiler, JSSelfProfilerConstructor, JSSelfProfileStack } from './jsSelfProfiling';
import { SENTRY_OP } from '@sentry/conventions/attributes';

const MS_TO_NS = 1e6;

Expand Down Expand Up @@ -380,7 +381,7 @@ export function isProfiledTransactionEvent(event: Event): event is ProfiledEvent
*
*/
export function isAutomatedPageLoadSpan(span: Span): boolean {
return spanToJSON(span).op === 'pageload';
return spanToJSON(span).attributes[SENTRY_OP] === 'pageload';
}

/**
Expand Down
6 changes: 5 additions & 1 deletion packages/browser/src/tracing/backgroundtab.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { debug, getActiveSpan, getRootSpan, SPAN_STATUS_ERROR, spanToJSON } from '@sentry/core/browser';
import { DEBUG_BUILD } from '../debug-build';
import { WINDOW } from '../helpers';
import { SENTRY_OP } from '@sentry/conventions/attributes';

/**
* Add a listener that cancels and finishes a transaction when the global
Expand All @@ -19,7 +20,10 @@ export function registerBackgroundTabDetection(): void {
if (WINDOW.document.hidden && rootSpan) {
const cancelledStatus = 'cancelled';

const { op, status } = spanToJSON(rootSpan);
const {
attributes: { [SENTRY_OP]: op },
status,
} = spanToJSON(rootSpan);

if (DEBUG_BUILD) {
debug.log(`[Tracing] Transaction: ${cancelledStatus} -> since tab moved to the background, op: ${op}`);
Expand Down
12 changes: 7 additions & 5 deletions packages/browser/src/tracing/browserTracingIntegration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ import { WEB_VITALS_INTEGRATION_NAME, webVitalsIntegration } from '../integratio
import { registerBackgroundTabDetection } from './backgroundtab';
import { linkTraces } from './linkedTraces';
import { defaultRequestInstrumentationOptions, instrumentOutgoingRequests } from './request';
import { URL_FULL, URL_PATH } from '@sentry/conventions/attributes';
import { SENTRY_IDLE_SPAN_FINISH_REASON, SENTRY_OP, URL_FULL, URL_PATH } from '@sentry/conventions/attributes';

export const BROWSER_TRACING_INTEGRATION_ID = 'BrowserTracing';

Expand Down Expand Up @@ -485,10 +485,12 @@ export const browserTracingIntegration = ((options: Partial<BrowserTracingOption
function maybeEndActiveSpan(): void {
const activeSpan = getActiveIdleSpan(client);

if (activeSpan && !spanToJSON(activeSpan).timestamp) {
DEBUG_BUILD && debug.log(`[Tracing] Finishing current active span with op: ${spanToJSON(activeSpan).op}`);
const activeSpanJson = activeSpan && spanToJSON(activeSpan);
if (activeSpan && activeSpanJson && !activeSpanJson.end_timestamp) {
DEBUG_BUILD &&
debug.log(`[Tracing] Finishing current active span with op: ${activeSpanJson?.attributes[SENTRY_OP]}`);
// If there's an open active span, we need to finish it before creating an new one.
activeSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_IDLE_SPAN_FINISH_REASON, 'cancelled');
activeSpan.setAttribute(SENTRY_IDLE_SPAN_FINISH_REASON, 'cancelled');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Broken open-span end checks

High Severity

spanToJSON now returns StreamedSpanJSON, where end_timestamp is always set (_endTime ?? _startTime). Call sites still treat it like the old optional timestamp, so open-span checks such as !end_timestamp never succeed. That can leave idle spans unfinished, skip lazy-route name updates, and mis-detect whether a navigation span has ended. In updateNavigationSpan, the condition is also inverted from the previous !timestamp check.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 54994d4. Configure here.

activeSpan.end();
}
}
Expand Down Expand Up @@ -812,7 +814,7 @@ function registerInteractionListener(

const activeIdleSpan = getActiveIdleSpan(client);
if (activeIdleSpan) {
const currentRootSpanOp = spanToJSON(activeIdleSpan).op;
const currentRootSpanOp = spanToJSON(activeIdleSpan).attributes[SENTRY_OP];
if (['navigation', 'pageload'].includes(currentRootSpanOp as string)) {
DEBUG_BUILD &&
debug.warn(`[Tracing] Did not create ${op} span because a pageload or navigation span is in progress.`);
Expand Down
5 changes: 3 additions & 2 deletions packages/browser/src/tracing/linkedTraces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from '@sentry/core/browser';
import { DEBUG_BUILD } from '../debug-build';
import { WINDOW } from '../exports';
import { SENTRY_OP } from '@sentry/conventions/attributes';

export interface PreviousTraceInfo {
/**
Expand Down Expand Up @@ -142,7 +143,7 @@ export function addPreviousTraceSpanLink(
function getSampleRate(): number {
try {
const oldSampleRate = Number(
spanJson.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE] ?? oldPropagationContext.dsc?.sample_rate,
spanJson.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE] ?? oldPropagationContext.dsc?.sample_rate,
);
return Number.isNaN(oldSampleRate) ? 0 : oldSampleRate;
} catch {
Expand Down Expand Up @@ -178,7 +179,7 @@ export function addPreviousTraceSpanLink(
if (DEBUG_BUILD) {
debug.log(
`Adding previous_trace \`${JSON.stringify(previousTraceSpanCtx)}\` link to span \`${JSON.stringify({
op: spanJson.op,
op: spanJson.attributes[SENTRY_OP],
...span.spanContext(),
})}\``,
);
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/tracing/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ const HTTP_TIMING_WAIT_MS = 300;
* @param span A span that has yet to be finished, must contain `url.full` on data.
*/
function addHTTPTimings(span: Span, client: Client): void {
const url = spanToJSON(span).data[URL_FULL];
const url = spanToJSON(span).attributes[URL_FULL];

if (!url || typeof url !== 'string') {
return;
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/integrations/conversationId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { defineIntegration } from '../integration';
import { GEN_AI_CONVERSATION_ID_ATTRIBUTE } from '../semanticAttributes';
import type { IntegrationFn } from '../types/integration';
import type { Span } from '../types/span';
import { spanToJSON } from '../utils/spanUtils';
import { spanToStaticSpanJSON } from '../utils/spanUtils';

const INTEGRATION_NAME = 'ConversationId' as const;

Expand All @@ -19,7 +19,7 @@ const _conversationIdIntegration = (() => {
const conversationId = scopeData.conversationId || isolationScopeData.conversationId;

if (conversationId) {
const { op, data: attributes, description: name } = spanToJSON(span);
const { op, data: attributes, description: name } = spanToStaticSpanJSON(span);

// Only apply conversation ID to gen_ai spans.
// We also check for Vercel AI spans (ai.operationId attribute or ai.* span name)
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/shared-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type { OfflineStore, OfflineTransportOptions } from './transports/offline
export type { IntegrationIndex } from './integration';
export * from './tracing';
export * from './semanticAttributes';
export type { RawAttributes } from './attributes';
export { createEventEnvelope, createSessionEnvelope } from './envelope';
export {
captureException,
Expand Down Expand Up @@ -99,8 +100,8 @@ export { addAutoIpAddressToUser } from './utils/ipAddress';
export {
convertSpanLinksForEnvelope,
spanToTraceHeader,
spanToStaticSpanJSON,
spanToJSON,
spanToStreamedSpanJSON,
spanIsSampled,
spanIsSentrySpan,
spanToTraceContext,
Expand Down
Loading
Loading