Skip to content

Commit 347fbe4

Browse files
authored
ref: Make dynamic sampling context mutable (#5710)
This PR makes Dynamic Sampling Context in Head-SDKs mutable. List of changes: - Remove our `Baggage` abstraction (keeping track of foreign baggage and mutability is not necessary) - Replace `Baggage` abstraction with functions that convert `baggage` headers into DSC and vice-versa - Replace `getBaggage()` method on the `Transaction` class with `getDynamicSamplingContext()` which doesn't freeze DSC upon retrieval - Refactor the adding of Sentry `baggage` items so that we're (almost) always adding an additional independent `baggage` header, in addition to any that were already there. This was done to reduce code complexity and bundle size. An exception here is in the browser where there is a case where I don't know if adding an additional header might break things. - Explicitly handle the case of multiple incoming baggage headers. (if there are multiple items that are named the same, last one always wins) - Freeze DSC in the transaction creation if DSC was provided via `transactionContext.metadata.dynamicSamplingContext` - used in non-head-SDKs - Fix `extractTraceparentData` so that it returns `undefined` (ie. "invalid") when passed an empty string - also added a test for that.
1 parent e26e43a commit 347fbe4

File tree

37 files changed

+502
-855
lines changed

37 files changed

+502
-855
lines changed

packages/core/src/envelope.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import {
2-
Baggage,
32
DsnComponents,
4-
DynamicSamplingContext,
53
Event,
64
EventEnvelope,
75
EventEnvelopeHeaders,
@@ -13,7 +11,7 @@ import {
1311
SessionEnvelope,
1412
SessionItem,
1513
} from '@sentry/types';
16-
import { createEnvelope, dropUndefinedKeys, dsnToString, getSentryBaggageItems } from '@sentry/utils';
14+
import { createEnvelope, dropUndefinedKeys, dsnToString } from '@sentry/utils';
1715

1816
/** Extract sdk info from from the API metadata */
1917
function getSdkMetadataForEnvelopeHeader(metadata?: SdkMetadata): SdkInfo | undefined {
@@ -101,8 +99,7 @@ function createEventEnvelopeHeaders(
10199
tunnel: string | undefined,
102100
dsn: DsnComponents,
103101
): EventEnvelopeHeaders {
104-
const baggage: Baggage | undefined = event.sdkProcessingMetadata && event.sdkProcessingMetadata.baggage;
105-
const dynamicSamplingContext = baggage && getSentryBaggageItems(baggage);
102+
const dynamicSamplingContext = event.sdkProcessingMetadata && event.sdkProcessingMetadata.dynamicSamplingContext;
106103

107104
return {
108105
event_id: event.event_id as string,
@@ -111,7 +108,7 @@ function createEventEnvelopeHeaders(
111108
...(!!tunnel && { dsn: dsnToString(dsn) }),
112109
...(event.type === 'transaction' &&
113110
dynamicSamplingContext && {
114-
trace: dropUndefinedKeys({ ...dynamicSamplingContext }) as DynamicSamplingContext,
111+
trace: dropUndefinedKeys({ ...dynamicSamplingContext }),
115112
}),
116113
};
117114
}

packages/core/test/lib/envelope.test.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ describe('createEventEnvelope', () => {
2020
{
2121
type: 'transaction',
2222
sdkProcessingMetadata: {
23-
baggage: [{ trace_id: '1234', public_key: 'pubKey123' }, '', false],
23+
dynamicSamplingContext: { trace_id: '1234', public_key: 'pubKey123' },
2424
},
2525
},
2626
{ trace_id: '1234', public_key: 'pubKey123' },
@@ -30,7 +30,12 @@ describe('createEventEnvelope', () => {
3030
{
3131
type: 'transaction',
3232
sdkProcessingMetadata: {
33-
baggage: [{ environment: 'prod', release: '1.0.0', public_key: 'pubKey123', trace_id: '1234' }, '', false],
33+
dynamicSamplingContext: {
34+
environment: 'prod',
35+
release: '1.0.0',
36+
public_key: 'pubKey123',
37+
trace_id: '1234',
38+
},
3439
},
3540
},
3641
{ release: '1.0.0', environment: 'prod', trace_id: '1234', public_key: 'pubKey123' },
@@ -40,19 +45,15 @@ describe('createEventEnvelope', () => {
4045
{
4146
type: 'transaction',
4247
sdkProcessingMetadata: {
43-
baggage: [
44-
{
45-
environment: 'prod',
46-
release: '1.0.0',
47-
transaction: 'TX',
48-
user_segment: 'segmentA',
49-
sample_rate: '0.95',
50-
public_key: 'pubKey123',
51-
trace_id: '1234',
52-
},
53-
'',
54-
false,
55-
],
48+
dynamicSamplingContext: {
49+
environment: 'prod',
50+
release: '1.0.0',
51+
transaction: 'TX',
52+
user_segment: 'segmentA',
53+
sample_rate: '0.95',
54+
public_key: 'pubKey123',
55+
trace_id: '1234',
56+
},
5657
},
5758
},
5859
{

packages/nextjs/src/config/wrappers/withSentryGetServerSideProps.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { hasTracingEnabled } from '@sentry/tracing';
2-
import { serializeBaggage } from '@sentry/utils';
2+
import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils';
33
import { GetServerSideProps } from 'next';
44

55
import { isBuild } from '../../utils/isBuild';
@@ -45,7 +45,9 @@ export function withSentryGetServerSideProps(
4545
const requestTransaction = getTransactionFromRequest(req);
4646
if (requestTransaction) {
4747
serverSideProps.props._sentryTraceData = requestTransaction.toTraceparent();
48-
serverSideProps.props._sentryBaggage = serializeBaggage(requestTransaction.getBaggage());
48+
49+
const dynamicSamplingContext = requestTransaction.getDynamicSamplingContext();
50+
serverSideProps.props._sentryBaggage = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
4951
}
5052
}
5153

packages/nextjs/src/config/wrappers/withSentryServerSideAppGetInitialProps.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { hasTracingEnabled } from '@sentry/tracing';
2-
import { serializeBaggage } from '@sentry/utils';
2+
import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils';
33
import App from 'next/app';
44

55
import { isBuild } from '../../utils/isBuild';
@@ -51,7 +51,10 @@ export function withSentryServerSideAppGetInitialProps(origAppGetInitialProps: A
5151
const requestTransaction = getTransactionFromRequest(req!);
5252
if (requestTransaction) {
5353
appGetInitialProps.pageProps._sentryTraceData = requestTransaction.toTraceparent();
54-
appGetInitialProps.pageProps._sentryBaggage = serializeBaggage(requestTransaction.getBaggage());
54+
55+
const dynamicSamplingContext = requestTransaction.getDynamicSamplingContext();
56+
appGetInitialProps.pageProps._sentryBaggage =
57+
dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
5558
}
5659

5760
return appGetInitialProps;

packages/nextjs/src/config/wrappers/withSentryServerSideErrorGetInitialProps.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { hasTracingEnabled } from '@sentry/tracing';
2-
import { serializeBaggage } from '@sentry/utils';
2+
import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils';
33
import { NextPageContext } from 'next';
44
import { ErrorProps } from 'next/error';
55

@@ -52,7 +52,9 @@ export function withSentryServerSideErrorGetInitialProps(
5252
const requestTransaction = getTransactionFromRequest(req!);
5353
if (requestTransaction) {
5454
errorGetInitialProps._sentryTraceData = requestTransaction.toTraceparent();
55-
errorGetInitialProps._sentryBaggage = serializeBaggage(requestTransaction.getBaggage());
55+
56+
const dynamicSamplingContext = requestTransaction.getDynamicSamplingContext();
57+
errorGetInitialProps._sentryBaggage = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
5658
}
5759

5860
return errorGetInitialProps;

packages/nextjs/src/config/wrappers/withSentryServerSideGetInitialProps.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { hasTracingEnabled } from '@sentry/tracing';
2-
import { serializeBaggage } from '@sentry/utils';
2+
import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils';
33
import { NextPage } from 'next';
44

55
import { isBuild } from '../../utils/isBuild';
@@ -42,7 +42,9 @@ export function withSentryServerSideGetInitialProps(origGetInitialProps: GetInit
4242
const requestTransaction = getTransactionFromRequest(req!);
4343
if (requestTransaction) {
4444
initialProps._sentryTraceData = requestTransaction.toTraceparent();
45-
initialProps._sentryBaggage = serializeBaggage(requestTransaction.getBaggage());
45+
46+
const dynamicSamplingContext = requestTransaction.getDynamicSamplingContext();
47+
initialProps._sentryBaggage = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
4648
}
4749

4850
return initialProps;

packages/nextjs/src/config/wrappers/wrapperUtils.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { captureException, getCurrentHub, startTransaction } from '@sentry/core'
22
import { addRequestDataToEvent } from '@sentry/node';
33
import { getActiveTransaction } from '@sentry/tracing';
44
import { Transaction } from '@sentry/types';
5-
import { extractTraceparentData, fill, isString, parseBaggageSetMutability } from '@sentry/utils';
5+
import { baggageHeaderToDynamicSamplingContext, extractTraceparentData, fill } from '@sentry/utils';
66
import * as domain from 'domain';
77
import { IncomingMessage, ServerResponse } from 'http';
88

@@ -88,19 +88,19 @@ export function callTracedServerSideDataFetcher<F extends (...args: any[]) => Pr
8888

8989
if (requestTransaction === undefined) {
9090
const sentryTraceHeader = req.headers['sentry-trace'];
91-
const rawBaggageString = req.headers && isString(req.headers.baggage) && req.headers.baggage;
91+
const rawBaggageString = req.headers && req.headers.baggage;
9292
const traceparentData =
9393
typeof sentryTraceHeader === 'string' ? extractTraceparentData(sentryTraceHeader) : undefined;
9494

95-
const baggage = parseBaggageSetMutability(rawBaggageString, traceparentData);
95+
const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(rawBaggageString);
9696

9797
const newTransaction = startTransaction({
9898
op: 'nextjs.data.server',
9999
name: options.requestedRouteName,
100100
...traceparentData,
101101
metadata: {
102102
source: 'route',
103-
baggage,
103+
dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext,
104104
},
105105
});
106106

packages/nextjs/src/performance/client.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { getCurrentHub } from '@sentry/hub';
22
import { Primitive, TraceparentData, Transaction, TransactionContext, TransactionSource } from '@sentry/types';
33
import {
4+
baggageHeaderToDynamicSamplingContext,
45
extractTraceparentData,
56
getGlobalObject,
67
logger,
7-
parseBaggageHeader,
88
stripUrlQueryAndFragment,
99
} from '@sentry/utils';
1010
import type { NEXT_DATA as NextData } from 'next/dist/next-server/lib/utils';
@@ -128,6 +128,7 @@ export function nextRouterInstrumentation(
128128

129129
if (startTransactionOnPageLoad) {
130130
const source = route ? 'route' : 'url';
131+
const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(baggage);
131132

132133
activeTransaction = startTransactionCb({
133134
name: prevLocationName,
@@ -137,7 +138,7 @@ export function nextRouterInstrumentation(
137138
...traceParentData,
138139
metadata: {
139140
source,
140-
...(baggage && { baggage: parseBaggageHeader(baggage) }),
141+
dynamicSamplingContext: traceParentData && !dynamicSamplingContext ? {} : dynamicSamplingContext,
141142
},
142143
});
143144
}

packages/nextjs/src/utils/instrumentServer.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ import {
1010
import { extractTraceparentData, getActiveTransaction, hasTracingEnabled } from '@sentry/tracing';
1111
import {
1212
addExceptionMechanism,
13+
baggageHeaderToDynamicSamplingContext,
1314
fill,
1415
isString,
1516
logger,
16-
parseBaggageSetMutability,
1717
stripUrlQueryAndFragment,
1818
} from '@sentry/utils';
1919
import * as domain from 'domain';
@@ -255,8 +255,8 @@ function makeWrappedReqHandler(origReqHandler: ReqHandler): WrappedReqHandler {
255255
__DEBUG_BUILD__ && logger.log(`[Tracing] Continuing trace ${traceparentData?.traceId}.`);
256256
}
257257

258-
const rawBaggageString = nextReq.headers && isString(nextReq.headers.baggage) && nextReq.headers.baggage;
259-
const baggage = parseBaggageSetMutability(rawBaggageString, traceparentData);
258+
const baggageHeader = nextReq.headers && nextReq.headers.baggage;
259+
const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(baggageHeader);
260260

261261
// pull off query string, if any
262262
const reqPath = stripUrlQueryAndFragment(nextReq.url);
@@ -270,7 +270,7 @@ function makeWrappedReqHandler(origReqHandler: ReqHandler): WrappedReqHandler {
270270
name: `${namePrefix}${reqPath}`,
271271
op: 'http.server',
272272
metadata: {
273-
baggage,
273+
dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext,
274274
requestPath: reqPath,
275275
// TODO: Investigate if there's a way to tell if this is a dynamic route, so that we can make this more
276276
// like `source: isDynamicRoute? 'url' : 'route'`

packages/nextjs/src/utils/withSentry.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import { extractTraceparentData, hasTracingEnabled } from '@sentry/tracing';
33
import { Transaction } from '@sentry/types';
44
import {
55
addExceptionMechanism,
6+
baggageHeaderToDynamicSamplingContext,
67
isString,
78
logger,
89
objectify,
9-
parseBaggageSetMutability,
1010
stripUrlQueryAndFragment,
1111
} from '@sentry/utils';
1212
import * as domain from 'domain';
@@ -66,8 +66,8 @@ export const withSentry = (origHandler: NextApiHandler): WrappedNextApiHandler =
6666
__DEBUG_BUILD__ && logger.log(`[Tracing] Continuing trace ${traceparentData?.traceId}.`);
6767
}
6868

69-
const rawBaggageString = req.headers && isString(req.headers.baggage) && req.headers.baggage;
70-
const baggage = parseBaggageSetMutability(rawBaggageString, traceparentData);
69+
const baggageHeader = req.headers && req.headers.baggage;
70+
const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(baggageHeader);
7171

7272
const url = `${req.url}`;
7373
// pull off query string, if any
@@ -87,7 +87,10 @@ export const withSentry = (origHandler: NextApiHandler): WrappedNextApiHandler =
8787
name: `${reqMethod}${reqPath}`,
8888
op: 'http.server',
8989
...traceparentData,
90-
metadata: { baggage, source: 'route' },
90+
metadata: {
91+
dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext,
92+
source: 'route',
93+
},
9194
},
9295
// extra context passed to the `tracesSampler`
9396
{ request: req },

packages/nextjs/test/performance/client.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ describe('nextRouterInstrumentation', () => {
142142
},
143143
metadata: {
144144
source: 'route',
145-
baggage: [{ environment: 'myEnv', release: '2.1.0' }, '', true],
145+
dynamicSamplingContext: { environment: 'myEnv', release: '2.1.0' },
146146
},
147147
traceId: 'c82b8554881b4d28ad977de04a4fb40a',
148148
parentSpanId: 'a755953cd3394d5f',
@@ -168,7 +168,7 @@ describe('nextRouterInstrumentation', () => {
168168
},
169169
metadata: {
170170
source: 'route',
171-
baggage: [{ environment: 'myEnv', release: '2.1.0' }, '', true],
171+
dynamicSamplingContext: { environment: 'myEnv', release: '2.1.0' },
172172
},
173173
traceId: 'c82b8554881b4d28ad977de04a4fb40a',
174174
parentSpanId: 'a755953cd3394d5f',

packages/nextjs/test/utils/withSentry.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ describe('withSentry', () => {
117117
op: 'http.server',
118118

119119
metadata: {
120-
baggage: expect.any(Array),
121120
source: 'route',
122121
},
123122
},

packages/node-integration-tests/suites/express/sentry-trace/baggage-header-assign/test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ test('Should propagate sentry trace baggage data from an incoming to an outgoing
3535
});
3636
});
3737

38-
test('Should propagate empty baggage if sentry-trace header is present in incoming request but no baggage header', async () => {
38+
test('Should not propagate baggage if sentry-trace header is present in incoming request but no baggage header', async () => {
3939
const env = await TestEnv.init(__dirname, `${path.resolve(__dirname, '..')}/server.ts`);
4040

4141
const response = (await env.getAPIResponse(`${env.url}/express`, {
@@ -46,12 +46,11 @@ test('Should propagate empty baggage if sentry-trace header is present in incomi
4646
expect(response).toMatchObject({
4747
test_data: {
4848
host: 'somewhere.not.sentry',
49-
baggage: '',
5049
},
5150
});
5251
});
5352

54-
test('Should propagate empty sentry and ignore original 3rd party baggage entries if sentry-trace header is present', async () => {
53+
test('Should not propagate baggage and ignore original 3rd party baggage entries if sentry-trace header is present', async () => {
5554
const env = await TestEnv.init(__dirname, `${path.resolve(__dirname, '..')}/server.ts`);
5655

5756
const response = (await env.getAPIResponse(`${env.url}/express`, {
@@ -63,7 +62,6 @@ test('Should propagate empty sentry and ignore original 3rd party baggage entrie
6362
expect(response).toMatchObject({
6463
test_data: {
6564
host: 'somewhere.not.sentry',
66-
baggage: '',
6765
},
6866
});
6967
});

packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors-with-sentry-entries/test.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ test('should ignore sentry-values in `baggage` header of a third party vendor an
1515
expect(response).toMatchObject({
1616
test_data: {
1717
host: 'somewhere.not.sentry',
18-
baggage: 'other=vendor,foo=bar,third=party,last=item,sentry-release=2.1.0,sentry-environment=myEnv',
18+
baggage: [
19+
'other=vendor,foo=bar,third=party,sentry-release=9.9.9,sentry-environment=staging,sentry-sample_rate=0.54,last=item',
20+
'sentry-release=2.1.0,sentry-environment=myEnv',
21+
],
1922
},
2023
});
2124
});
@@ -29,9 +32,12 @@ test('should ignore sentry-values in `baggage` header of a third party vendor an
2932
expect(response).toMatchObject({
3033
test_data: {
3134
host: 'somewhere.not.sentry',
32-
baggage: expect.stringContaining(
33-
'other=vendor,foo=bar,third=party,last=item,sentry-environment=prod,sentry-release=1.0,sentry-transaction=GET%20%2Ftest%2Fexpress,sentry-public_key=public',
34-
),
35+
baggage: [
36+
'other=vendor,foo=bar,third=party,sentry-release=9.9.9,sentry-environment=staging,sentry-sample_rate=0.54,last=item',
37+
expect.stringMatching(
38+
/sentry-environment=prod,sentry-release=1\.0,sentry-transaction=GET%20%2Ftest%2Fexpress,sentry-public_key=public,sentry-trace_id=[0-9a-f]{32},sentry-sample_rate=1/,
39+
),
40+
],
3541
},
3642
});
3743
});

packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors/test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ test('should merge `baggage` header of a third party vendor with the Sentry DSC
1515
expect(response).toMatchObject({
1616
test_data: {
1717
host: 'somewhere.not.sentry',
18-
baggage: 'other=vendor,foo=bar,third=party,sentry-release=2.0.0,sentry-environment=myEnv',
18+
baggage: ['other=vendor,foo=bar,third=party', 'sentry-release=2.0.0,sentry-environment=myEnv'],
1919
},
2020
});
2121
});

0 commit comments

Comments
 (0)