Skip to content

Commit 3336ff3

Browse files
feat(webvitals): Add profile id, replay id, and user to standalone INP spans (#10849)
Adds profile id, replay id, and user to standalone INP spans. User comes from the current scope. Replay Id is retrieved from the relay integration module and calling getReplayId(). Profile Id is retrieved from getting the active transaction at the time of the interaction Since profile id isn't added to the transaction until the transaction ends, we need to hold onto a reference to the transaction instead of trying to grab the profile id right away
2 parents d943c8d + ac749fb commit 3336ff3

File tree

8 files changed

+70
-6
lines changed

8 files changed

+70
-6
lines changed

packages/browser/src/profiling/utils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,9 @@ export function createProfilingEvent(
583583
return createProfilePayload(profile_id, start_timestamp, profile, event);
584584
}
585585

586+
// TODO (v8): We need to obtain profile ids in @sentry-internal/tracing,
587+
// but we don't have access to this map because importing this map would
588+
// cause a circular dependancy. We need to resolve this in v8.
586589
const PROFILE_MAP: Map<string, JSSelfProfile> = new Map();
587590
/**
588591
*

packages/core/src/semanticAttributes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,8 @@ export const SEMANTIC_ATTRIBUTE_SENTRY_OP = 'sentry.op';
1919
* Use this attribute to represent the origin of a span.
2020
*/
2121
export const SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN = 'sentry.origin';
22+
23+
/**
24+
* The id of the profile that this span occured in.
25+
*/
26+
export const SEMANTIC_ATTRIBUTE_PROFILE_ID = 'profile_id';

packages/core/src/tracing/span.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ import { dropUndefinedKeys, logger, timestampInSeconds, uuid4 } from '@sentry/ut
1818

1919
import { DEBUG_BUILD } from '../debug-build';
2020
import { getMetricSummaryJsonForSpan } from '../metrics/metric-summary';
21-
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../semanticAttributes';
21+
import {
22+
SEMANTIC_ATTRIBUTE_PROFILE_ID,
23+
SEMANTIC_ATTRIBUTE_SENTRY_OP,
24+
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
25+
} from '../semanticAttributes';
2226
import { getRootSpan } from '../utils/getRootSpan';
2327
import {
2428
TRACE_FLAG_NONE,
@@ -634,6 +638,7 @@ export class Span implements SpanInterface {
634638
trace_id: this._traceId,
635639
origin: this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] as SpanOrigin | undefined,
636640
_metrics_summary: getMetricSummaryJsonForSpan(this),
641+
profile_id: this._attributes[SEMANTIC_ATTRIBUTE_PROFILE_ID] as string | undefined,
637642
exclusive_time: this._exclusiveTime,
638643
measurements: Object.keys(this._measurements).length > 0 ? this._measurements : undefined,
639644
});

packages/core/src/tracing/transaction.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,16 @@ export class Transaction extends SpanClass implements TransactionInterface {
254254
this._hub = hub;
255255
}
256256

257+
/**
258+
* Get the profile id of the transaction.
259+
*/
260+
public getProfileId(): string | undefined {
261+
if (this._contexts !== undefined && this._contexts['profile'] !== undefined) {
262+
return this._contexts['profile'].profile_id as string;
263+
}
264+
return undefined;
265+
}
266+
257267
/**
258268
* Finish the transaction & prepare the event to send to Sentry.
259269
*/

