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
4 changes: 3 additions & 1 deletion dev-packages/node-integration-tests/utils/assertions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import type {
SerializedSession,
SerializedStreamedSpanContainer,
SessionAggregates,
TransactionEvent,
} from '@sentry/core';
import { SDK_VERSION } from '@sentry/core';
import { expect } from 'vitest';

/** The SDK no longer emits transaction events; kept so legacy assertions still compile. */
type TransactionEvent = Event;

export type DeepPartial<T> = T extends object
? {
[P in keyof T]?: DeepPartial<T[P]>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import type {
SerializedSession,
SerializedStreamedSpanContainer,
SessionAggregates,
TransactionEvent,
} from '@sentry/core';
import { normalize } from '@sentry/core';
import { createBasicSentryServer } from '@sentry-internal/test-utils';
Expand All @@ -20,6 +19,10 @@ import { tmpdir } from 'os';
import { join } from 'path';
import { inspect } from 'util';
import type { DeepPartial } from './../assertions';

/** The SDK no longer emits transaction events; kept so legacy assertions still compile. */
type TransactionEvent = Event;

import {
assertEnvelopeHeader,
assertSentryCheckIn,
Expand Down
22 changes: 0 additions & 22 deletions dev-packages/test-utils/src/event-proxy-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,28 +378,6 @@ export function waitForSession(
});
}

/** Wait for a transaction to be sent. */
export function waitForTransaction(
proxyServerName: string,
callback: (transactionEvent: Event) => Promise<boolean> | boolean,
): Promise<Event> {
const timestamp = getNanosecondTimestamp();
return new Promise((resolve, reject) => {
waitForEnvelopeItem(
proxyServerName,
async envelopeItem => {
const [envelopeItemHeader, envelopeItemBody] = envelopeItem;
if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as Event))) {
resolve(envelopeItemBody as Event);
return true;
}
return false;
},
timestamp,
).catch(reject);
});
}

