1
1
import { Event , SdkInfo , SentryRequest , SentryRequestType , Session , SessionAggregates } from '@sentry/types' ;
2
+ import { base64ToUnicode , logger } from '@sentry/utils' ;
2
3
3
4
import { API } from './api' ;
4
5
@@ -12,19 +13,20 @@ function getSdkMetadataForEnvelopeHeader(api: API): SdkInfo | undefined {
12
13
}
13
14
14
15
/**
15
- * Apply SdkInfo (name, version, packages, integrations) to the corresponding event key.
16
- * Merge with existing data if any.
16
+ * Add SDK metadata (name, version, packages, integrations) to the event.
17
+ *
18
+ * Mutates the object in place. If prior metadata exists, it will be merged with the given metadata.
17
19
**/
18
- function enhanceEventWithSdkInfo ( event : Event , sdkInfo ?: SdkInfo ) : Event {
20
+ function enhanceEventWithSdkInfo ( event : Event , sdkInfo ?: SdkInfo ) : void {
19
21
if ( ! sdkInfo ) {
20
- return event ;
22
+ return ;
21
23
}
22
24
event . sdk = event . sdk || { } ;
23
25
event . sdk . name = event . sdk . name || sdkInfo . name ;
24
26
event . sdk . version = event . sdk . version || sdkInfo . version ;
25
27
event . sdk . integrations = [ ...( event . sdk . integrations || [ ] ) , ...( sdkInfo . integrations || [ ] ) ] ;
26
28
event . sdk . packages = [ ...( event . sdk . packages || [ ] ) , ...( sdkInfo . packages || [ ] ) ] ;
27
- return event ;
29
+ return ;
28
30
}
29
31
30
32
/** Creates a SentryRequest from a Session. */
@@ -54,61 +56,81 @@ export function eventToSentryRequest(event: Event, api: API): SentryRequest {
54
56
const eventType = event . type || 'event' ;
55
57
const useEnvelope = eventType === 'transaction' || api . forceEnvelope ( ) ;
56
58
57
- const { transactionSampling, ...metadata } = event . debug_meta || { } ;
58
- const { method : samplingMethod , rate : sampleRate } = transactionSampling || { } ;
59
- if ( Object . keys ( metadata ) . length === 0 ) {
60
- delete event . debug_meta ;
61
- } else {
62
- event . debug_meta = metadata ;
63
- }
59
+ enhanceEventWithSdkInfo ( event , api . metadata . sdk ) ;
64
60
65
- const req : SentryRequest = {
66
- body : JSON . stringify ( sdkInfo ? enhanceEventWithSdkInfo ( event , api . metadata . sdk ) : event ) ,
67
- type : eventType ,
68
- url : useEnvelope ? api . getEnvelopeEndpointWithUrlEncodedAuth ( ) : api . getStoreEndpointWithUrlEncodedAuth ( ) ,
69
- } ;
61
+ // Since we don't need to manipulate envelopes nor store them, there is no exported concept of an Envelope with
62
+ // operations including serialization and deserialization. Instead, we only implement a minimal subset of the spec to
63
+ // serialize events inline here. See https://develop.sentry.dev/sdk/envelopes/.
64
+ if ( useEnvelope ) {
65
+ // Extract header information from event
66
+ const { transactionSampling, tracestate, ...metadata } = event . debug_meta || { } ;
67
+ if ( Object . keys ( metadata ) . length === 0 ) {
68
+ delete event . debug_meta ;
69
+ } else {
70
+ event . debug_meta = metadata ;
71
+ }
70
72
71
- // https://develop.sentry.dev/sdk/envelopes/
73
+ // the tracestate is stored in bas64-encoded JSON, but envelope header values are expected to be full JS values,
74
+ // so we have to decode and reinflate it
75
+ let reinflatedTracestate ;
76
+ try {
77
+ // Because transaction metadata passes through a number of locations (transactionContext, transaction, event during
78
+ // processing, event as sent), each with different requirements, all of the parts are typed as optional. That said,
79
+ // if we get to this point and either `tracestate` or `tracestate.sentry` are undefined, something's gone very wrong.
80
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
81
+ const encodedSentryValue = tracestate ! . sentry ! . replace ( 'sentry=' , '' ) ;
82
+ reinflatedTracestate = JSON . parse ( base64ToUnicode ( encodedSentryValue ) ) ;
83
+ } catch ( err ) {
84
+ logger . warn ( err ) ;
85
+ }
72
86
73
- // Since we don't need to manipulate envelopes nor store them, there is no
74
- // exported concept of an Envelope with operations including serialization and
75
- // deserialization. Instead, we only implement a minimal subset of the spec to
76
- // serialize events inline here.
77
- if ( useEnvelope ) {
78
87
const envelopeHeaders = JSON . stringify ( {
79
88
event_id : event . event_id ,
80
89
sent_at : new Date ( ) . toISOString ( ) ,
81
90
...( sdkInfo && { sdk : sdkInfo } ) ,
82
91
...( api . forceEnvelope ( ) && { dsn : api . getDsn ( ) . toString ( ) } ) ,
92
+ ...( reinflatedTracestate && { trace : reinflatedTracestate } ) , // trace context for dynamic sampling on relay
83
93
} ) ;
84
- const itemHeaders = JSON . stringify ( {
85
- type : eventType ,
86
94
87
- // TODO: Right now, sampleRate may or may not be defined (it won't be in the cases of inheritance and
88
- // explicitly-set sampling decisions). Are we good with that?
89
- sample_rates : [ { id : samplingMethod , rate : sampleRate } ] ,
95
+ const itemHeaderEntries : { [ key : string ] : unknown } = {
96
+ type : eventType ,
90
97
91
- // The content-type is assumed to be 'application/json' and not part of
92
- // the current spec for transaction items, so we don't bloat the request
93
- // body with it.
98
+ // Note: as mentioned above, `content_type` and `length` were left out on purpose.
94
99
//
95
- // content_type: 'application/json',
100
+ // `content_type`:
101
+ // Assumed to be 'application/json' and not part of the current spec for transaction items. No point in bloating the
102
+ // request body with it. (Would be `content_type: 'application/json'`.)
96
103
//
97
- // The length is optional. It must be the number of bytes in req.Body
98
- // encoded as UTF-8. Since the server can figure this out and would
99
- // otherwise refuse events that report the length incorrectly, we decided
100
- // not to send the length to avoid problems related to reporting the wrong
101
- // size and to reduce request body size.
102
- //
103
- // length: new TextEncoder().encode(req.body).length,
104
- } ) ;
105
- // The trailing newline is optional. We intentionally don't send it to avoid
106
- // sending unnecessary bytes.
107
- //
108
- // const envelope = `${envelopeHeaders}\n${itemHeaders}\n${req.body}\n`;
109
- const envelope = `${ envelopeHeaders } \n${ itemHeaders } \n${ req . body } ` ;
110
- req . body = envelope ;
104
+ // `length`:
105
+ // Optional and equal to the number of bytes in `req.Body` encoded as UTF-8. Since the server can figure this out
106
+ // and will refuse events that report the length incorrectly, we decided not to send the length to reduce request
107
+ // body size and to avoid problems related to reporting the wrong size.(Would be
108
+ // `length: new TextEncoder().encode(req.body).length`.)
109
+ } ;
110
+
111
+ if ( eventType === 'transaction' ) {
112
+ // TODO: Right now, `sampleRate` will be undefined in the cases of inheritance and explicitly-set sampling decisions.
113
+ itemHeaderEntries . sample_rates = [ { id : transactionSampling ?. method , rate : transactionSampling ?. rate } ] ;
114
+ }
115
+
116
+ const itemHeaders = JSON . stringify ( itemHeaderEntries ) ;
117
+
118
+ const eventJSON = JSON . stringify ( event ) ;
119
+
120
+ // The trailing newline is optional; leave it off to avoid sending unnecessary bytes. (Would be
121
+ // `const envelope = `${envelopeHeaders}\n${itemHeaders}\n${req.body}\n`;`.)
122
+ const envelope = `${ envelopeHeaders } \n${ itemHeaders } \n${ eventJSON } ` ;
123
+
124
+ return {
125
+ body : envelope ,
126
+ type : eventType ,
127
+ url : api . getEnvelopeEndpointWithUrlEncodedAuth ( ) ,
128
+ } ;
111
129
}
112
130
113
- return req ;
131
+ return {
132
+ body : JSON . stringify ( event ) ,
133
+ type : eventType ,
134
+ url : api . getStoreEndpointWithUrlEncodedAuth ( ) ,
135
+ } ;
114
136
}
0 commit comments