-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathrequest.ts
More file actions
153 lines (136 loc) · 5.47 KB
/
request.ts
File metadata and controls
153 lines (136 loc) · 5.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import { Event, SdkInfo, SentryRequest, Session } from '@sentry/types';
import { base64ToUnicode, logger } from '@sentry/utils';
import { API } from './api';
/** Extract sdk info from from the API metadata */
function getSdkMetadataForEnvelopeHeader(api: API): SdkInfo | undefined {
if (!api.metadata || !api.metadata.sdk) {
return;
}
const { name, version } = api.metadata.sdk;
return { name, version };
}
/**
* Apply SdkInfo (name, version, packages, integrations) to the corresponding event key.
* Merge with existing data if any.
**/
function enhanceEventWithSdkInfo(event: Event, sdkInfo?: SdkInfo): Event {
if (!sdkInfo) {
return event;
}
event.sdk = event.sdk || {
name: sdkInfo.name,
version: sdkInfo.version,
};
event.sdk.name = event.sdk.name || sdkInfo.name;
event.sdk.version = event.sdk.version || sdkInfo.version;
event.sdk.integrations = [...(event.sdk.integrations || []), ...(sdkInfo.integrations || [])];
event.sdk.packages = [...(event.sdk.packages || []), ...(sdkInfo.packages || [])];
return event;
}
/**
* Create a SentryRequest from an error, message, or transaction event.
*
* @param event The event to send
* @param api Helper to provide the correct url for the request
* @returns SentryRequest representing the event
*/
export function eventToSentryRequest(event: Event, api: API): SentryRequest {
const sdkInfo = getSdkMetadataForEnvelopeHeader(api);
const eventWithSdkInfo = sdkInfo ? enhanceEventWithSdkInfo(event, api.metadata.sdk) : event;
if (event.type === 'transaction') {
return transactionToSentryRequest(eventWithSdkInfo, api);
}
return {
body: JSON.stringify(eventWithSdkInfo),
type: event.type || 'event',
url: api.getStoreEndpointWithUrlEncodedAuth(),
};
}
/**
* Create a SentryRequest from a transaction event.
*
* Since we don't need to manipulate envelopes nor store them, there is no exported concept of an Envelope with
* operations including serialization and deserialization. Instead, we only implement a minimal subset of the spec to
* serialize events inline here. See https://develop.sentry.dev/sdk/envelopes/.
*
* @param event The transaction event to send
* @param api Helper to provide the correct url for the request
* @returns SentryRequest in envelope form
*/
export function transactionToSentryRequest(event: Event, api: API): SentryRequest {
const sdkInfo = getSdkMetadataForEnvelopeHeader(api);
const { transactionSampling, tracestate, ...metadata } = event.debug_meta || {};
const { method: samplingMethod, rate: sampleRate } = transactionSampling || {};
if (Object.keys(metadata).length === 0) {
delete event.debug_meta;
} else {
event.debug_meta = metadata;
}
// the tracestate is stored in bas64-encoded JSON, but envelope header values are expected to be full JS values,
// so we have to decode and reinflate it
let reinflatedTracestate;
try {
const encodedSentryValue = (tracestate?.sentry as string).replace('sentry=', '');
reinflatedTracestate = JSON.parse(base64ToUnicode(encodedSentryValue));
} catch (err) {
logger.warn(err);
}
const envelopeHeaders = JSON.stringify({
event_id: event.event_id,
sent_at: new Date().toISOString(),
...(sdkInfo && { sdk: sdkInfo }),
...(reinflatedTracestate && { trace: reinflatedTracestate }), // trace context for dynamic sampling on relay
});
const itemHeaders = JSON.stringify({
type: event.type,
// TODO: Right now, sampleRate will be undefined in the cases of inheritance and explicitly-set sampling decisions.
sample_rates: [{ id: samplingMethod, rate: sampleRate }],
// Note: `content_type` and `length` were left out on purpose. Here's a quick explanation of why, along with the
// value to use if we ever decide to put them back in.
//
// `content_type`:
// Assumed to be 'application/json' and not part of the current spec for transaction items. No point in bloating the
// request body with it.
//
// would be:
// content_type: 'application/json',
//
// `length`:
// Optional and equal to the number of bytes in req.Body encoded as UTF-8. Since the server can figure this out and
// would otherwise refuse events that report the length incorrectly, we decided not to send the length to avoid
// problems related to reporting the wrong size and to reduce request body size.
//
// would be:
// length: new TextEncoder().encode(req.body).length,
});
const req: SentryRequest = {
// The trailing newline is optional; leave it off to avoid sending unnecessary bytes.
// body: `${envelopeHeaders}\n${itemHeaders}\n${JSON.stringify(event)\n}`,
body: `${envelopeHeaders}\n${itemHeaders}\n${JSON.stringify(event)}`,
type: 'transaction',
url: api.getEnvelopeEndpointWithUrlEncodedAuth(),
};
return req;
}
/**
* Create a SentryRequest from a session event.
*
* @param event The session event to send
* @param api Helper to provide the correct url for the request
* @returns SentryRequest in envelope form
*/
export function sessionToSentryRequest(session: Session, api: API): SentryRequest {
const sdkInfo = getSdkMetadataForEnvelopeHeader(api);
const envelopeHeaders = JSON.stringify({
sent_at: new Date().toISOString(),
...(sdkInfo && { sdk: sdkInfo }),
});
const itemHeaders = JSON.stringify({
type: 'session',
});
return {
body: `${envelopeHeaders}\n${itemHeaders}\n${JSON.stringify(session)}`,
type: 'session',
url: api.getEnvelopeEndpointWithUrlEncodedAuth(),
};
}