Skip to content

Commit 4ec6389

Browse files
committed
feat: Add replay_id to transaction DSC
1 parent 21dd20d commit 4ec6389

File tree

7 files changed

+88
-9
lines changed

7 files changed

+88
-9
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import * as Sentry from '@sentry/browser';
2+
import { Integrations } from '@sentry/tracing';
3+
4+
window.Sentry = Sentry;
5+
window.Replay = new Sentry.Replay({
6+
flushMinDelay: 200,
7+
flushMaxDelay: 200,
8+
});
9+
10+
Sentry.init({
11+
dsn: 'https://[email protected]/1337',
12+
integrations: [new Integrations.BrowserTracing({ tracingOrigins: [/.*/] }), window.Replay],
13+
environment: 'production',
14+
tracesSampleRate: 1,
15+
replaysSessionSampleRate: 0.0,
16+
replaysOnErrorSampleRate: 1.0,
17+
debug: true,
18+
});
19+
20+
Sentry.configureScope(scope => {
21+
scope.setUser({ id: 'user123', segment: 'segmentB' });
22+
scope.setTransactionName('testTransactionDSC');
23+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { expect } from '@playwright/test';
2+
import type { EventEnvelopeHeaders } from '@sentry/types';
3+
4+
import { sentryTest } from '../../../utils/fixtures';
5+
import { envelopeHeaderRequestParser, getFirstSentryEnvelopeRequest } from '../../../utils/helpers';
6+
import { getReplaySnapshot } from '../../../utils/replayHelpers';
7+
8+
sentryTest('should add replay_id to dsc of transactions', async ({ getLocalTestPath, page }) => {
9+
const url = await getLocalTestPath({ testDir: __dirname });
10+
await page.goto(url);
11+
12+
const replay = await getReplaySnapshot(page);
13+
14+
expect(replay.session?.id).toBeDefined();
15+
16+
const envHeader = await getFirstSentryEnvelopeRequest<EventEnvelopeHeaders>(page, url, envelopeHeaderRequestParser);
17+
18+
expect(envHeader.trace).toBeDefined();
19+
expect(envHeader.trace).toEqual({
20+
environment: 'production',
21+
user_segment: 'segmentB',
22+
sample_rate: '1',
23+
trace_id: expect.any(String),
24+
public_key: 'public',
25+
replay_id: replay.session?.id,
26+
});
27+
});

packages/core/src/baseclient.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
ClientOptions,
77
DataCategory,
88
DsnComponents,
9+
DynamicSamplingContext,
910
Envelope,
1011
ErrorEvent,
1112
Event,
@@ -378,6 +379,9 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
378379
/** @inheritdoc */
379380
public on(hook: 'beforeAddBreadcrumb', callback: (breadcrumb: Breadcrumb, hint?: BreadcrumbHint) => void): void;
380381

382+
/** @inheritdoc */
383+
public on(hook: 'createDsc', callback: (dsc: DynamicSamplingContext) => void): void;
384+
381385
/** @inheritdoc */
382386
public on(hook: string, callback: unknown): void {
383387
if (!this._hooks[hook]) {
@@ -400,6 +404,9 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
400404
/** @inheritdoc */
401405
public emit(hook: 'beforeAddBreadcrumb', breadcrumb: Breadcrumb, hint?: BreadcrumbHint): void;
402406

407+
/** @inheritdoc */
408+
public emit(hook: 'createDsc', dsc: DynamicSamplingContext): void;
409+
403410
/** @inheritdoc */
404411
public emit(hook: string, ...rest: unknown[]): void {
405412
if (this._hooks[hook]) {

packages/core/src/tracing/transaction.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,8 @@ export class Transaction extends SpanClass implements TransactionInterface {
276276
// Uncomment if we want to make DSC immutable
277277
// this._frozenDynamicSamplingContext = dsc;
278278

279+
client.emit && client.emit('createDsc', dsc);
280+
279281
return dsc;
280282
}
281283
}

packages/replay/src/util/addGlobalListeners.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { BaseClient } from '@sentry/core';
22
import { addGlobalEventProcessor, getCurrentHub } from '@sentry/core';
3+
import type { Client, DynamicSamplingContext } from '@sentry/types';
34
import { addInstrumentationHandler } from '@sentry/utils';
45

56
import { handleAfterSendEvent } from '../coreHandlers/handleAfterSendEvent';
@@ -25,15 +26,23 @@ export function addGlobalListeners(replay: ReplayContainer): void {
2526
addInstrumentationHandler('history', handleHistorySpanListener(replay));
2627
handleNetworkBreadcrumbs(replay);
2728

28-
// If a custom client has no hooks yet, we continue to use the "old" implementation
29-
const hasHooks = !!(client && client.on);
30-
3129
// Tag all (non replay) events that get sent to Sentry with the current
3230
// replay ID so that we can reference them later in the UI
33-
addGlobalEventProcessor(handleGlobalEventListener(replay, !hasHooks));
31+
addGlobalEventProcessor(handleGlobalEventListener(replay, !hasHooks(client)));
3432

35-
if (hasHooks) {
36-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
37-
(client as BaseClient<any>).on('afterSendEvent', handleAfterSendEvent(replay));
33+
// If a custom client has no hooks yet, we continue to use the "old" implementation
34+
if (hasHooks(client)) {
35+
client.on('afterSendEvent', handleAfterSendEvent(replay));
36+
client.on('createDsc', (dsc: DynamicSamplingContext) => {
37+
const replayId = replay.getSessionId();
38+
if (replayId) {
39+
dsc.replay_id = replayId;
40+
}
41+
});
3842
}
3943
}
44+
45+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
46+
function hasHooks(client: Client | undefined): client is BaseClient<any> {
47+
return !!(client && client.on);
48+
}

packages/types/src/client.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { Breadcrumb, BreadcrumbHint } from './breadcrumb';
22
import type { EventDropReason } from './clientreport';
33
import type { DataCategory } from './datacategory';
44
import type { DsnComponents } from './dsn';
5-
import type { Envelope } from './envelope';
5+
import type { DynamicSamplingContext, Envelope } from './envelope';
66
import type { Event, EventHint } from './event';
77
import type { Integration, IntegrationClass } from './integration';
88
import type { ClientOptions } from './options';
@@ -177,6 +177,11 @@ export interface Client<O extends ClientOptions = ClientOptions> {
177177
*/
178178
on?(hook: 'beforeAddBreadcrumb', callback: (breadcrumb: Breadcrumb, hint?: BreadcrumbHint) => void): void;
179179

180+
/**
181+
* Register a callback whena DSC (Dynamic Sampling Context) is created.
182+
*/
183+
on?(hook: 'createDsc', callback: (dsc: DynamicSamplingContext) => void): void;
184+
180185
/**
181186
* Fire a hook event for transaction start and finish. Expects to be given a transaction as the
182187
* second argument.
@@ -196,7 +201,12 @@ export interface Client<O extends ClientOptions = ClientOptions> {
196201
emit?(hook: 'afterSendEvent', event: Event, sendResponse: TransportMakeRequestResponse | void): void;
197202

198203
/**
199-
* Fire a hook for when a bredacrumb is added. Expects the breadcrumb as second argument.
204+
* Fire a hook for when a breadcrumb is added. Expects the breadcrumb as second argument.
200205
*/
201206
emit?(hook: 'beforeAddBreadcrumb', breadcrumb: Breadcrumb, hint?: BreadcrumbHint): void;
207+
208+
/**
209+
* Fire a hook for when a DSC (Dynamic Sampling Context) is created. Expects the DSC as second argument.
210+
*/
211+
emit?(hook: 'createDsc', dsc: DynamicSamplingContext): void;
202212
}

packages/types/src/envelope.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export type DynamicSamplingContext = {
1818
environment?: string;
1919
transaction?: string;
2020
user_segment?: string;
21+
replay_id?: string;
2122
};
2223

2324
export type EnvelopeItemType =

0 commit comments

Comments
 (0)