Skip to content

Commit bea3e54

Browse files
committed
feat(tracing): Add dynamic sampling correlation context data to envelope header (#3062)
1 parent 438ed91 commit bea3e54

File tree

2 files changed

+180
-99
lines changed

2 files changed

+180
-99
lines changed

packages/core/src/request.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,16 @@ export function eventToSentryRequest(event: Event, api: API): SentryRequest {
8080
event_id: event.event_id,
8181
sent_at: new Date().toISOString(),
8282
...(sdkInfo && { sdk: sdkInfo }),
83+
84+
// trace context for dynamic sampling on relay
85+
trace: {
86+
trace_id: event.contexts?.trace?.trace_id,
87+
public_key: api.getDsn().publicKey,
88+
environment: event.environment || null,
89+
release: event.release || null,
90+
},
8391
});
92+
8493
const itemHeaders = JSON.stringify({
8594
type: event.type,
8695

@@ -102,6 +111,7 @@ export function eventToSentryRequest(event: Event, api: API): SentryRequest {
102111
//
103112
// length: new TextEncoder().encode(req.body).length,
104113
});
114+
105115
// The trailing newline is optional. We intentionally don't send it to avoid
106116
// sending unnecessary bytes.
107117
//
Lines changed: 170 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DebugMeta, Event, SentryRequest, TransactionSamplingMethod } from '@sentry/types';
1+
import { Event, SentryRequest, TransactionSamplingMethod } from '@sentry/types';
22

