Skip to content

Commit 52626d2

Browse files
committed
split transactionToSentryRequest into its own method, use transaction's tracestate value in envelope
1 parent 4033a51 commit 52626d2

File tree

2 files changed

+96
-68
lines changed

2 files changed

+96
-68
lines changed

packages/core/src/request.ts

Lines changed: 89 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,110 @@
11
import { Event, SentryRequest, Session } from '@sentry/types';
2+
import { base64ToUnicode, logger } from '@sentry/utils';
23

34
import { API } from './api';
45

5-
/** Creates a SentryRequest from an event. */
6-
export function sessionToSentryRequest(session: Session, api: API): SentryRequest {
7-
const envelopeHeaders = JSON.stringify({
8-
sent_at: new Date().toISOString(),
9-
});
10-
const itemHeaders = JSON.stringify({
11-
type: 'session',
12-
});
13-
6+
/**
7+
* Create a SentryRequest from an error, message, or transaction event.
8+
*
9+
* @param event The event to send
10+
* @param api Helper to provide the correct url for the request
11+
* @returns SentryRequest representing the event
12+
*/
13+
export function eventToSentryRequest(event: Event, api: API): SentryRequest {
14+
if (event.type === 'transaction') {
15+
return transactionToSentryRequest(event, api);
16+
}
1417
return {
15-
body: `${envelopeHeaders}\n${itemHeaders}\n${JSON.stringify(session)}`,
16-
type: 'session',
17-
url: api.getEnvelopeEndpointWithUrlEncodedAuth(),
18+
body: JSON.stringify(event),
19+
type: event.type || 'event',
20+
url: api.getStoreEndpointWithUrlEncodedAuth(),
1821
};
1922
}
2023

21-
/** Creates a SentryRequest from an event. */
22-
export function eventToSentryRequest(event: Event, api: API): SentryRequest {
24+
/**
25+
* Create a SentryRequest from a transaction event.
26+
*
27+
* Since we don't need to manipulate envelopes nor store them, there is no exported concept of an Envelope with
28+
* operations including serialization and deserialization. Instead, we only implement a minimal subset of the spec to
29+
* serialize events inline here. See https://develop.sentry.dev/sdk/envelopes/.
30+
*
31+
* @param event The transaction event to send
32+
* @param api Helper to provide the correct url for the request
33+
* @returns SentryRequest in envelope form
34+
*/
35+
export function transactionToSentryRequest(event: Event, api: API): SentryRequest {
2336
// since JS has no Object.prototype.pop()
2437
const { __sentry_samplingMethod: samplingMethod, __sentry_sampleRate: sampleRate, ...otherTags } = event.tags || {};
2538
event.tags = otherTags;
2639

27-
const useEnvelope = event.type === 'transaction';
28-
29-
const req: SentryRequest = {
30-
body: JSON.stringify(event),
31-
type: event.type || 'event',
32-
url: useEnvelope ? api.getEnvelopeEndpointWithUrlEncodedAuth() : api.getStoreEndpointWithUrlEncodedAuth(),
33-
};
40+
// because we send event.tracestate as a header whose values have meaningful equals signs, when we create it we have
41+
// to replace the padding character with something else; the `.replace` call here reverses that to make it valid
42+
// base64 before converting to unicode for parsing
43+
let tracestateJSON;
44+
try {
45+
tracestateJSON = event.tracestate ? base64ToUnicode(event.tracestate.replace('.', '=')) : undefined;
46+
} catch (err) {
47+
logger.warn(err);
48+
tracestateJSON = '';
49+
}
50+
delete event.tracestate;
3451

35-
// https://develop.sentry.dev/sdk/envelopes/
52+
const envelopeHeaders = JSON.stringify({
53+
event_id: event.event_id,
54+
sent_at: new Date().toISOString(),
55+
trace: tracestateJSON, // trace context for dynamic sampling on relay
56+
});
3657

37-
// Since we don't need to manipulate envelopes nor store them, there is no
38-
// exported concept of an Envelope with operations including serialization and
39-
// deserialization. Instead, we only implement a minimal subset of the spec to
40-
// serialize events inline here.
41-
if (useEnvelope) {
42-
const envelopeHeaders = JSON.stringify({
43-
event_id: event.event_id,
44-
sent_at: new Date().toISOString(),
58+
const itemHeaders = JSON.stringify({
59+
type: event.type,
4560

46-
// trace context for dynamic sampling on relay
47-
trace: {
48-
trace_id: event.contexts?.trace?.trace_id,
49-
// TODO: any reason we can't change this property to be called publicKey, since that's what it is?
50-
public_key: api.getDsn().user,
51-
environment: event.environment || 'no environment specified',
52-
release: event.release || 'no release specified',
53-
},
54-
});
61+
// TODO: Right now, sampleRate won't be defined in the cases of inheritance and explicitly-set sampling decisions.
62+
sample_rates: [{ id: samplingMethod, rate: sampleRate }],
5563

56-
const itemHeaders = JSON.stringify({
57-
type: event.type,
64+
// The content-type is assumed to be 'application/json' and not part of
65+
// the current spec for transaction items, so we don't bloat the request
66+
// body with it.
67+
//
68+
// content_type: 'application/json',
69+
//
70+
// The length is optional. It must be the number of bytes in req.Body
71+
// encoded as UTF-8. Since the server can figure this out and would
72+
// otherwise refuse events that report the length incorrectly, we decided
73+
// not to send the length to avoid problems related to reporting the wrong
74+
// size and to reduce request body size.
75+
//
76+
// length: new TextEncoder().encode(req.body).length,
77+
});
5878

59-
// TODO: Right now, sampleRate may or may not be defined (it won't be in the cases of inheritance and
60-
// explicitly-set sampling decisions). Are we good with that?
61-
sample_rates: [{ id: samplingMethod, rate: sampleRate }],
79+
const req: SentryRequest = {
80+
// The trailing newline is optional; leave it off to avoid sending unnecessary bytes.
81+
// body: `${envelopeHeaders}\n${itemHeaders}\n${JSON.stringify(event)\n}`,
82+
body: `${envelopeHeaders}\n${itemHeaders}\n${JSON.stringify(event)}`,
83+
type: 'transaction',
84+
url: api.getEnvelopeEndpointWithUrlEncodedAuth(),
85+
};
6286

63-
// The content-type is assumed to be 'application/json' and not part of
64-
// the current spec for transaction items, so we don't bloat the request
65-
// body with it.
66-
//
67-
// content_type: 'application/json',
68-
//
69-
// The length is optional. It must be the number of bytes in req.Body
70-
// encoded as UTF-8. Since the server can figure this out and would
71-
// otherwise refuse events that report the length incorrectly, we decided
72-
// not to send the length to avoid problems related to reporting the wrong
73-
// size and to reduce request body size.
74-
//
75-
// length: new TextEncoder().encode(req.body).length,
76-
});
87+
return req;
88+
}
7789

78-
// The trailing newline is optional. We intentionally don't send it to avoid
79-
// sending unnecessary bytes.
80-
//
81-
// const envelope = `${envelopeHeaders}\n${itemHeaders}\n${req.body}\n`;
82-
const envelope = `${envelopeHeaders}\n${itemHeaders}\n${req.body}`;
83-
req.body = envelope;
84-
}
90+
/**
91+
* Create a SentryRequest from a session event.
92+
*
93+
* @param event The session event to send
94+
* @param api Helper to provide the correct url for the request
95+
* @returns SentryRequest in envelope form
96+
*/
97+
export function sessionToSentryRequest(session: Session, api: API): SentryRequest {
98+
const envelopeHeaders = JSON.stringify({
99+
sent_at: new Date().toISOString(),
100+
});
101+
const itemHeaders = JSON.stringify({
102+
type: 'session',
103+
});
85104

86-
return req;
105+
return {
106+
body: `${envelopeHeaders}\n${itemHeaders}\n${JSON.stringify(session)}`,
107+
type: 'session',
108+
url: api.getEnvelopeEndpointWithUrlEncodedAuth(),
109+
};
87110
}

packages/core/test/lib/request.test.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ describe('eventToSentryRequest', () => {
4040
event_id: eventId,
4141
release: 'off.leash.park',
4242
spans: [],
43+
tracestate:
44+
'ewAiAGUAbgB2AGkAcgBvAG4AbQBlAG4AdAAiADoAIgBkAG8AZwBwAGEAcgBrACIALAAiAHAAdQBiAGwAaQBjAF8AawBlAHkAIgA6ACIAZAB' +
45+
'vAGcAcwBhAHIAZQBiAGEAZABhAHQAawBlAGUAcABpAG4AZwBzAGUAYwByAGUAdABzACIALAAiAHIAZQBsAGUAYQBzAGUAIgA6ACIAbwBmAG' +
46+
'YALgBsAGUAYQBzAGgALgBwAGEAcgBrACIALAAiAHQAcgBhAGMAZQBfAGkAZAAiADoAIgAwADkAMAA4ADIAMAAxADMAMAA0ADEANQAyADAAM' +
47+
'QAzACIAfQA.',
4348
transaction: '/dogs/are/great/',
4449
type: 'transaction',
4550
user: { id: '1121', username: 'CharlieDog', ip_address: '11.21.20.12' },
@@ -62,12 +67,12 @@ describe('eventToSentryRequest', () => {
6267
expect(envelope.envelopeHeader).toEqual({
6368
event_id: eventId,
6469
sent_at: expect.any(String),
65-
trace: {
70+
trace: JSON.stringify({
6671
environment: 'dogpark',
6772
public_key: 'dogsarebadatkeepingsecrets',
6873
release: 'off.leash.park',
6974
trace_id: traceId,
70-
},
75+
}),
7176
});
7277
expect(envelope.itemHeader).toEqual({
7378
type: 'transaction',

0 commit comments

Comments
 (0)