Skip to content

Commit ea9ddaf

Browse files
authored
feat(tracing): Add additional Dynamic Sampling Context items to baggage and envelope headers (#5292)
Add several additional Dynamic Sampling Context items to be collected, populated and propagated via baggage or sent via envelope headers. The propagated/sent data now conforms with the specs proposed in getsentry/develop#613. With this PR, the following fields were added to DSC: * transaction name * user id * user segment * sample rate * public key * trace id Note that trace id and public key were already previously sent with envelope headers but the SDK would send its own values instead of the trace's values in case it is not the head-of-trace SDK. This PR also fixes that, in the sense that all `trace` envelope header data items are directly taken from the DSC we either received from an incoming baggage header or from the one we populate instead. In addition to these fields, this patch makes the following changes: * store final baggage in `event.sdkProcessingMetadata` (was previously stored in `event.contexts.baggage` which was wrong, caused a bug and caused the baggage to even be sent to Sentry (which we do not want in this form). * fix a bug where populated DSC (which was set immutable) would not be stored on the span. This bug would have affected data consistency across multiple outgoing requests in one transaction, where the second/third/etc outgoing requests would potentially still find a mutable baggage object and populate it again * rename `_getBaggageDataWithSentryValues` to `_populateBaggageDataWithSentryValues` because it better describes the purpose of the function
1 parent 9b02afb commit ea9ddaf

File tree

16 files changed

+212
-108
lines changed

16 files changed

+212
-108
lines changed

packages/core/src/envelope.ts

+13-17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {
2-
BaggageObj,
32
DsnComponents,
43
Event,
54
EventEnvelope,
@@ -13,7 +12,7 @@ import {
1312
SessionEnvelope,
1413
SessionItem,
1514
} from '@sentry/types';
16-
import { createEnvelope, dropUndefinedKeys, dsnToString } from '@sentry/utils';
15+
import { createEnvelope, dropUndefinedKeys, dsnToString, getSentryBaggageItems } from '@sentry/utils';
1716

1817
/** Extract sdk info from from the API metadata */
1918
function getSdkMetadataForEnvelopeHeader(metadata?: SdkMetadata): SdkInfo | undefined {
@@ -77,14 +76,14 @@ export function createEventEnvelope(
7776

7877
enhanceEventWithSdkInfo(event, metadata && metadata.sdk);
7978

79+
const envelopeHeaders = createEventEnvelopeHeaders(event, sdkInfo, tunnel, dsn);
80+
8081
// Prevent this data (which, if it exists, was used in earlier steps in the processing pipeline) from being sent to
8182
// sentry. (Note: Our use of this property comes and goes with whatever we might be debugging, whatever hacks we may
8283
// have temporarily added, etc. Even if we don't happen to be using it at some point in the future, let's not get rid
8384
// of this `delete`, lest we miss putting it back in the next time the property is in use.)
8485
delete event.sdkProcessingMetadata;
8586

86-
const envelopeHeaders = createEventEnvelopeHeaders(event, sdkInfo, tunnel, dsn);
87-
8887
const eventItem: EventItem = [
8988
{
9089
type: eventType,
@@ -101,27 +100,24 @@ function createEventEnvelopeHeaders(
101100
tunnel: string | undefined,
102101
dsn: DsnComponents,
103102
): EventEnvelopeHeaders {
104-
const baggage = event.contexts && (event.contexts.baggage as BaggageObj);
105-
const { environment, release, transaction, userid, usersegment } = baggage || {};
103+
const baggage = event.sdkProcessingMetadata && event.sdkProcessingMetadata.baggage;
104+
const { environment, release, transaction, userid, usersegment, samplerate, publickey, traceid } =
105+
(baggage && getSentryBaggageItems(baggage)) || {};
106106

107107
return {
108108
event_id: event.event_id as string,
109109
sent_at: new Date().toISOString(),
110110
...(sdkInfo && { sdk: sdkInfo }),
111111
...(!!tunnel && { dsn: dsnToString(dsn) }),
112112
...(event.type === 'transaction' &&
113-
// If we don't already have a trace context in the event, we can't get the trace id, which makes adding any other
114-
// trace data pointless
115-
event.contexts &&
116-
event.contexts.trace && {
113+
baggage && {
117114
trace: dropUndefinedKeys({
118-
// Trace context must be defined for transactions
119-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
120-
trace_id: (event.contexts!.trace as Record<string, unknown>).trace_id as string,
121-
public_key: dsn.publicKey,
122-
environment: environment,
123-
release: release,
124-
transaction: transaction,
115+
trace_id: traceid,
116+
public_key: publickey,
117+
sample_rate: samplerate,
118+
environment,
119+
release,
120+
transaction,
125121
...((userid || usersegment) && {
126122
user: {
127123
id: userid,

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

+23-44
Original file line numberDiff line numberDiff line change
@@ -14,49 +14,23 @@ describe('createEventEnvelope', () => {
1414
expect(envelopeHeaders.trace).toBeUndefined();
1515
});
1616

17-
it('adds minimal trace data if event is a transaction and no other baggage-related data is available', () => {
18-
const event: Event = {
19-
type: 'transaction',
20-
contexts: {
21-
trace: {
22-
trace_id: '1234',
23-
},
24-
},
25-
};
26-
const envelopeHeaders = createEventEnvelope(event, testDsn)[0];
27-
28-
expect(envelopeHeaders).toBeDefined();
29-
expect(envelopeHeaders.trace).toEqual({ trace_id: '1234', public_key: 'pubKey123' });
30-
});
31-
3217
const testTable: Array<[string, Event, EventTraceContext]> = [
3318
[
34-
'adds one baggage item',
19+
'adds minimal baggage items',
3520
{
3621
type: 'transaction',
37-
contexts: {
38-
trace: {
39-
trace_id: '1234',
40-
},
41-
baggage: {
42-
release: '1.0.0',
43-
},
22+
sdkProcessingMetadata: {
23+
baggage: [{ traceid: '1234', publickey: 'pubKey123' }, '', false],
4424
},
4525
},
46-
{ release: '1.0.0', trace_id: '1234', public_key: 'pubKey123' },
26+
{ trace_id: '1234', public_key: 'pubKey123' },
4727
],
4828
[
49-
'adds two baggage items',
29+
'adds multiple baggage items',
5030
{
5131
type: 'transaction',
52-
contexts: {
53-
trace: {
54-
trace_id: '1234',
55-
},
56-
baggage: {
57-
environment: 'prod',
58-
release: '1.0.0',
59-
},
32+
sdkProcessingMetadata: {
33+
baggage: [{ environment: 'prod', release: '1.0.0', publickey: 'pubKey123', traceid: '1234' }, '', false],
6034
},
6135
},
6236
{ release: '1.0.0', environment: 'prod', trace_id: '1234', public_key: 'pubKey123' },
@@ -65,17 +39,21 @@ describe('createEventEnvelope', () => {
6539
'adds all baggage items',
6640
{
6741
type: 'transaction',
68-
contexts: {
69-
trace: {
70-
trace_id: '1234',
71-
},
72-
baggage: {
73-
environment: 'prod',
74-
release: '1.0.0',
75-
userid: 'bob',
76-
usersegment: 'segmentA',
77-
transaction: 'TX',
78-
},
42+
sdkProcessingMetadata: {
43+
baggage: [
44+
{
45+
environment: 'prod',
46+
release: '1.0.0',
47+
userid: 'bob',
48+
usersegment: 'segmentA',
49+
transaction: 'TX',
50+
samplerate: '0.95',
51+
publickey: 'pubKey123',
52+
traceid: '1234',
53+
},
54+
'',
55+
false,
56+
],
7957
},
8058
},
8159
{
@@ -85,6 +63,7 @@ describe('createEventEnvelope', () => {
8563
transaction: 'TX',
8664
trace_id: '1234',
8765
public_key: 'pubKey123',
66+
sample_rate: '0.95',
8867
},
8968
],
9069
];

packages/hub/src/scope.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,7 @@ export class Scope implements ScopeInterface {
454454
if (this._transactionName) {
455455
event.transaction = this._transactionName;
456456
}
457+
457458
// We want to set the trace context for normal events only if there isn't already
458459
// a trace context on the event. There is a product feature in place where we link
459460
// errors with transaction and it relies on that.
@@ -470,7 +471,7 @@ export class Scope implements ScopeInterface {
470471
event.breadcrumbs = [...(event.breadcrumbs || []), ...this._breadcrumbs];
471472
event.breadcrumbs = event.breadcrumbs.length > 0 ? event.breadcrumbs : undefined;
472473

473-
event.sdkProcessingMetadata = this._sdkProcessingMetadata;
474+
event.sdkProcessingMetadata = { ...event.sdkProcessingMetadata, ...this._sdkProcessingMetadata };
474475

475476
return this._notifyEventProcessors([...getGlobalEventProcessors(), ...this._eventProcessors], event, hint);
476477
}

packages/integration-tests/suites/tracing/baggage/test.ts

-24
This file was deleted.

packages/integration-tests/suites/tracing/browsertracing/meta/template.html

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
<head>
33
<meta charset="utf-8" />
44
<meta name="sentry-trace" content="12312012123120121231201212312012-1121201211212012-1" />
5-
<meta name="baggage" content="sentry-release=2.1.12" />
5+
<meta
6+
name="baggage"
7+
content="sentry-release=2.1.12,sentry-publickey=public,sentry-traceid=123,sentry-samplerate=0.3232"
8+
/>
69
</head>
710
</html>

packages/integration-tests/suites/tracing/browsertracing/meta/test.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ sentryTest(
3030

3131
expect(envHeader.trace).toBeDefined();
3232
expect(envHeader.trace).toEqual({
33-
public_key: 'public',
34-
trace_id: expect.any(String),
3533
release: '2.1.12',
34+
sample_rate: '0.3232',
35+
trace_id: '123',
36+
public_key: 'public',
3637
});
3738
},
3839
);

packages/integration-tests/suites/tracing/baggage/init.js packages/integration-tests/suites/tracing/envelope-header/init.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ Sentry.init({
88
integrations: [new Integrations.BrowserTracing({ tracingOrigins: [/.*/] })],
99
environment: 'production',
1010
tracesSampleRate: 1,
11+
debug: true,
1112
});
1213

1314
Sentry.configureScope(scope => {
1415
scope.setUser({ id: 'user123', segment: 'segmentB' });
15-
scope.setTransactionName('testTransactionBaggage');
16+
scope.setTransactionName('testTransactionDSC');
1617
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { expect } from '@playwright/test';
2+
import { EventEnvelopeHeaders } from '@sentry/types';
3+
4+
import { sentryTest } from '../../../utils/fixtures';
5+
import { envelopeHeaderRequestParser, getFirstSentryEnvelopeRequest } from '../../../utils/helpers';
6+
7+
sentryTest(
8+
'should send dynamic sampling context data in transaction envelope header',
9+
async ({ getLocalTestPath, page }) => {
10+
const url = await getLocalTestPath({ testDir: __dirname });
11+
12+
const envHeader = await getFirstSentryEnvelopeRequest<EventEnvelopeHeaders>(page, url, envelopeHeaderRequestParser);
13+
14+
expect(envHeader.trace).toBeDefined();
15+
expect(envHeader.trace).toEqual({
16+
environment: 'production',
17+
transaction: expect.stringContaining('index.html'),
18+
user: {
19+
id: 'user123',
20+
segment: 'segmentB',
21+
},
22+
sample_rate: '1',
23+
trace_id: expect.any(String),
24+
public_key: 'public',
25+
});
26+
},
27+
);

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

+9-2
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,10 @@ test('Should populate and propagate sentry baggage if sentry-trace header does n
7777
expect(response).toMatchObject({
7878
test_data: {
7979
host: 'somewhere.not.sentry',
80-
baggage: 'sentry-environment=prod,sentry-release=1.0',
80+
baggage: expect.stringContaining(
81+
'sentry-environment=prod,sentry-release=1.0,sentry-transaction=GET%20%2Ftest%2Fexpress,sentry-samplerate=1,' +
82+
'sentry-publickey=public,sentry-traceid=',
83+
),
8184
},
8285
});
8386
});
@@ -93,7 +96,11 @@ test('Should populate Sentry and propagate 3rd party content if sentry-trace hea
9396
expect(response).toMatchObject({
9497
test_data: {
9598
host: 'somewhere.not.sentry',
96-
baggage: 'foo=bar,bar=baz,sentry-environment=prod,sentry-release=1.0',
99+
// TraceId changes, hence we only expect that the string contains the traceid key
100+
baggage: expect.stringContaining(
101+
'foo=bar,bar=baz,sentry-environment=prod,sentry-release=1.0,sentry-transaction=GET%20%2Ftest%2Fexpress,' +
102+
'sentry-samplerate=1,sentry-publickey=public,sentry-traceid=',
103+
),
97104
},
98105
});
99106
});

packages/node/test/integrations/http.test.ts

+21-3
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,22 @@ describe('tracing', () => {
2727
const hub = new Hub(new NodeClient(options));
2828
addExtensionMethods();
2929

30+
hub.configureScope(scope =>
31+
scope.setUser({
32+
id: 'uid123',
33+
segment: 'segmentA',
34+
}),
35+
);
36+
3037
// we need to mock both of these because the tracing handler relies on `@sentry/core` while the sampler relies on
3138
// `@sentry/hub`, and mocking breaks the link between the two
3239
jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub);
3340
jest.spyOn(hubModule, 'getCurrentHub').mockReturnValue(hub);
3441

35-
const transaction = hub.startTransaction({ name: 'dogpark' });
42+
const transaction = hub.startTransaction({
43+
name: 'dogpark',
44+
traceId: '12312012123120121231201212312012',
45+
});
3646
hub.getScope()?.setSpan(transaction);
3747

3848
return transaction;
@@ -98,7 +108,11 @@ describe('tracing', () => {
98108
const baggageHeader = request.getHeader('baggage') as string;
99109

100110
expect(baggageHeader).toBeDefined();
101-
expect(baggageHeader).toEqual('sentry-environment=production,sentry-release=1.0.0');
111+
expect(baggageHeader).toEqual(
112+
'sentry-environment=production,sentry-release=1.0.0,sentry-transaction=dogpark,sentry-userid=uid123,' +
113+
'sentry-usersegment=segmentA,sentry-samplerate=1,sentry-publickey=dogsarebadatkeepingsecrets,' +
114+
'sentry-traceid=12312012123120121231201212312012',
115+
);
102116
});
103117

104118
it('propagates 3rd party baggage header data to outgoing non-sentry requests', async () => {
@@ -110,7 +124,11 @@ describe('tracing', () => {
110124
const baggageHeader = request.getHeader('baggage') as string;
111125

112126
expect(baggageHeader).toBeDefined();
113-
expect(baggageHeader).toEqual('dog=great,sentry-environment=production,sentry-release=1.0.0');
127+
expect(baggageHeader).toEqual(
128+
'dog=great,sentry-environment=production,sentry-release=1.0.0,sentry-transaction=dogpark,' +
129+
'sentry-userid=uid123,sentry-usersegment=segmentA,sentry-samplerate=1,' +
130+
'sentry-publickey=dogsarebadatkeepingsecrets,sentry-traceid=12312012123120121231201212312012',
131+
);
114132
});
115133

116134
it("doesn't attach the sentry-trace header to outgoing sentry requests", () => {

0 commit comments

Comments
 (0)