/**
* Wait for metric items to be sent.
*/
Expand Down
1 change: 0 additions & 1 deletion dev-packages/test-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ export {
waitForEnvelopeItem,
waitForError,
waitForRequest,
waitForTransaction,
waitForSession,
waitForPlainRequest,
waitForMetric,
Expand Down
4 changes: 0 additions & 4 deletions packages/astro/src/index.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ export {
setExtra,
setExtras,
setHttpStatus,
setMeasurement,
setTag,
setTags,
setAttribute,
Expand Down Expand Up @@ -172,9 +171,6 @@ export {
unleashIntegration,
growthbookIntegration,
spanStreamingIntegration,
withStaticSpan,
// oxlint-disable-next-line typescript/no-deprecated
withStreamedSpan,
metrics,
} from '@sentry/node';

Expand Down
2 changes: 0 additions & 2 deletions packages/astro/src/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ 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[];
export declare const defaultStackParser: StackParser;
Expand Down
4 changes: 0 additions & 4 deletions packages/aws-serverless/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ export {
functionToStringIntegration,
eventFiltersIntegration,
linkedErrorsIntegration,
setMeasurement,
getActiveSpan,
startSpan,
startInactiveSpan,
Expand Down Expand Up @@ -159,9 +158,6 @@ export {
growthbookIntegration,
metrics,
spanStreamingIntegration,
withStaticSpan,
// oxlint-disable-next-line typescript/no-deprecated
withStreamedSpan,
} from '@sentry/node';

export {
Expand Down
46 changes: 10 additions & 36 deletions packages/browser-utils/src/performance/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
getComponentName,
parseUrl,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
setMeasurement,
spanToJSON,
filterCollectedUrl,
} from '@sentry/core';
Expand Down Expand Up @@ -198,14 +197,9 @@ interface AddPerformanceEntriesOptions {
* Default: []
*/
ignoreResourceSpans: Array<'resource.script' | 'resource.css' | 'resource.img' | 'resource.other' | string>;

/**
* Whether span streaming is enabled.
*/
spanStreamingEnabled?: boolean;
}

/** Add performance related spans to a transaction */
/** Add performance related spans to the pageload/navigation span */
export function addPerformanceEntries(span: Span, options: AddPerformanceEntriesOptions): void {
const performance = getBrowserPerformanceAPI();
const origin = browserPerformanceTimeOrigin();
Expand All @@ -214,7 +208,7 @@ export function addPerformanceEntries(span: Span, options: AddPerformanceEntries
return;
}

const { spanStreamingEnabled, ignoreResourceSpans } = options;
const { ignoreResourceSpans } = options;

const timeOrigin = msToSec(origin);

Expand Down Expand Up @@ -263,7 +257,7 @@ export function addPerformanceEntries(span: Span, options: AddPerformanceEntries

_performanceCursor = Math.max(performanceEntries.length - 1, 0);

_trackNavigator(span, spanStreamingEnabled);
_trackNavigator(span);
}

/** Create a span for a browser paint performance entry. */
Expand Down Expand Up @@ -452,11 +446,8 @@ export function _addResourceSpans(
});
}

/**
* Capture the information of the user agent.
* TODO v11: Remove non-span-streaming attributes and measurements once we removed transactions
*/
function _trackNavigator(span: Span, spanStreamingEnabled: boolean | undefined): void {
/** Capture the information of the user agent. */
function _trackNavigator(span: Span): void {
const navigator = WINDOW.navigator as null | (Navigator & NavigatorNetworkInformation & NavigatorDeviceMemory);
if (!navigator) {
return;
Expand All @@ -466,41 +457,24 @@ function _trackNavigator(span: Span, spanStreamingEnabled: boolean | undefined):
const connection = navigator.connection;
if (connection) {
if (connection.effectiveType) {
span.setAttribute(
spanStreamingEnabled ? 'network.connection.effective_type' : 'effectiveConnectionType',
connection.effectiveType,
);
span.setAttribute('network.connection.effective_type', connection.effectiveType);
}

if (connection.type) {
span.setAttribute(spanStreamingEnabled ? 'network.connection.type' : 'connectionType', connection.type);
span.setAttribute('network.connection.type', connection.type);
}

if (isMeasurementValue(connection.rtt)) {
if (spanStreamingEnabled) {
span.setAttribute('network.connection.rtt', connection.rtt);
} else if (spanToJSON(span).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');
}
span.setAttribute('network.connection.rtt', connection.rtt);
}
}

if (isMeasurementValue(navigator.deviceMemory)) {
if (spanStreamingEnabled) {
span.setAttribute('device.memory.estimated_capacity', navigator.deviceMemory);
} else {
span.setAttribute('deviceMemory', `${navigator.deviceMemory} GB`);
}
span.setAttribute('device.memory.estimated_capacity', navigator.deviceMemory);
}

if (isMeasurementValue(navigator.hardwareConcurrency)) {
if (spanStreamingEnabled) {
span.setAttribute('device.processor_count', navigator.hardwareConcurrency);
} else {
span.setAttribute('hardwareConcurrency', String(navigator.hardwareConcurrency));
}
span.setAttribute('device.processor_count', navigator.hardwareConcurrency);
}
}

Expand Down
61 changes: 5 additions & 56 deletions packages/browser-utils/src/web-vitals/spans.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import type { Client, Integration, Span, SpanAttributes } from '@sentry/core';
import type { Client, Span, SpanAttributes } from '@sentry/core';
import {
browserPerformanceTimeOrigin,
debug,
getActiveSpan,
getClient,
getCurrentScope,
getRootSpan,
hasSpanStreamingEnabled,
SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME,
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
Expand Down Expand Up @@ -61,19 +59,10 @@ interface WebVitalSpanOptions {
reportEvent?: WebVitalReportEvent;
startTime: number;
endTime?: number;
/**
* When `true`, the span is sent on its own as a v2 streamed span instead of being folded into a
* transaction. Used for INP when span streaming is disabled (it reports late, so it can't ride
* the pageload transaction).
*
* TODO(standalone): remove once the static (transaction) trace lifecycle is dropped and INP always streams.
*/
standalone?: boolean;
}

/**
* Emits a web vital span. When `standalone` is set it is sent on its own as a v2 streamed span;
* otherwise it flows through the span streaming pipeline as a child of `parentSpan`.
* Emits a web vital span, flowing through the span streaming pipeline as a child of `parentSpan`.
*/
export function _emitWebVitalSpan(options: WebVitalSpanOptions): void {
const {
Expand All @@ -87,7 +76,6 @@ export function _emitWebVitalSpan(options: WebVitalSpanOptions): void {
reportEvent,
startTime,
endTime,
standalone,
} = options;

const routeName = getCurrentScope().getScopeData().transactionName;
Expand All @@ -114,49 +102,18 @@ export function _emitWebVitalSpan(options: WebVitalSpanOptions): void {
attributes[`browser.web_vital.${metricName}.report_event`] = reportEvent;
}

// A standalone span is sent as a plain v2 span without running the `processSpan` hooks (see
// `captureStandaloneSpanWithStaticCallback`), so Replay can't attach the replay id itself. Set it
// here, mirroring Replay's `processSpan`, so INP keeps its replay association like it did on v1.
// TODO(standalone): remove once the static (transaction) trace lifecycle is dropped and INP always
// streams, at which point Replay's `processSpan` runs and attaches the replay id.
if (standalone) {
Object.assign(attributes, getReplayAttributes());
}

const span = startInactiveSpan({
name,
attributes,
startTime,
parentSpan,
// oxlint-disable-next-line typescript/no-deprecated -- intentional during the v1/v2 transition; see the TODO(standalone) above
experimental: standalone ? { standalone: true } : undefined,
});

if (span) {
span.end(endTime ?? startTime);
}
}

interface ReplayIntegration extends Integration {
getReplayId: (onlyIfSampled?: boolean) => string | undefined;
getRecordingMode: () => 'session' | 'buffer' | undefined;
}

// TODO(standalone): remove once the static (transaction) trace lifecycle is dropped; Replay's
// `processSpan` then attaches the replay id to the streamed INP span instead.
function getReplayAttributes(): SpanAttributes {
const replay = getClient()?.getIntegrationByName<ReplayIntegration>('Replay');
const replayId = replay?.getReplayId(true);
if (!replayId) {
return {};
}

return {
'sentry.replay_id': replayId,
'sentry._internal.replay_is_buffering': replay!.getRecordingMode() === 'buffer' ? true : undefined,
};
}

/**
* Tracks LCP as a streamed span.
*/
Expand Down Expand Up @@ -292,19 +249,12 @@ export function _sendClsSpan(
* Requires `registerInpInteractionListener()` to be called separately for cached element names and
* root spans per interaction.
*/
export function trackInpAsSpan(client: Client): void {
export function trackInpAsSpan(): void {
const performance = getBrowserPerformanceAPI();
if (!performance || !browserPerformanceTimeOrigin()) {
return;
}

// INP reports late (on pagehide, after the pageload span has ended). With span streaming enabled
// it rides the streaming pipeline. With streaming disabled it would be dropped as a late span, so
// it is emitted as its own standalone v2 span instead (see `_emitWebVitalSpan`), overriding the
// static trace lifecycle for INP only.
// TODO(standalone): once the static trace lifecycle is dropped, INP always streams; drop this flag.
const standalone = !hasSpanStreamingEnabled(client);

const onInp: InstrumentationHandlerCallback = ({ metric }) => {
if (metric.value == null) {
return;
Expand All @@ -322,7 +272,7 @@ export function trackInpAsSpan(client: Client): void {
return;
}

_sendInpSpan(metric.value, entry, standalone);
_sendInpSpan(metric.value, entry);
};

addInpInstrumentationHandler(onInp);
Expand All @@ -331,7 +281,7 @@ export function trackInpAsSpan(client: Client): void {
/**
* Exported only for testing.
*/
export function _sendInpSpan(inpValue: number, entry: PerformanceEventTiming, standalone = false): void {
export function _sendInpSpan(inpValue: number, entry: PerformanceEventTiming): void {
DEBUG_BUILD && debug.log(`Sending INP span (${inpValue})`);

const startTime = msToSec((browserPerformanceTimeOrigin() as number) + entry.startTime);
Expand Down Expand Up @@ -363,6 +313,5 @@ export function _sendInpSpan(inpValue: number, entry: PerformanceEventTiming, st
startTime,
endTime: startTime + duration,
parentSpan: spanToUse,
standalone,
});
}
Loading