packages/tracing-internal/src/browser/browserTracingIntegration.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable max-lines */
22
import type { IdleTransaction } from '@sentry/core';
3-
import { getActiveSpan } from '@sentry/core';
3+
import { getActiveSpan, getClient, getCurrentScope } from '@sentry/core';
44
import { getCurrentHub } from '@sentry/core';
55
import {
66
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
@@ -12,6 +12,7 @@ import {
1212
} from '@sentry/core';
1313
import type {
1414
Client,
15+
Integration,
1516
IntegrationFn,
1617
StartSpanOptions,
1718
Transaction,
@@ -539,6 +540,18 @@ function registerInpInteractionListener(
539540
},
540541
): void {
541542
addPerformanceInstrumentationHandler('event', ({ entries }) => {
543+
const client = getClient();
544+
// We need to get the replay, user, and activeTransaction from the current scope
545+
// so that we can associate replay id, profile id, and a user display to the span
546+
const replay =
547+
client !== undefined && client.getIntegrationByName !== undefined
548+
? (client.getIntegrationByName('Replay') as Integration & { getReplayId: () => string })
549+
: undefined;
550+
const replayId = replay !== undefined ? replay.getReplayId() : undefined;
551+
// eslint-disable-next-line deprecation/deprecation
552+
const activeTransaction = getActiveTransaction();
553+
const currentScope = getCurrentScope();
554+
const user = currentScope !== undefined ? currentScope.getUser() : undefined;
542555
for (const entry of entries) {
543556
if (isPerformanceEventTiming(entry)) {
544557
const duration = entry.duration;
@@ -564,6 +577,9 @@ function registerInpInteractionListener(
564577
routeName,
565578
duration,
566579
parentContext,
580+
user,
581+
activeTransaction,
582+
replayId,
567583
};
568584
}
569585
}

packages/tracing-internal/src/browser/metrics/index.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,10 +213,19 @@ function _trackINP(interactionIdtoRouteNameMapping: InteractionRouteNameMapping)
213213
/** Build the INP span, create an envelope from the span, and then send the envelope */
214214
const startTime = msToSec((browserPerformanceTimeOrigin as number) + entry.startTime);
215215
const duration = msToSec(metric.value);
216-
const { routeName, parentContext } =
216+
const { routeName, parentContext, activeTransaction, user, replayId } =
217217
entry.interactionId !== undefined
218218
? interactionIdtoRouteNameMapping[entry.interactionId]
219-
: { routeName: undefined, parentContext: undefined };
219+
: {
220+
routeName: undefined,
221+
parentContext: undefined,
222+
activeTransaction: undefined,
223+
user: undefined,
224+
replayId: undefined,
225+
};
226+
const userDisplay = user !== undefined ? user.email || user.id || user.ip_address : undefined;
227+
// eslint-disable-next-line deprecation/deprecation
228+
const profileId = activeTransaction !== undefined ? activeTransaction.getProfileId() : undefined;
220229
const span = new Span({
221230
startTimestamp: startTime,
222231
endTimestamp: startTime + duration,
@@ -226,6 +235,9 @@ function _trackINP(interactionIdtoRouteNameMapping: InteractionRouteNameMapping)
226235
release: options.release,
227236
environment: options.environment,
228237
transaction: routeName,
238+
...(userDisplay !== undefined && userDisplay !== '' ? { user: userDisplay } : {}),
239+
...(profileId !== undefined ? { profile_id: profileId } : {}),
240+
...(replayId !== undefined ? { replay_id: replayId } : {}),
229241
},
230242
exclusiveTime: metric.value,
231243
measurements: {

packages/tracing-internal/src/browser/web-vitals/types.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import type { TransactionContext } from '@sentry/types';
17+
import type { Transaction, TransactionContext, User } from '@sentry/types';
1818
import type { FirstInputPolyfillCallback } from './types/polyfills';
1919

2020
export * from './types/base';
@@ -165,5 +165,12 @@ declare global {
165165
}
166166

167167
export type InteractionRouteNameMapping = {
168-
[key: string]: { routeName: string; duration: number; parentContext: TransactionContext };
168+
[key: string]: {
169+
routeName: string;
170+
duration: number;
171+
parentContext: TransactionContext;
172+
user?: User;
173+
activeTransaction?: Transaction;
174+
replayId?: string;
175+
};
169176
};

packages/types/src/transaction.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ export interface Transaction extends TransactionContext, Omit<Span, 'setName' |
152152
* @deprecated Use top-level `getDynamicSamplingContextFromSpan` instead.
153153
*/
154154
getDynamicSamplingContext(): Partial<DynamicSamplingContext>;
155+
156+
/**
157+
* Get the profile id from the transaction
158+
* @deprecated Use `toJSON()` or access the fields directly instead.
159+
*/
160+
getProfileId(): string | undefined;
155161
}
156162

157163
/**

0 commit comments

Comments
 (0)