33
import { API } from '../../src/api';
44
import { eventToSentryRequest } from '../../src/request';
@@ -22,106 +22,177 @@ describe('eventToSentryRequest', () => {
2222
packages: [{ name: 'npm:@sentry/browser', version: `12.31.12` }],
2323
},
2424
});
25-
let event: Event;
26-
27-
beforeEach(() => {
28-
event = {
29-
contexts: { trace: { trace_id: '1231201211212012', span_id: '12261980', op: 'pageload' } },
30-
environment: 'dogpark',
31-
event_id: '0908201304152013',
32-
release: 'off.leash.park',
33-
spans: [],
34-
transaction: '/dogs/are/great/',
35-
type: 'transaction',
36-
user: { id: '1121', username: 'CharlieDog', ip_address: '11.21.20.12' },
37-
};
38-
});
39-
40-
it(`adds transaction sampling information to item header`, () => {
41-
event.debug_meta = { transactionSampling: { method: TransactionSamplingMethod.Rate, rate: 0.1121 } };
42-
43-
const result = eventToSentryRequest(event, api);
44-
const envelope = parseEnvelopeRequest(result);
45-
46-
expect(envelope.itemHeader).toEqual(
47-
expect.objectContaining({
48-
sample_rates: [{ id: TransactionSamplingMethod.Rate, rate: 0.1121 }],
49-
}),
50-
);
51-
});
52-
53-
it('removes transaction sampling information (and only that) from debug_meta', () => {
54-
event.debug_meta = {
55-
transactionSampling: { method: TransactionSamplingMethod.Sampler, rate: 0.1121 },
56-
dog: 'Charlie',
57-
} as DebugMeta;
58-
59-
const result = eventToSentryRequest(event, api);
60-
const envelope = parseEnvelopeRequest(result);
6125

62-
expect('transactionSampling' in envelope.event.debug_meta).toBe(false);
63-
expect('dog' in envelope.event.debug_meta).toBe(true);
26+
const eventBase = {
27+
contexts: { trace: { trace_id: '1231201211212012', span_id: '12261980', op: 'pageload' } },
28+
environment: 'dogpark',
29+
event_id: '0908201304152013',
30+
release: 'off.leash.park',
31+
user: { id: '1121', username: 'CharlieDog', ip_address: '11.21.20.12' },
32+
};
33+
34+
describe('error/message events', () => {
35+
let errorEvent: Event;
36+
37+
beforeEach(() => {
38+
errorEvent = {
39+
...eventBase,
40+
exception: { values: [{ type: 'PuppyProblemsError', value: 'Charlie ate the flip-flops :-(' }] },
41+
};
42+
});
43+
44+
it('adds correct type, url, and event data', () => {
45+
const result = eventToSentryRequest(errorEvent, api);
46+
47+
expect(result.type).toEqual('event');
48+
expect(result.url).toEqual(
49+
'https://squirrelchasers.ingest.sentry.io/api/12312012/store/?sentry_key=dogsarebadatkeepingsecrets&sentry_version=7',
50+
);
51+
expect(result.body).toEqual(JSON.stringify(errorEvent));
52+
});
6453
});
6554

66-
it('removes debug_meta entirely if it ends up empty', () => {
67-
event.debug_meta = {
68-
transactionSampling: { method: TransactionSamplingMethod.Rate, rate: 0.1121 },
69-
} as DebugMeta;
70-
71-
const result = eventToSentryRequest(event, api);
72-
const envelope = parseEnvelopeRequest(result);
73-
74-
expect('debug_meta' in envelope.event).toBe(false);
75-
});
76-
77-
it('adds sdk info to envelope header', () => {
78-
const result = eventToSentryRequest(event, api);
79-
const envelope = parseEnvelopeRequest(result);
80-
81-
expect(envelope.envelopeHeader).toEqual(
82-
expect.objectContaining({ sdk: { name: 'sentry.javascript.browser', version: '12.31.12' } }),
83-
);
84-
});
85-
86-
it('adds sdk info to event body', () => {
87-
const result = eventToSentryRequest(event, api);
88-
const envelope = parseEnvelopeRequest(result);
89-
90-
expect(envelope.event).toEqual(
91-
expect.objectContaining({
92-
sdk: {
93-
integrations: ['AWSLambda'],
94-
name: 'sentry.javascript.browser',
95-
version: `12.31.12`,
96-
packages: [{ name: 'npm:@sentry/browser', version: `12.31.12` }],
97-
},
98-
}),
99-
);
100-
});
101-
102-
it('merges existing sdk info if one is present on the event body', () => {
103-
event.sdk = {
104-
integrations: ['Clojure'],
105-
name: 'foo',
106-
packages: [{ name: 'npm:@sentry/clj', version: `12.31.12` }],
107-
version: '1337',
108-
};
109-
110-
const result = eventToSentryRequest(event, api);
111-
const envelope = parseEnvelopeRequest(result);
112-
113-
expect(envelope.event).toEqual(
114-
expect.objectContaining({
115-
sdk: {
116-
integrations: ['Clojure', 'AWSLambda'],
117-
name: 'foo',
118-
packages: [
119-
{ name: 'npm:@sentry/clj', version: `12.31.12` },
120-
{ name: 'npm:@sentry/browser', version: `12.31.12` },
121-
],
122-
version: '1337',
123-
},
124-
}),
125-
);
55+
describe('transaction events', () => {
56+
let transactionEvent: Event;
57+
58+
beforeEach(() => {
59+
transactionEvent = {
60+
...eventBase,
61+
debug_meta: { transactionSampling: { method: TransactionSamplingMethod.Rate, rate: 0.1121 } },
62+
spans: [],
63+
transaction: '/dogs/are/great/',
64+
type: 'transaction',
65+
};
66+
});
67+
68+
it('ads correct type, url, and event data', () => {
69+
const result = eventToSentryRequest(transactionEvent, api);
70+
const envelope = parseEnvelopeRequest(result);
71+
72+
expect(result.type).toEqual('transaction');
73+
expect(result.url).toEqual(
74+
'https://squirrelchasers.ingest.sentry.io/api/12312012/envelope/?sentry_key=dogsarebadatkeepingsecrets&sentry_version=7',
75+
);
76+
expect(envelope.event).toEqual(transactionEvent);
77+
});
78+
79+
describe('envelope header', () => {
80+
it('adds correct data to envelope header', () => {
81+
jest.spyOn(Date.prototype, 'toISOString').mockReturnValueOnce('2012-12-31T09:08:13.000Z');
82+
83+
const result = eventToSentryRequest(transactionEvent, api);
84+
const envelope = parseEnvelopeRequest(result);
85+
86+
// the values for `sdk` and `trace` are more complicated and are therefore tested separately
87+
expect(Object.keys(envelope.envelopeHeader)).toEqual(['event_id', 'sent_at', 'sdk', 'trace']);
88+
expect(envelope.envelopeHeader.event_id).toEqual('0908201304152013');
89+
expect(envelope.envelopeHeader.sent_at).toEqual('2012-12-31T09:08:13.000Z');
90+
});
91+
92+
describe('sdk info', () => {
93+
it('adds sdk info to envelope header', () => {
94+
const result = eventToSentryRequest(transactionEvent, api);
95+
const envelope = parseEnvelopeRequest(result);
96+
97+
expect(envelope.envelopeHeader.sdk).toBeDefined();
98+
expect(envelope.envelopeHeader.sdk).toEqual({ name: 'sentry.javascript.browser', version: '12.31.12' });
99+
});
100+
101+
it('adds sdk info to event body', () => {
102+
const result = eventToSentryRequest(transactionEvent, api);
103+
const envelope = parseEnvelopeRequest(result);
104+
105+
expect(envelope.event.sdk).toBeDefined();
106+
expect(envelope.event.sdk).toEqual({
107+
integrations: ['AWSLambda'],
108+
name: 'sentry.javascript.browser',
109+
version: `12.31.12`,
110+
packages: [{ name: 'npm:@sentry/browser', version: `12.31.12` }],
111+
});
112+
});
113+
114+
it('merges sdk info if sdk data already exists on the event body', () => {
115+
transactionEvent.sdk = {
116+
integrations: ['Ball Fetching'],
117+
name: 'sentry.dog.tricks',
118+
packages: [{ name: 'npm:@sentry/dogtricks', version: `11.21.12` }],
119+
version: '11.21.12',
120+
};
121+
122+
const result = eventToSentryRequest(transactionEvent, api);
123+
const envelope = parseEnvelopeRequest(result);
124+
125+
expect(envelope.event).toEqual(
126+
expect.objectContaining({
127+
sdk: {
128+
integrations: ['Ball Fetching', 'AWSLambda'],
129+
name: 'sentry.dog.tricks',
130+
packages: [
131+
{ name: 'npm:@sentry/dogtricks', version: `11.21.12` },
132+
{ name: 'npm:@sentry/browser', version: `12.31.12` },
133+
],
134+
version: '11.21.12',
135+
},
136+
}),
137+
);
138+
});
139+
});
140+
141+
describe('tracestate', () => {
142+
it('adds tracestate data to envelope header', () => {
143+
const result = eventToSentryRequest(transactionEvent, api);
144+
const envelope = parseEnvelopeRequest(result);
145+
146+
expect(envelope.envelopeHeader.trace).toBeDefined();
147+
expect(envelope.envelopeHeader.trace).toEqual({
148+
environment: 'dogpark',
149+
public_key: 'dogsarebadatkeepingsecrets',
150+
release: 'off.leash.park',
151+
trace_id: '1231201211212012',
152+
});
153+
});
154+
});
155+
});
156+
157+
describe('item header', () => {
158+
it('adds correct data to item header', () => {
159+
const result = eventToSentryRequest(transactionEvent, api);
160+
const envelope = parseEnvelopeRequest(result);
161+
162+
// the value for `sample_rates` is more complicated and is therefore tested separately
163+
expect(Object.keys(envelope.itemHeader)).toEqual(['type', 'sample_rates']);
164+
expect(envelope.itemHeader.type).toEqual('transaction');
165+
});
166+
167+
describe('transaction sampling info', () => {
168+
it(`adds transaction sampling information to item header`, () => {
169+
const result = eventToSentryRequest(transactionEvent, api);
170+
const envelope = parseEnvelopeRequest(result);
171+
172+
expect(envelope.itemHeader).toEqual(
173+
expect.objectContaining({
174+
sample_rates: [{ id: TransactionSamplingMethod.Rate, rate: 0.1121 }],
175+
}),
176+
);
177+
});
178+
179+
it('removes transaction sampling information (and only that) from debug_meta', () => {
180+
(transactionEvent.debug_meta as any).dog = 'Charlie';
181+
182+
const result = eventToSentryRequest(transactionEvent, api);
183+
const envelope = parseEnvelopeRequest(result);
184+
185+
expect('transactionSampling' in envelope.event.debug_meta).toBe(false);
186+
expect('dog' in envelope.event.debug_meta).toBe(true);
187+
});
188+
189+
it('removes debug_meta entirely if it ends up empty', () => {
190+
const result = eventToSentryRequest(transactionEvent, api);
191+
const envelope = parseEnvelopeRequest(result);
192+
193+
expect('debug_meta' in envelope.event).toBe(false);
194+
});
195+
});
196+
});
126197
});
127198
});

0 commit comments

Comments
 (0)