@sentry/profiling-node 8.0.0-alpha.2 → 8.0.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -6
- package/lib/index.js +1671 -545
- package/lib/index.js.map +1 -1
- package/lib/sentry_cpu_profiler-darwin-arm64-108.node +0 -0
- package/lib/sentry_cpu_profiler-darwin-arm64-115.node +0 -0
- package/lib/sentry_cpu_profiler-darwin-arm64-93.node +0 -0
- package/lib/sentry_cpu_profiler-darwin-x64-108.node +0 -0
- package/lib/sentry_cpu_profiler-darwin-x64-115.node +0 -0
- package/lib/sentry_cpu_profiler-darwin-x64-93.node +0 -0
- package/lib/sentry_cpu_profiler-win32-x64-108.node +0 -0
- package/lib/sentry_cpu_profiler-win32-x64-115.node +0 -0
- package/lib/sentry_cpu_profiler-win32-x64-93.node +0 -0
- package/lib/types/index.d.ts +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/integration.d.ts +7 -26
- package/lib/types/integration.d.ts.map +1 -1
- package/lib/types/spanProfileUtils.d.ts +17 -0
- package/lib/types/spanProfileUtils.d.ts.map +1 -0
- package/lib/types/utils.d.ts +1 -25
- package/lib/types/utils.d.ts.map +1 -1
- package/lib/types-ts3.8/index.d.ts +1 -1
- package/lib/types-ts3.8/integration.d.ts +7 -26
- package/lib/types-ts3.8/spanProfileUtils.d.ts +17 -0
- package/lib/types-ts3.8/utils.d.ts +1 -25
- package/package.json +15 -6
- package/lib/types/hubextensions.d.ts +0 -29
- package/lib/types/hubextensions.d.ts.map +0 -1
- package/lib/types-ts3.8/hubextensions.d.ts +0 -29
package/lib/index.js
CHANGED
|
@@ -7,33 +7,26 @@ const nodeAbi = require('node-abi');
|
|
|
7
7
|
const process$1 = require('process');
|
|
8
8
|
const worker_threads = require('worker_threads');
|
|
9
9
|
|
|
10
|
-
/******************************************************************************
|
|
11
|
-
Copyright (c) Microsoft Corporation.
|
|
12
|
-
|
|
13
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
14
|
-
purpose with or without fee is hereby granted.
|
|
15
|
-
|
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
17
|
-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
18
|
-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
19
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
20
|
-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
21
|
-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
22
|
-
PERFORMANCE OF THIS SOFTWARE.
|
|
23
|
-
***************************************************************************** */
|
|
24
|
-
|
|
25
|
-
function __awaiter(thisArg, _arguments, P, generator) {
|
|
26
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
10
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
36
11
|
const objectToString = Object.prototype.toString;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Checks whether given value's type is one of a few Error or Error-like
|
|
15
|
+
* {@link isError}.
|
|
16
|
+
*
|
|
17
|
+
* @param wat A value to be checked.
|
|
18
|
+
* @returns A boolean representing the result.
|
|
19
|
+
*/
|
|
20
|
+
function isError(wat) {
|
|
21
|
+
switch (objectToString.call(wat)) {
|
|
22
|
+
case '[object Error]':
|
|
23
|
+
case '[object Exception]':
|
|
24
|
+
case '[object DOMException]':
|
|
25
|
+
return true;
|
|
26
|
+
default:
|
|
27
|
+
return isInstanceOf(wat, Error);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
37
30
|
/**
|
|
38
31
|
* Checks whether given value is an instance of the given built-in class.
|
|
39
32
|
*
|
|
@@ -45,6 +38,17 @@ function isBuiltin(wat, className) {
|
|
|
45
38
|
return objectToString.call(wat) === `[object ${className}]`;
|
|
46
39
|
}
|
|
47
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Checks whether given value's type is a string
|
|
43
|
+
* {@link isString}.
|
|
44
|
+
*
|
|
45
|
+
* @param wat A value to be checked.
|
|
46
|
+
* @returns A boolean representing the result.
|
|
47
|
+
*/
|
|
48
|
+
function isString(wat) {
|
|
49
|
+
return isBuiltin(wat, 'String');
|
|
50
|
+
}
|
|
51
|
+
|
|
48
52
|
/**
|
|
49
53
|
* Checks whether given value's type is an object literal, or a class instance.
|
|
50
54
|
* {@link isPlainObject}.
|
|
@@ -56,6 +60,28 @@ function isPlainObject(wat) {
|
|
|
56
60
|
return isBuiltin(wat, 'Object');
|
|
57
61
|
}
|
|
58
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Checks whether given value's type is an Event instance
|
|
65
|
+
* {@link isEvent}.
|
|
66
|
+
*
|
|
67
|
+
* @param wat A value to be checked.
|
|
68
|
+
* @returns A boolean representing the result.
|
|
69
|
+
*/
|
|
70
|
+
function isEvent(wat) {
|
|
71
|
+
return typeof Event !== 'undefined' && isInstanceOf(wat, Event);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Checks whether given value's type is an Element instance
|
|
76
|
+
* {@link isElement}.
|
|
77
|
+
*
|
|
78
|
+
* @param wat A value to be checked.
|
|
79
|
+
* @returns A boolean representing the result.
|
|
80
|
+
*/
|
|
81
|
+
function isElement(wat) {
|
|
82
|
+
return typeof Element !== 'undefined' && isInstanceOf(wat, Element);
|
|
83
|
+
}
|
|
84
|
+
|
|
59
85
|
/**
|
|
60
86
|
* Checks whether given value has a then function.
|
|
61
87
|
* @param wat A value to be checked.
|
|
@@ -65,6 +91,55 @@ function isThenable(wat) {
|
|
|
65
91
|
return Boolean(wat && wat.then && typeof wat.then === 'function');
|
|
66
92
|
}
|
|
67
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Checks whether given value's type is a SyntheticEvent
|
|
96
|
+
* {@link isSyntheticEvent}.
|
|
97
|
+
*
|
|
98
|
+
* @param wat A value to be checked.
|
|
99
|
+
* @returns A boolean representing the result.
|
|
100
|
+
*/
|
|
101
|
+
function isSyntheticEvent(wat) {
|
|
102
|
+
return isPlainObject(wat) && 'nativeEvent' in wat && 'preventDefault' in wat && 'stopPropagation' in wat;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Checks whether given value is NaN
|
|
107
|
+
* {@link isNaN}.
|
|
108
|
+
*
|
|
109
|
+
* @param wat A value to be checked.
|
|
110
|
+
* @returns A boolean representing the result.
|
|
111
|
+
*/
|
|
112
|
+
function isNaN$1(wat) {
|
|
113
|
+
return typeof wat === 'number' && wat !== wat;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Checks whether given value's type is an instance of provided constructor.
|
|
118
|
+
* {@link isInstanceOf}.
|
|
119
|
+
*
|
|
120
|
+
* @param wat A value to be checked.
|
|
121
|
+
* @param base A constructor to be used in a check.
|
|
122
|
+
* @returns A boolean representing the result.
|
|
123
|
+
*/
|
|
124
|
+
function isInstanceOf(wat, base) {
|
|
125
|
+
try {
|
|
126
|
+
return wat instanceof base;
|
|
127
|
+
} catch (_e) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Checks whether given value's type is a Vue ViewModel.
|
|
134
|
+
*
|
|
135
|
+
* @param wat A value to be checked.
|
|
136
|
+
* @returns A boolean representing the result.
|
|
137
|
+
*/
|
|
138
|
+
function isVueViewModel(wat) {
|
|
139
|
+
// Not using Object.prototype.toString because in Vue 3 it would read the instance's Symbol(Symbol.toStringTag) property.
|
|
140
|
+
return !!(typeof wat === 'object' && wat !== null && ((wat ).__isVue || (wat )._isVue));
|
|
141
|
+
}
|
|
142
|
+
|
|
68
143
|
/** Internal global with common properties and Sentry extensions */
|
|
69
144
|
|
|
70
145
|
// The code below for 'isGlobalObj' and 'GLOBAL_OBJ' was copied from core-js before modification
|
|
@@ -108,6 +183,153 @@ const GLOBAL_OBJ =
|
|
|
108
183
|
})() ||
|
|
109
184
|
{};
|
|
110
185
|
|
|
186
|
+
/**
|
|
187
|
+
* @deprecated Use GLOBAL_OBJ instead or WINDOW from @sentry/browser. This will be removed in v8
|
|
188
|
+
*/
|
|
189
|
+
function getGlobalObject() {
|
|
190
|
+
return GLOBAL_OBJ ;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Returns a global singleton contained in the global `__SENTRY__` object.
|
|
195
|
+
*
|
|
196
|
+
* If the singleton doesn't already exist in `__SENTRY__`, it will be created using the given factory
|
|
197
|
+
* function and added to the `__SENTRY__` object.
|
|
198
|
+
*
|
|
199
|
+
* @param name name of the global singleton on __SENTRY__
|
|
200
|
+
* @param creator creator Factory function to create the singleton if it doesn't already exist on `__SENTRY__`
|
|
201
|
+
* @param obj (Optional) The global object on which to look for `__SENTRY__`, if not `GLOBAL_OBJ`'s return value
|
|
202
|
+
* @returns the singleton
|
|
203
|
+
*/
|
|
204
|
+
function getGlobalSingleton(name, creator, obj) {
|
|
205
|
+
const gbl = (obj || GLOBAL_OBJ) ;
|
|
206
|
+
const __SENTRY__ = (gbl.__SENTRY__ = gbl.__SENTRY__ || {});
|
|
207
|
+
const singleton = __SENTRY__[name] || (__SENTRY__[name] = creator());
|
|
208
|
+
return singleton;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
212
|
+
const WINDOW$5 = getGlobalObject();
|
|
213
|
+
|
|
214
|
+
const DEFAULT_MAX_STRING_LENGTH = 80;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Given a child DOM element, returns a query-selector statement describing that
|
|
218
|
+
* and its ancestors
|
|
219
|
+
* e.g. [HTMLElement] => body > div > input#foo.btn[name=baz]
|
|
220
|
+
* @returns generated DOM path
|
|
221
|
+
*/
|
|
222
|
+
function htmlTreeAsString(
|
|
223
|
+
elem,
|
|
224
|
+
options = {},
|
|
225
|
+
) {
|
|
226
|
+
if (!elem) {
|
|
227
|
+
return '<unknown>';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// try/catch both:
|
|
231
|
+
// - accessing event.target (see getsentry/raven-js#838, #768)
|
|
232
|
+
// - `htmlTreeAsString` because it's complex, and just accessing the DOM incorrectly
|
|
233
|
+
// - can throw an exception in some circumstances.
|
|
234
|
+
try {
|
|
235
|
+
let currentElem = elem ;
|
|
236
|
+
const MAX_TRAVERSE_HEIGHT = 5;
|
|
237
|
+
const out = [];
|
|
238
|
+
let height = 0;
|
|
239
|
+
let len = 0;
|
|
240
|
+
const separator = ' > ';
|
|
241
|
+
const sepLength = separator.length;
|
|
242
|
+
let nextStr;
|
|
243
|
+
const keyAttrs = Array.isArray(options) ? options : options.keyAttrs;
|
|
244
|
+
const maxStringLength = (!Array.isArray(options) && options.maxStringLength) || DEFAULT_MAX_STRING_LENGTH;
|
|
245
|
+
|
|
246
|
+
while (currentElem && height++ < MAX_TRAVERSE_HEIGHT) {
|
|
247
|
+
nextStr = _htmlElementAsString(currentElem, keyAttrs);
|
|
248
|
+
// bail out if
|
|
249
|
+
// - nextStr is the 'html' element
|
|
250
|
+
// - the length of the string that would be created exceeds maxStringLength
|
|
251
|
+
// (ignore this limit if we are on the first iteration)
|
|
252
|
+
if (nextStr === 'html' || (height > 1 && len + out.length * sepLength + nextStr.length >= maxStringLength)) {
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
out.push(nextStr);
|
|
257
|
+
|
|
258
|
+
len += nextStr.length;
|
|
259
|
+
currentElem = currentElem.parentNode;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return out.reverse().join(separator);
|
|
263
|
+
} catch (_oO) {
|
|
264
|
+
return '<unknown>';
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Returns a simple, query-selector representation of a DOM element
|
|
270
|
+
* e.g. [HTMLElement] => input#foo.btn[name=baz]
|
|
271
|
+
* @returns generated DOM path
|
|
272
|
+
*/
|
|
273
|
+
function _htmlElementAsString(el, keyAttrs) {
|
|
274
|
+
const elem = el
|
|
275
|
+
|
|
276
|
+
;
|
|
277
|
+
|
|
278
|
+
const out = [];
|
|
279
|
+
let className;
|
|
280
|
+
let classes;
|
|
281
|
+
let key;
|
|
282
|
+
let attr;
|
|
283
|
+
let i;
|
|
284
|
+
|
|
285
|
+
if (!elem || !elem.tagName) {
|
|
286
|
+
return '';
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// @ts-expect-error WINDOW has HTMLElement
|
|
290
|
+
if (WINDOW$5.HTMLElement) {
|
|
291
|
+
// If using the component name annotation plugin, this value may be available on the DOM node
|
|
292
|
+
if (elem instanceof HTMLElement && elem.dataset && elem.dataset['sentryComponent']) {
|
|
293
|
+
return elem.dataset['sentryComponent'];
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
out.push(elem.tagName.toLowerCase());
|
|
298
|
+
|
|
299
|
+
// Pairs of attribute keys defined in `serializeAttribute` and their values on element.
|
|
300
|
+
const keyAttrPairs =
|
|
301
|
+
keyAttrs && keyAttrs.length
|
|
302
|
+
? keyAttrs.filter(keyAttr => elem.getAttribute(keyAttr)).map(keyAttr => [keyAttr, elem.getAttribute(keyAttr)])
|
|
303
|
+
: null;
|
|
304
|
+
|
|
305
|
+
if (keyAttrPairs && keyAttrPairs.length) {
|
|
306
|
+
keyAttrPairs.forEach(keyAttrPair => {
|
|
307
|
+
out.push(`[${keyAttrPair[0]}="${keyAttrPair[1]}"]`);
|
|
308
|
+
});
|
|
309
|
+
} else {
|
|
310
|
+
if (elem.id) {
|
|
311
|
+
out.push(`#${elem.id}`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
className = elem.className;
|
|
315
|
+
if (className && isString(className)) {
|
|
316
|
+
classes = className.split(/\s+/);
|
|
317
|
+
for (i = 0; i < classes.length; i++) {
|
|
318
|
+
out.push(`.${classes[i]}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const allowedAttrs = ['aria-label', 'type', 'name', 'title', 'alt'];
|
|
323
|
+
for (i = 0; i < allowedAttrs.length; i++) {
|
|
324
|
+
key = allowedAttrs[i];
|
|
325
|
+
attr = elem.getAttribute(key);
|
|
326
|
+
if (attr) {
|
|
327
|
+
out.push(`[${key}="${attr}"]`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return out.join('');
|
|
331
|
+
}
|
|
332
|
+
|
|
111
333
|
/**
|
|
112
334
|
* This serves as a build time flag that will be true by default, but false in non-debug builds or if users replace `__SENTRY_DEBUG__` in their generated code.
|
|
113
335
|
*
|
|
@@ -202,21 +424,97 @@ function makeLogger() {
|
|
|
202
424
|
|
|
203
425
|
const logger = makeLogger();
|
|
204
426
|
|
|
427
|
+
const defaultFunctionName = '<anonymous>';
|
|
428
|
+
|
|
205
429
|
/**
|
|
206
|
-
*
|
|
430
|
+
* Safely extract function name from itself
|
|
431
|
+
*/
|
|
432
|
+
function getFunctionName(fn) {
|
|
433
|
+
try {
|
|
434
|
+
if (!fn || typeof fn !== 'function') {
|
|
435
|
+
return defaultFunctionName;
|
|
436
|
+
}
|
|
437
|
+
return fn.name || defaultFunctionName;
|
|
438
|
+
} catch (e) {
|
|
439
|
+
// Just accessing custom props in some Selenium environments
|
|
440
|
+
// can cause a "Permission denied" exception (see raven-js#495).
|
|
441
|
+
return defaultFunctionName;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* This extracts the original function if available. See
|
|
447
|
+
* `markFunctionWrapped` for more information.
|
|
207
448
|
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
|
|
449
|
+
* @param func the function to unwrap
|
|
450
|
+
* @returns the unwrapped version of the function if available.
|
|
451
|
+
*/
|
|
452
|
+
function getOriginalFunction(func) {
|
|
453
|
+
return func.__sentry_original__;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Transforms any `Error` or `Event` into a plain object with all of their enumerable properties, and some of their
|
|
458
|
+
* non-enumerable properties attached.
|
|
211
459
|
*
|
|
212
|
-
* @param
|
|
460
|
+
* @param value Initial source that we have to transform in order for it to be usable by the serializer
|
|
461
|
+
* @returns An Event or Error turned into an object - or the value argurment itself, when value is neither an Event nor
|
|
462
|
+
* an Error.
|
|
213
463
|
*/
|
|
214
|
-
function
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
)
|
|
464
|
+
function convertToPlainObject(
|
|
465
|
+
value,
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
{
|
|
469
|
+
if (isError(value)) {
|
|
470
|
+
return {
|
|
471
|
+
message: value.message,
|
|
472
|
+
name: value.name,
|
|
473
|
+
stack: value.stack,
|
|
474
|
+
...getOwnProperties(value),
|
|
475
|
+
};
|
|
476
|
+
} else if (isEvent(value)) {
|
|
477
|
+
const newObj
|
|
478
|
+
|
|
479
|
+
= {
|
|
480
|
+
type: value.type,
|
|
481
|
+
target: serializeEventTarget(value.target),
|
|
482
|
+
currentTarget: serializeEventTarget(value.currentTarget),
|
|
483
|
+
...getOwnProperties(value),
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
if (typeof CustomEvent !== 'undefined' && isInstanceOf(value, CustomEvent)) {
|
|
487
|
+
newObj.detail = value.detail;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return newObj;
|
|
491
|
+
} else {
|
|
492
|
+
return value;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/** Creates a string representation of the target of an `Event` object */
|
|
497
|
+
function serializeEventTarget(target) {
|
|
498
|
+
try {
|
|
499
|
+
return isElement(target) ? htmlTreeAsString(target) : Object.prototype.toString.call(target);
|
|
500
|
+
} catch (_oO) {
|
|
501
|
+
return '<unknown>';
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/** Filters out all but an object's own properties */
|
|
506
|
+
function getOwnProperties(obj) {
|
|
507
|
+
if (typeof obj === 'object' && obj !== null) {
|
|
508
|
+
const extractedProps = {};
|
|
509
|
+
for (const property in obj) {
|
|
510
|
+
if (Object.prototype.hasOwnProperty.call(obj, property)) {
|
|
511
|
+
extractedProps[property] = (obj )[property];
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return extractedProps;
|
|
515
|
+
} else {
|
|
516
|
+
return {};
|
|
517
|
+
}
|
|
220
518
|
}
|
|
221
519
|
|
|
222
520
|
/**
|
|
@@ -304,28 +602,693 @@ function uuid4() {
|
|
|
304
602
|
if (crypto && crypto.randomUUID) {
|
|
305
603
|
return crypto.randomUUID().replace(/-/g, '');
|
|
306
604
|
}
|
|
307
|
-
if (crypto && crypto.getRandomValues) {
|
|
308
|
-
getRandomByte = () => {
|
|
309
|
-
// crypto.getRandomValues might return undefined instead of the typed array
|
|
310
|
-
// in old Chromium versions (e.g. 23.0.1235.0 (151422))
|
|
311
|
-
// However, `typedArray` is still filled in-place.
|
|
312
|
-
// @see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues#typedarray
|
|
313
|
-
const typedArray = new Uint8Array(1);
|
|
314
|
-
crypto.getRandomValues(typedArray);
|
|
315
|
-
return typedArray[0];
|
|
605
|
+
if (crypto && crypto.getRandomValues) {
|
|
606
|
+
getRandomByte = () => {
|
|
607
|
+
// crypto.getRandomValues might return undefined instead of the typed array
|
|
608
|
+
// in old Chromium versions (e.g. 23.0.1235.0 (151422))
|
|
609
|
+
// However, `typedArray` is still filled in-place.
|
|
610
|
+
// @see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues#typedarray
|
|
611
|
+
const typedArray = new Uint8Array(1);
|
|
612
|
+
crypto.getRandomValues(typedArray);
|
|
613
|
+
return typedArray[0];
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
} catch (_) {
|
|
617
|
+
// some runtimes can crash invoking crypto
|
|
618
|
+
// https://github.com/getsentry/sentry-javascript/issues/8935
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
|
|
622
|
+
// Concatenating the following numbers as strings results in '10000000100040008000100000000000'
|
|
623
|
+
return (([1e7] ) + 1e3 + 4e3 + 8e3 + 1e11).replace(/[018]/g, c =>
|
|
624
|
+
// eslint-disable-next-line no-bitwise
|
|
625
|
+
((c ) ^ ((getRandomByte() & 15) >> ((c ) / 4))).toString(16),
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
630
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Helper to decycle json objects
|
|
634
|
+
*/
|
|
635
|
+
function memoBuilder() {
|
|
636
|
+
const hasWeakSet = typeof WeakSet === 'function';
|
|
637
|
+
const inner = hasWeakSet ? new WeakSet() : [];
|
|
638
|
+
function memoize(obj) {
|
|
639
|
+
if (hasWeakSet) {
|
|
640
|
+
if (inner.has(obj)) {
|
|
641
|
+
return true;
|
|
642
|
+
}
|
|
643
|
+
inner.add(obj);
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
|
647
|
+
for (let i = 0; i < inner.length; i++) {
|
|
648
|
+
const value = inner[i];
|
|
649
|
+
if (value === obj) {
|
|
650
|
+
return true;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
inner.push(obj);
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function unmemoize(obj) {
|
|
658
|
+
if (hasWeakSet) {
|
|
659
|
+
inner.delete(obj);
|
|
660
|
+
} else {
|
|
661
|
+
for (let i = 0; i < inner.length; i++) {
|
|
662
|
+
if (inner[i] === obj) {
|
|
663
|
+
inner.splice(i, 1);
|
|
664
|
+
break;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return [memoize, unmemoize];
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Recursively normalizes the given object.
|
|
674
|
+
*
|
|
675
|
+
* - Creates a copy to prevent original input mutation
|
|
676
|
+
* - Skips non-enumerable properties
|
|
677
|
+
* - When stringifying, calls `toJSON` if implemented
|
|
678
|
+
* - Removes circular references
|
|
679
|
+
* - Translates non-serializable values (`undefined`/`NaN`/functions) to serializable format
|
|
680
|
+
* - Translates known global objects/classes to a string representations
|
|
681
|
+
* - Takes care of `Error` object serialization
|
|
682
|
+
* - Optionally limits depth of final output
|
|
683
|
+
* - Optionally limits number of properties/elements included in any single object/array
|
|
684
|
+
*
|
|
685
|
+
* @param input The object to be normalized.
|
|
686
|
+
* @param depth The max depth to which to normalize the object. (Anything deeper stringified whole.)
|
|
687
|
+
* @param maxProperties The max number of elements or properties to be included in any single array or
|
|
688
|
+
* object in the normallized output.
|
|
689
|
+
* @returns A normalized version of the object, or `"**non-serializable**"` if any errors are thrown during normalization.
|
|
690
|
+
*/
|
|
691
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
692
|
+
function normalize(input, depth = 100, maxProperties = +Infinity) {
|
|
693
|
+
try {
|
|
694
|
+
// since we're at the outermost level, we don't provide a key
|
|
695
|
+
return visit('', input, depth, maxProperties);
|
|
696
|
+
} catch (err) {
|
|
697
|
+
return { ERROR: `**non-serializable** (${err})` };
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Visits a node to perform normalization on it
|
|
703
|
+
*
|
|
704
|
+
* @param key The key corresponding to the given node
|
|
705
|
+
* @param value The node to be visited
|
|
706
|
+
* @param depth Optional number indicating the maximum recursion depth
|
|
707
|
+
* @param maxProperties Optional maximum number of properties/elements included in any single object/array
|
|
708
|
+
* @param memo Optional Memo class handling decycling
|
|
709
|
+
*/
|
|
710
|
+
function visit(
|
|
711
|
+
key,
|
|
712
|
+
value,
|
|
713
|
+
depth = +Infinity,
|
|
714
|
+
maxProperties = +Infinity,
|
|
715
|
+
memo = memoBuilder(),
|
|
716
|
+
) {
|
|
717
|
+
const [memoize, unmemoize] = memo;
|
|
718
|
+
|
|
719
|
+
// Get the simple cases out of the way first
|
|
720
|
+
if (
|
|
721
|
+
value == null || // this matches null and undefined -> eqeq not eqeqeq
|
|
722
|
+
(['number', 'boolean', 'string'].includes(typeof value) && !isNaN$1(value))
|
|
723
|
+
) {
|
|
724
|
+
return value ;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const stringified = stringifyValue(key, value);
|
|
728
|
+
|
|
729
|
+
// Anything we could potentially dig into more (objects or arrays) will have come back as `"[object XXXX]"`.
|
|
730
|
+
// Everything else will have already been serialized, so if we don't see that pattern, we're done.
|
|
731
|
+
if (!stringified.startsWith('[object ')) {
|
|
732
|
+
return stringified;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// From here on, we can assert that `value` is either an object or an array.
|
|
736
|
+
|
|
737
|
+
// Do not normalize objects that we know have already been normalized. As a general rule, the
|
|
738
|
+
// "__sentry_skip_normalization__" property should only be used sparingly and only should only be set on objects that
|
|
739
|
+
// have already been normalized.
|
|
740
|
+
if ((value )['__sentry_skip_normalization__']) {
|
|
741
|
+
return value ;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// We can set `__sentry_override_normalization_depth__` on an object to ensure that from there
|
|
745
|
+
// We keep a certain amount of depth.
|
|
746
|
+
// This should be used sparingly, e.g. we use it for the redux integration to ensure we get a certain amount of state.
|
|
747
|
+
const remainingDepth =
|
|
748
|
+
typeof (value )['__sentry_override_normalization_depth__'] === 'number'
|
|
749
|
+
? ((value )['__sentry_override_normalization_depth__'] )
|
|
750
|
+
: depth;
|
|
751
|
+
|
|
752
|
+
// We're also done if we've reached the max depth
|
|
753
|
+
if (remainingDepth === 0) {
|
|
754
|
+
// At this point we know `serialized` is a string of the form `"[object XXXX]"`. Clean it up so it's just `"[XXXX]"`.
|
|
755
|
+
return stringified.replace('object ', '');
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// If we've already visited this branch, bail out, as it's circular reference. If not, note that we're seeing it now.
|
|
759
|
+
if (memoize(value)) {
|
|
760
|
+
return '[Circular ~]';
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// If the value has a `toJSON` method, we call it to extract more information
|
|
764
|
+
const valueWithToJSON = value ;
|
|
765
|
+
if (valueWithToJSON && typeof valueWithToJSON.toJSON === 'function') {
|
|
766
|
+
try {
|
|
767
|
+
const jsonValue = valueWithToJSON.toJSON();
|
|
768
|
+
// We need to normalize the return value of `.toJSON()` in case it has circular references
|
|
769
|
+
return visit('', jsonValue, remainingDepth - 1, maxProperties, memo);
|
|
770
|
+
} catch (err) {
|
|
771
|
+
// pass (The built-in `toJSON` failed, but we can still try to do it ourselves)
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// At this point we know we either have an object or an array, we haven't seen it before, and we're going to recurse
|
|
776
|
+
// because we haven't yet reached the max depth. Create an accumulator to hold the results of visiting each
|
|
777
|
+
// property/entry, and keep track of the number of items we add to it.
|
|
778
|
+
const normalized = (Array.isArray(value) ? [] : {}) ;
|
|
779
|
+
let numAdded = 0;
|
|
780
|
+
|
|
781
|
+
// Before we begin, convert`Error` and`Event` instances into plain objects, since some of each of their relevant
|
|
782
|
+
// properties are non-enumerable and otherwise would get missed.
|
|
783
|
+
const visitable = convertToPlainObject(value );
|
|
784
|
+
|
|
785
|
+
for (const visitKey in visitable) {
|
|
786
|
+
// Avoid iterating over fields in the prototype if they've somehow been exposed to enumeration.
|
|
787
|
+
if (!Object.prototype.hasOwnProperty.call(visitable, visitKey)) {
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (numAdded >= maxProperties) {
|
|
792
|
+
normalized[visitKey] = '[MaxProperties ~]';
|
|
793
|
+
break;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// Recursively visit all the child nodes
|
|
797
|
+
const visitValue = visitable[visitKey];
|
|
798
|
+
normalized[visitKey] = visit(visitKey, visitValue, remainingDepth - 1, maxProperties, memo);
|
|
799
|
+
|
|
800
|
+
numAdded++;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Once we've visited all the branches, remove the parent from memo storage
|
|
804
|
+
unmemoize(value);
|
|
805
|
+
|
|
806
|
+
// Return accumulated values
|
|
807
|
+
return normalized;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/* eslint-disable complexity */
|
|
811
|
+
/**
|
|
812
|
+
* Stringify the given value. Handles various known special values and types.
|
|
813
|
+
*
|
|
814
|
+
* Not meant to be used on simple primitives which already have a string representation, as it will, for example, turn
|
|
815
|
+
* the number 1231 into "[Object Number]", nor on `null`, as it will throw.
|
|
816
|
+
*
|
|
817
|
+
* @param value The value to stringify
|
|
818
|
+
* @returns A stringified representation of the given value
|
|
819
|
+
*/
|
|
820
|
+
function stringifyValue(
|
|
821
|
+
key,
|
|
822
|
+
// this type is a tiny bit of a cheat, since this function does handle NaN (which is technically a number), but for
|
|
823
|
+
// our internal use, it'll do
|
|
824
|
+
value,
|
|
825
|
+
) {
|
|
826
|
+
try {
|
|
827
|
+
if (key === 'domain' && value && typeof value === 'object' && (value )._events) {
|
|
828
|
+
return '[Domain]';
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
if (key === 'domainEmitter') {
|
|
832
|
+
return '[DomainEmitter]';
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// It's safe to use `global`, `window`, and `document` here in this manner, as we are asserting using `typeof` first
|
|
836
|
+
// which won't throw if they are not present.
|
|
837
|
+
|
|
838
|
+
if (typeof global !== 'undefined' && value === global) {
|
|
839
|
+
return '[Global]';
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// eslint-disable-next-line no-restricted-globals
|
|
843
|
+
if (typeof window !== 'undefined' && value === window) {
|
|
844
|
+
return '[Window]';
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// eslint-disable-next-line no-restricted-globals
|
|
848
|
+
if (typeof document !== 'undefined' && value === document) {
|
|
849
|
+
return '[Document]';
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
if (isVueViewModel(value)) {
|
|
853
|
+
return '[VueViewModel]';
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// React's SyntheticEvent thingy
|
|
857
|
+
if (isSyntheticEvent(value)) {
|
|
858
|
+
return '[SyntheticEvent]';
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
if (typeof value === 'number' && value !== value) {
|
|
862
|
+
return '[NaN]';
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
if (typeof value === 'function') {
|
|
866
|
+
return `[Function: ${getFunctionName(value)}]`;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
if (typeof value === 'symbol') {
|
|
870
|
+
return `[${String(value)}]`;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// stringified BigInts are indistinguishable from regular numbers, so we need to label them to avoid confusion
|
|
874
|
+
if (typeof value === 'bigint') {
|
|
875
|
+
return `[BigInt: ${String(value)}]`;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// Now that we've knocked out all the special cases and the primitives, all we have left are objects. Simply casting
|
|
879
|
+
// them to strings means that instances of classes which haven't defined their `toStringTag` will just come out as
|
|
880
|
+
// `"[object Object]"`. If we instead look at the constructor's name (which is the same as the name of the class),
|
|
881
|
+
// we can make sure that only plain objects come out that way.
|
|
882
|
+
const objName = getConstructorName(value);
|
|
883
|
+
|
|
884
|
+
// Handle HTML Elements
|
|
885
|
+
if (/^HTML(\w*)Element$/.test(objName)) {
|
|
886
|
+
return `[HTMLElement: ${objName}]`;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
return `[object ${objName}]`;
|
|
890
|
+
} catch (err) {
|
|
891
|
+
return `**non-serializable** (${err})`;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
/* eslint-enable complexity */
|
|
895
|
+
|
|
896
|
+
function getConstructorName(value) {
|
|
897
|
+
const prototype = Object.getPrototypeOf(value);
|
|
898
|
+
|
|
899
|
+
return prototype ? prototype.constructor.name : 'null prototype';
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
|
903
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
904
|
+
|
|
905
|
+
/** SyncPromise internal states */
|
|
906
|
+
var States; (function (States) {
|
|
907
|
+
/** Pending */
|
|
908
|
+
const PENDING = 0; States[States["PENDING"] = PENDING] = "PENDING";
|
|
909
|
+
/** Resolved / OK */
|
|
910
|
+
const RESOLVED = 1; States[States["RESOLVED"] = RESOLVED] = "RESOLVED";
|
|
911
|
+
/** Rejected / Error */
|
|
912
|
+
const REJECTED = 2; States[States["REJECTED"] = REJECTED] = "REJECTED";
|
|
913
|
+
})(States || (States = {}));
|
|
914
|
+
|
|
915
|
+
/**
|
|
916
|
+
* This code was originally copied from the 'cookie` module at v0.5.0 and was simplified for our use case.
|
|
917
|
+
* https://github.com/jshttp/cookie/blob/a0c84147aab6266bdb3996cf4062e93907c0b0fc/index.js
|
|
918
|
+
* It had the following license:
|
|
919
|
+
*
|
|
920
|
+
* (The MIT License)
|
|
921
|
+
*
|
|
922
|
+
* Copyright (c) 2012-2014 Roman Shtylman <shtylman@gmail.com>
|
|
923
|
+
* Copyright (c) 2015 Douglas Christopher Wilson <doug@somethingdoug.com>
|
|
924
|
+
*
|
|
925
|
+
* Permission is hereby granted, free of charge, to any person obtaining
|
|
926
|
+
* a copy of this software and associated documentation files (the
|
|
927
|
+
* 'Software'), to deal in the Software without restriction, including
|
|
928
|
+
* without limitation the rights to use, copy, modify, merge, publish,
|
|
929
|
+
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
930
|
+
* permit persons to whom the Software is furnished to do so, subject to
|
|
931
|
+
* the following conditions:
|
|
932
|
+
*
|
|
933
|
+
* The above copyright notice and this permission notice shall be
|
|
934
|
+
* included in all copies or substantial portions of the Software.
|
|
935
|
+
*
|
|
936
|
+
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
937
|
+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
938
|
+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
939
|
+
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
940
|
+
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
941
|
+
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
942
|
+
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
943
|
+
*/
|
|
944
|
+
|
|
945
|
+
/**
|
|
946
|
+
* Parses a cookie string
|
|
947
|
+
*/
|
|
948
|
+
function parseCookie(str) {
|
|
949
|
+
const obj = {};
|
|
950
|
+
let index = 0;
|
|
951
|
+
|
|
952
|
+
while (index < str.length) {
|
|
953
|
+
const eqIdx = str.indexOf('=', index);
|
|
954
|
+
|
|
955
|
+
// no more cookie pairs
|
|
956
|
+
if (eqIdx === -1) {
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
let endIdx = str.indexOf(';', index);
|
|
961
|
+
|
|
962
|
+
if (endIdx === -1) {
|
|
963
|
+
endIdx = str.length;
|
|
964
|
+
} else if (endIdx < eqIdx) {
|
|
965
|
+
// backtrack on prior semicolon
|
|
966
|
+
index = str.lastIndexOf(';', eqIdx - 1) + 1;
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
const key = str.slice(index, eqIdx).trim();
|
|
971
|
+
|
|
972
|
+
// only assign once
|
|
973
|
+
if (undefined === obj[key]) {
|
|
974
|
+
let val = str.slice(eqIdx + 1, endIdx).trim();
|
|
975
|
+
|
|
976
|
+
// quoted values
|
|
977
|
+
if (val.charCodeAt(0) === 0x22) {
|
|
978
|
+
val = val.slice(1, -1);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
try {
|
|
982
|
+
obj[key] = val.indexOf('%') !== -1 ? decodeURIComponent(val) : val;
|
|
983
|
+
} catch (e) {
|
|
984
|
+
obj[key] = val;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
index = endIdx + 1;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
return obj;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* Strip the query string and fragment off of a given URL or path (if present)
|
|
996
|
+
*
|
|
997
|
+
* @param urlPath Full URL or path, including possible query string and/or fragment
|
|
998
|
+
* @returns URL or path without query string or fragment
|
|
999
|
+
*/
|
|
1000
|
+
function stripUrlQueryAndFragment(urlPath) {
|
|
1001
|
+
// eslint-disable-next-line no-useless-escape
|
|
1002
|
+
return urlPath.split(/[\?#]/, 1)[0];
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
const DEFAULT_INCLUDES = {
|
|
1006
|
+
ip: false,
|
|
1007
|
+
request: true,
|
|
1008
|
+
transaction: true,
|
|
1009
|
+
user: true,
|
|
1010
|
+
};
|
|
1011
|
+
const DEFAULT_REQUEST_INCLUDES = ['cookies', 'data', 'headers', 'method', 'query_string', 'url'];
|
|
1012
|
+
const DEFAULT_USER_INCLUDES = ['id', 'username', 'email'];
|
|
1013
|
+
|
|
1014
|
+
/**
|
|
1015
|
+
* Extracts a complete and parameterized path from the request object and uses it to construct transaction name.
|
|
1016
|
+
* If the parameterized transaction name cannot be extracted, we fall back to the raw URL.
|
|
1017
|
+
*
|
|
1018
|
+
* Additionally, this function determines and returns the transaction name source
|
|
1019
|
+
*
|
|
1020
|
+
* eg. GET /mountpoint/user/:id
|
|
1021
|
+
*
|
|
1022
|
+
* @param req A request object
|
|
1023
|
+
* @param options What to include in the transaction name (method, path, or a custom route name to be
|
|
1024
|
+
* used instead of the request's route)
|
|
1025
|
+
*
|
|
1026
|
+
* @returns A tuple of the fully constructed transaction name [0] and its source [1] (can be either 'route' or 'url')
|
|
1027
|
+
*/
|
|
1028
|
+
function extractPathForTransaction(
|
|
1029
|
+
req,
|
|
1030
|
+
options = {},
|
|
1031
|
+
) {
|
|
1032
|
+
const method = req.method && req.method.toUpperCase();
|
|
1033
|
+
|
|
1034
|
+
let path = '';
|
|
1035
|
+
let source = 'url';
|
|
1036
|
+
|
|
1037
|
+
// Check to see if there's a parameterized route we can use (as there is in Express)
|
|
1038
|
+
if (options.customRoute || req.route) {
|
|
1039
|
+
path = options.customRoute || `${req.baseUrl || ''}${req.route && req.route.path}`;
|
|
1040
|
+
source = 'route';
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// Otherwise, just take the original URL
|
|
1044
|
+
else if (req.originalUrl || req.url) {
|
|
1045
|
+
path = stripUrlQueryAndFragment(req.originalUrl || req.url || '');
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
let name = '';
|
|
1049
|
+
if (options.method && method) {
|
|
1050
|
+
name += method;
|
|
1051
|
+
}
|
|
1052
|
+
if (options.method && options.path) {
|
|
1053
|
+
name += ' ';
|
|
1054
|
+
}
|
|
1055
|
+
if (options.path && path) {
|
|
1056
|
+
name += path;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
return [name, source];
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/** JSDoc */
|
|
1063
|
+
function extractTransaction(req, type) {
|
|
1064
|
+
switch (type) {
|
|
1065
|
+
case 'path': {
|
|
1066
|
+
return extractPathForTransaction(req, { path: true })[0];
|
|
1067
|
+
}
|
|
1068
|
+
case 'handler': {
|
|
1069
|
+
return (req.route && req.route.stack && req.route.stack[0] && req.route.stack[0].name) || '<anonymous>';
|
|
1070
|
+
}
|
|
1071
|
+
case 'methodPath':
|
|
1072
|
+
default: {
|
|
1073
|
+
// if exist _reconstructedRoute return that path instead of route.path
|
|
1074
|
+
const customRoute = req._reconstructedRoute ? req._reconstructedRoute : undefined;
|
|
1075
|
+
return extractPathForTransaction(req, { path: true, method: true, customRoute })[0];
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
/** JSDoc */
|
|
1081
|
+
function extractUserData(
|
|
1082
|
+
user
|
|
1083
|
+
|
|
1084
|
+
,
|
|
1085
|
+
keys,
|
|
1086
|
+
) {
|
|
1087
|
+
const extractedUser = {};
|
|
1088
|
+
const attributes = Array.isArray(keys) ? keys : DEFAULT_USER_INCLUDES;
|
|
1089
|
+
|
|
1090
|
+
attributes.forEach(key => {
|
|
1091
|
+
if (user && key in user) {
|
|
1092
|
+
extractedUser[key] = user[key];
|
|
1093
|
+
}
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
return extractedUser;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
/**
|
|
1100
|
+
* Normalize data from the request object, accounting for framework differences.
|
|
1101
|
+
*
|
|
1102
|
+
* @param req The request object from which to extract data
|
|
1103
|
+
* @param options.include An optional array of keys to include in the normalized data. Defaults to
|
|
1104
|
+
* DEFAULT_REQUEST_INCLUDES if not provided.
|
|
1105
|
+
* @param options.deps Injected, platform-specific dependencies
|
|
1106
|
+
* @returns An object containing normalized request data
|
|
1107
|
+
*/
|
|
1108
|
+
function extractRequestData(
|
|
1109
|
+
req,
|
|
1110
|
+
options
|
|
1111
|
+
|
|
1112
|
+
,
|
|
1113
|
+
) {
|
|
1114
|
+
const { include = DEFAULT_REQUEST_INCLUDES } = options || {};
|
|
1115
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1116
|
+
const requestData = {};
|
|
1117
|
+
|
|
1118
|
+
// headers:
|
|
1119
|
+
// node, express, koa, nextjs: req.headers
|
|
1120
|
+
const headers = (req.headers || {})
|
|
1121
|
+
|
|
1122
|
+
;
|
|
1123
|
+
// method:
|
|
1124
|
+
// node, express, koa, nextjs: req.method
|
|
1125
|
+
const method = req.method;
|
|
1126
|
+
// host:
|
|
1127
|
+
// express: req.hostname in > 4 and req.host in < 4
|
|
1128
|
+
// koa: req.host
|
|
1129
|
+
// node, nextjs: req.headers.host
|
|
1130
|
+
// Express 4 mistakenly strips off port number from req.host / req.hostname so we can't rely on them
|
|
1131
|
+
// See: https://github.com/expressjs/express/issues/3047#issuecomment-236653223
|
|
1132
|
+
// Also: https://github.com/getsentry/sentry-javascript/issues/1917
|
|
1133
|
+
const host = headers.host || req.hostname || req.host || '<no host>';
|
|
1134
|
+
// protocol:
|
|
1135
|
+
// node, nextjs: <n/a>
|
|
1136
|
+
// express, koa: req.protocol
|
|
1137
|
+
const protocol = req.protocol === 'https' || (req.socket && req.socket.encrypted) ? 'https' : 'http';
|
|
1138
|
+
// url (including path and query string):
|
|
1139
|
+
// node, express: req.originalUrl
|
|
1140
|
+
// koa, nextjs: req.url
|
|
1141
|
+
const originalUrl = req.originalUrl || req.url || '';
|
|
1142
|
+
// absolute url
|
|
1143
|
+
const absoluteUrl = originalUrl.startsWith(protocol) ? originalUrl : `${protocol}://${host}${originalUrl}`;
|
|
1144
|
+
include.forEach(key => {
|
|
1145
|
+
switch (key) {
|
|
1146
|
+
case 'headers': {
|
|
1147
|
+
requestData.headers = headers;
|
|
1148
|
+
|
|
1149
|
+
// Remove the Cookie header in case cookie data should not be included in the event
|
|
1150
|
+
if (!include.includes('cookies')) {
|
|
1151
|
+
delete (requestData.headers ).cookie;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
break;
|
|
1155
|
+
}
|
|
1156
|
+
case 'method': {
|
|
1157
|
+
requestData.method = method;
|
|
1158
|
+
break;
|
|
1159
|
+
}
|
|
1160
|
+
case 'url': {
|
|
1161
|
+
requestData.url = absoluteUrl;
|
|
1162
|
+
break;
|
|
1163
|
+
}
|
|
1164
|
+
case 'cookies': {
|
|
1165
|
+
// cookies:
|
|
1166
|
+
// node, express, koa: req.headers.cookie
|
|
1167
|
+
// vercel, sails.js, express (w/ cookie middleware), nextjs: req.cookies
|
|
1168
|
+
requestData.cookies =
|
|
1169
|
+
// TODO (v8 / #5257): We're only sending the empty object for backwards compatibility, so the last bit can
|
|
1170
|
+
// come off in v8
|
|
1171
|
+
req.cookies || (headers.cookie && parseCookie(headers.cookie)) || {};
|
|
1172
|
+
break;
|
|
1173
|
+
}
|
|
1174
|
+
case 'query_string': {
|
|
1175
|
+
// query string:
|
|
1176
|
+
// node: req.url (raw)
|
|
1177
|
+
// express, koa, nextjs: req.query
|
|
1178
|
+
requestData.query_string = extractQueryParams(req);
|
|
1179
|
+
break;
|
|
1180
|
+
}
|
|
1181
|
+
case 'data': {
|
|
1182
|
+
if (method === 'GET' || method === 'HEAD') {
|
|
1183
|
+
break;
|
|
1184
|
+
}
|
|
1185
|
+
// body data:
|
|
1186
|
+
// express, koa, nextjs: req.body
|
|
1187
|
+
//
|
|
1188
|
+
// when using node by itself, you have to read the incoming stream(see
|
|
1189
|
+
// https://nodejs.dev/learn/get-http-request-body-data-using-nodejs); if a user is doing that, we can't know
|
|
1190
|
+
// where they're going to store the final result, so they'll have to capture this data themselves
|
|
1191
|
+
if (req.body !== undefined) {
|
|
1192
|
+
requestData.data = isString(req.body) ? req.body : JSON.stringify(normalize(req.body));
|
|
1193
|
+
}
|
|
1194
|
+
break;
|
|
1195
|
+
}
|
|
1196
|
+
default: {
|
|
1197
|
+
if ({}.hasOwnProperty.call(req, key)) {
|
|
1198
|
+
requestData[key] = (req )[key];
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
return requestData;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
/**
|
|
1208
|
+
* Add data from the given request to the given event
|
|
1209
|
+
*
|
|
1210
|
+
* @param event The event to which the request data will be added
|
|
1211
|
+
* @param req Request object
|
|
1212
|
+
* @param options.include Flags to control what data is included
|
|
1213
|
+
* @param options.deps Injected platform-specific dependencies
|
|
1214
|
+
* @returns The mutated `Event` object
|
|
1215
|
+
*/
|
|
1216
|
+
function addRequestDataToEvent(
|
|
1217
|
+
event,
|
|
1218
|
+
req,
|
|
1219
|
+
options,
|
|
1220
|
+
) {
|
|
1221
|
+
const include = {
|
|
1222
|
+
...DEFAULT_INCLUDES,
|
|
1223
|
+
...(options && options.include),
|
|
1224
|
+
};
|
|
1225
|
+
|
|
1226
|
+
if (include.request) {
|
|
1227
|
+
const extractedRequestData = Array.isArray(include.request)
|
|
1228
|
+
? extractRequestData(req, { include: include.request })
|
|
1229
|
+
: extractRequestData(req);
|
|
1230
|
+
|
|
1231
|
+
event.request = {
|
|
1232
|
+
...event.request,
|
|
1233
|
+
...extractedRequestData,
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
if (include.user) {
|
|
1238
|
+
const extractedUser = req.user && isPlainObject(req.user) ? extractUserData(req.user, include.user) : {};
|
|
1239
|
+
|
|
1240
|
+
if (Object.keys(extractedUser).length) {
|
|
1241
|
+
event.user = {
|
|
1242
|
+
...event.user,
|
|
1243
|
+
...extractedUser,
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// client ip:
|
|
1249
|
+
// node, nextjs: req.socket.remoteAddress
|
|
1250
|
+
// express, koa: req.ip
|
|
1251
|
+
if (include.ip) {
|
|
1252
|
+
const ip = req.ip || (req.socket && req.socket.remoteAddress);
|
|
1253
|
+
if (ip) {
|
|
1254
|
+
event.user = {
|
|
1255
|
+
...event.user,
|
|
1256
|
+
ip_address: ip,
|
|
316
1257
|
};
|
|
317
1258
|
}
|
|
318
|
-
} catch (_) {
|
|
319
|
-
// some runtimes can crash invoking crypto
|
|
320
|
-
// https://github.com/getsentry/sentry-javascript/issues/8935
|
|
321
1259
|
}
|
|
322
1260
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
1261
|
+
if (include.transaction && !event.transaction) {
|
|
1262
|
+
// TODO do we even need this anymore?
|
|
1263
|
+
// TODO make this work for nextjs
|
|
1264
|
+
event.transaction = extractTransaction(req, include.transaction);
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
return event;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
function extractQueryParams(req) {
|
|
1271
|
+
// url (including path and query string):
|
|
1272
|
+
// node, express: req.originalUrl
|
|
1273
|
+
// koa, nextjs: req.url
|
|
1274
|
+
let originalUrl = req.originalUrl || req.url || '';
|
|
1275
|
+
|
|
1276
|
+
if (!originalUrl) {
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// The `URL` constructor can't handle internal URLs of the form `/some/path/here`, so stick a dummy protocol and
|
|
1281
|
+
// hostname on the beginning. Since the point here is just to grab the query string, it doesn't matter what we use.
|
|
1282
|
+
if (originalUrl.startsWith('/')) {
|
|
1283
|
+
originalUrl = `http://dogs.are.great${originalUrl}`;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
try {
|
|
1287
|
+
const queryParams = req.query || new URL(originalUrl).search.slice(1);
|
|
1288
|
+
return queryParams.length ? queryParams : undefined;
|
|
1289
|
+
} catch (e2) {
|
|
1290
|
+
return undefined;
|
|
1291
|
+
}
|
|
329
1292
|
}
|
|
330
1293
|
|
|
331
1294
|
const ONE_SECOND_IN_MS = 1000;
|
|
@@ -433,15 +1396,6 @@ const timestampInSeconds = createUnixTimestampInSecondsFunc();
|
|
|
433
1396
|
return dateNow;
|
|
434
1397
|
})();
|
|
435
1398
|
|
|
436
|
-
/**
|
|
437
|
-
* Creates an envelope.
|
|
438
|
-
* Make sure to always explicitly provide the generic to this function
|
|
439
|
-
* so that the envelope types resolve correctly.
|
|
440
|
-
*/
|
|
441
|
-
function createEnvelope(headers, items = []) {
|
|
442
|
-
return [headers, items] ;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
1399
|
/**
|
|
446
1400
|
* Convenience function to loop through the items and item types of an envelope.
|
|
447
1401
|
* (This function was mostly created because working with envelope types is painful at the moment)
|
|
@@ -466,6 +1420,13 @@ function forEachEnvelopeItem(
|
|
|
466
1420
|
return false;
|
|
467
1421
|
}
|
|
468
1422
|
|
|
1423
|
+
/**
|
|
1424
|
+
* This serves as a build time flag that will be true by default, but false in non-debug builds or if users replace `__SENTRY_DEBUG__` in their generated code.
|
|
1425
|
+
*
|
|
1426
|
+
* ATTENTION: This constant must never cross package boundaries (i.e. be exported) to guarantee that it can be used for tree shaking.
|
|
1427
|
+
*/
|
|
1428
|
+
const DEBUG_BUILD$1 = (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__);
|
|
1429
|
+
|
|
469
1430
|
/**
|
|
470
1431
|
* @private Private API with no semver guarantees!
|
|
471
1432
|
*
|
|
@@ -495,13 +1456,6 @@ function getSentryCarrier(carrier) {
|
|
|
495
1456
|
return carrier.__SENTRY__;
|
|
496
1457
|
}
|
|
497
1458
|
|
|
498
|
-
/**
|
|
499
|
-
* This serves as a build time flag that will be true by default, but false in non-debug builds or if users replace `__SENTRY_DEBUG__` in their generated code.
|
|
500
|
-
*
|
|
501
|
-
* ATTENTION: This constant must never cross package boundaries (i.e. be exported) to guarantee that it can be used for tree shaking.
|
|
502
|
-
*/
|
|
503
|
-
const DEBUG_BUILD$1 = (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__);
|
|
504
|
-
|
|
505
1459
|
const DEFAULT_ENVIRONMENT = 'production';
|
|
506
1460
|
|
|
507
1461
|
/**
|
|
@@ -700,6 +1654,9 @@ class Scope {
|
|
|
700
1654
|
|
|
701
1655
|
/**
|
|
702
1656
|
* Transaction Name
|
|
1657
|
+
*
|
|
1658
|
+
* IMPORTANT: The transaction name on the scope has nothing to do with root spans/transaction objects.
|
|
1659
|
+
* It's purpose is to assign a transaction to the scope that's added to non-transaction events.
|
|
703
1660
|
*/
|
|
704
1661
|
|
|
705
1662
|
/** Span */
|
|
@@ -735,7 +1692,7 @@ class Scope {
|
|
|
735
1692
|
}
|
|
736
1693
|
|
|
737
1694
|
/**
|
|
738
|
-
*
|
|
1695
|
+
* @inheritDoc
|
|
739
1696
|
*/
|
|
740
1697
|
clone() {
|
|
741
1698
|
const newScope = new Scope();
|
|
@@ -759,23 +1716,22 @@ class Scope {
|
|
|
759
1716
|
return newScope;
|
|
760
1717
|
}
|
|
761
1718
|
|
|
762
|
-
/**
|
|
1719
|
+
/**
|
|
1720
|
+
* @inheritDoc
|
|
1721
|
+
*/
|
|
763
1722
|
setClient(client) {
|
|
764
1723
|
this._client = client;
|
|
765
1724
|
}
|
|
766
1725
|
|
|
767
1726
|
/**
|
|
768
|
-
*
|
|
769
|
-
*
|
|
770
|
-
* It is generally recommended to use the global function `Sentry.getClient()` instead, unless you know what you are doing.
|
|
1727
|
+
* @inheritDoc
|
|
771
1728
|
*/
|
|
772
1729
|
getClient() {
|
|
773
1730
|
return this._client ;
|
|
774
1731
|
}
|
|
775
1732
|
|
|
776
1733
|
/**
|
|
777
|
-
*
|
|
778
|
-
* @hidden
|
|
1734
|
+
* @inheritDoc
|
|
779
1735
|
*/
|
|
780
1736
|
addScopeListener(callback) {
|
|
781
1737
|
this._scopeListeners.push(callback);
|
|
@@ -893,8 +1849,7 @@ class Scope {
|
|
|
893
1849
|
}
|
|
894
1850
|
|
|
895
1851
|
/**
|
|
896
|
-
*
|
|
897
|
-
* @deprecated Use extra or tags instead.
|
|
1852
|
+
* @inheritDoc
|
|
898
1853
|
*/
|
|
899
1854
|
setTransactionName(name) {
|
|
900
1855
|
this._transactionName = name;
|
|
@@ -1156,7 +2111,7 @@ class Scope {
|
|
|
1156
2111
|
}
|
|
1157
2112
|
|
|
1158
2113
|
/**
|
|
1159
|
-
*
|
|
2114
|
+
* @inheritDoc
|
|
1160
2115
|
*/
|
|
1161
2116
|
setSDKProcessingMetadata(newData) {
|
|
1162
2117
|
this._sdkProcessingMetadata = { ...this._sdkProcessingMetadata, ...newData };
|
|
@@ -1180,11 +2135,7 @@ class Scope {
|
|
|
1180
2135
|
}
|
|
1181
2136
|
|
|
1182
2137
|
/**
|
|
1183
|
-
*
|
|
1184
|
-
*
|
|
1185
|
-
* @param exception The exception to capture.
|
|
1186
|
-
* @param hint Optinal additional data to attach to the Sentry event.
|
|
1187
|
-
* @returns the id of the captured Sentry event.
|
|
2138
|
+
* @inheritDoc
|
|
1188
2139
|
*/
|
|
1189
2140
|
captureException(exception, hint) {
|
|
1190
2141
|
const eventId = hint && hint.event_id ? hint.event_id : uuid4();
|
|
@@ -1211,12 +2162,7 @@ class Scope {
|
|
|
1211
2162
|
}
|
|
1212
2163
|
|
|
1213
2164
|
/**
|
|
1214
|
-
*
|
|
1215
|
-
*
|
|
1216
|
-
* @param message The message to capture.
|
|
1217
|
-
* @param level An optional severity level to report the message with.
|
|
1218
|
-
* @param hint Optional additional data to attach to the Sentry event.
|
|
1219
|
-
* @returns the id of the captured message.
|
|
2165
|
+
* @inheritDoc
|
|
1220
2166
|
*/
|
|
1221
2167
|
captureMessage(message, level, hint) {
|
|
1222
2168
|
const eventId = hint && hint.event_id ? hint.event_id : uuid4();
|
|
@@ -1244,11 +2190,7 @@ class Scope {
|
|
|
1244
2190
|
}
|
|
1245
2191
|
|
|
1246
2192
|
/**
|
|
1247
|
-
*
|
|
1248
|
-
*
|
|
1249
|
-
* @param exception The event to capture.
|
|
1250
|
-
* @param hint Optional additional data to attach to the Sentry event.
|
|
1251
|
-
* @returns the id of the captured event.
|
|
2193
|
+
* @inheritDoc
|
|
1252
2194
|
*/
|
|
1253
2195
|
captureEvent(event, hint) {
|
|
1254
2196
|
const eventId = hint && hint.event_id ? hint.event_id : uuid4();
|
|
@@ -1287,7 +2229,7 @@ function generatePropagationContext() {
|
|
|
1287
2229
|
};
|
|
1288
2230
|
}
|
|
1289
2231
|
|
|
1290
|
-
const SDK_VERSION = '8.0.0-alpha.
|
|
2232
|
+
const SDK_VERSION = '8.0.0-alpha.3';
|
|
1291
2233
|
|
|
1292
2234
|
/**
|
|
1293
2235
|
* API compatibility version of this hub.
|
|
@@ -1303,7 +2245,7 @@ const API_VERSION = parseFloat(SDK_VERSION);
|
|
|
1303
2245
|
* Default maximum number of breadcrumbs added to an event. Can be overwritten
|
|
1304
2246
|
* with {@link Options.maxBreadcrumbs}.
|
|
1305
2247
|
*/
|
|
1306
|
-
const DEFAULT_BREADCRUMBS = 100;
|
|
2248
|
+
const DEFAULT_BREADCRUMBS$1 = 100;
|
|
1307
2249
|
|
|
1308
2250
|
/**
|
|
1309
2251
|
* A layer in the process stack.
|
|
@@ -1599,7 +2541,7 @@ class Hub {
|
|
|
1599
2541
|
|
|
1600
2542
|
if (!client) return;
|
|
1601
2543
|
|
|
1602
|
-
const { beforeBreadcrumb = null, maxBreadcrumbs = DEFAULT_BREADCRUMBS } =
|
|
2544
|
+
const { beforeBreadcrumb = null, maxBreadcrumbs = DEFAULT_BREADCRUMBS$1 } =
|
|
1603
2545
|
(client.getOptions && client.getOptions()) || {};
|
|
1604
2546
|
|
|
1605
2547
|
if (maxBreadcrumbs <= 0) return;
|
|
@@ -1690,46 +2632,6 @@ class Hub {
|
|
|
1690
2632
|
}
|
|
1691
2633
|
}
|
|
1692
2634
|
|
|
1693
|
-
/**
|
|
1694
|
-
* Starts a new `Transaction` and returns it. This is the entry point to manual tracing instrumentation.
|
|
1695
|
-
*
|
|
1696
|
-
* A tree structure can be built by adding child spans to the transaction, and child spans to other spans. To start a
|
|
1697
|
-
* new child span within the transaction or any span, call the respective `.startChild()` method.
|
|
1698
|
-
*
|
|
1699
|
-
* Every child span must be finished before the transaction is finished, otherwise the unfinished spans are discarded.
|
|
1700
|
-
*
|
|
1701
|
-
* The transaction must be finished with a call to its `.end()` method, at which point the transaction with all its
|
|
1702
|
-
* finished child spans will be sent to Sentry.
|
|
1703
|
-
*
|
|
1704
|
-
* @param context Properties of the new `Transaction`.
|
|
1705
|
-
* @param customSamplingContext Information given to the transaction sampling function (along with context-dependent
|
|
1706
|
-
* default values). See {@link Options.tracesSampler}.
|
|
1707
|
-
*
|
|
1708
|
-
* @returns The transaction which was just started
|
|
1709
|
-
*
|
|
1710
|
-
* @deprecated Use `startSpan()`, `startSpanManual()` or `startInactiveSpan()` instead.
|
|
1711
|
-
*/
|
|
1712
|
-
startTransaction(context, customSamplingContext) {
|
|
1713
|
-
const result = this._callExtensionMethod('startTransaction', context, customSamplingContext);
|
|
1714
|
-
|
|
1715
|
-
if (DEBUG_BUILD$1 && !result) {
|
|
1716
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
1717
|
-
const client = this.getClient();
|
|
1718
|
-
if (!client) {
|
|
1719
|
-
logger.warn(
|
|
1720
|
-
"Tracing extension 'startTransaction' is missing. You should 'init' the SDK before calling 'startTransaction'",
|
|
1721
|
-
);
|
|
1722
|
-
} else {
|
|
1723
|
-
logger.warn(`Tracing extension 'startTransaction' has not been added. Call 'addTracingExtensions' before calling 'init':
|
|
1724
|
-
Sentry.addTracingExtensions();
|
|
1725
|
-
Sentry.init({...});
|
|
1726
|
-
`);
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
|
|
1730
|
-
return result;
|
|
1731
|
-
}
|
|
1732
|
-
|
|
1733
2635
|
/**
|
|
1734
2636
|
* @inheritDoc
|
|
1735
2637
|
*
|
|
@@ -1857,128 +2759,635 @@ function getCurrentHub() {
|
|
|
1857
2759
|
return acs.getCurrentHub() || getGlobalHub();
|
|
1858
2760
|
}
|
|
1859
2761
|
|
|
1860
|
-
|
|
1861
|
-
|
|
2762
|
+
/** Get the default current scope. */
|
|
2763
|
+
function getDefaultCurrentScope() {
|
|
2764
|
+
return getGlobalSingleton('defaultCurrentScope', () => new Scope());
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
/** Get the default isolation scope. */
|
|
2768
|
+
function getDefaultIsolationScope() {
|
|
2769
|
+
return getGlobalSingleton('defaultIsolationScope', () => new Scope());
|
|
2770
|
+
}
|
|
2771
|
+
|
|
2772
|
+
/**
|
|
2773
|
+
* Get the global hub.
|
|
2774
|
+
* This will be removed during the v8 cycle and is only here to make migration easier.
|
|
2775
|
+
*/
|
|
2776
|
+
function getGlobalHub() {
|
|
2777
|
+
const registry = getMainCarrier();
|
|
2778
|
+
const sentry = getSentryCarrier(registry) ;
|
|
2779
|
+
|
|
2780
|
+
// If there's no hub, or its an old API, assign a new one
|
|
2781
|
+
if (sentry.hub) {
|
|
2782
|
+
return sentry.hub;
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2785
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
2786
|
+
sentry.hub = new Hub(undefined, getDefaultCurrentScope(), getDefaultIsolationScope());
|
|
2787
|
+
return sentry.hub;
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2790
|
+
/**
|
|
2791
|
+
* Get the current async context strategy.
|
|
2792
|
+
* If none has been setup, the default will be used.
|
|
2793
|
+
*/
|
|
2794
|
+
function getAsyncContextStrategy(carrier) {
|
|
2795
|
+
const sentry = getSentryCarrier(carrier);
|
|
2796
|
+
|
|
2797
|
+
if (sentry.acs) {
|
|
2798
|
+
return sentry.acs;
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
// Otherwise, use the default one
|
|
2802
|
+
return getHubStackAsyncContextStrategy();
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2805
|
+
function withScope$1(callback) {
|
|
2806
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
2807
|
+
return getGlobalHub().withScope(callback);
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
function withSetScope(scope, callback) {
|
|
2811
|
+
const hub = getGlobalHub() ;
|
|
2812
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
2813
|
+
return hub.withScope(() => {
|
|
2814
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
2815
|
+
hub.getStackTop().scope = scope ;
|
|
2816
|
+
return callback(scope);
|
|
2817
|
+
});
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2820
|
+
function withIsolationScope$1(callback) {
|
|
2821
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
2822
|
+
return getGlobalHub().withScope(() => {
|
|
2823
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
2824
|
+
return callback(getGlobalHub().getIsolationScope());
|
|
2825
|
+
});
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
/* eslint-disable deprecation/deprecation */
|
|
2829
|
+
function getHubStackAsyncContextStrategy() {
|
|
2830
|
+
return {
|
|
2831
|
+
getCurrentHub: getGlobalHub,
|
|
2832
|
+
withIsolationScope: withIsolationScope$1,
|
|
2833
|
+
withScope: withScope$1,
|
|
2834
|
+
withSetScope,
|
|
2835
|
+
withSetIsolationScope: (_isolationScope, callback) => {
|
|
2836
|
+
return withIsolationScope$1(callback);
|
|
2837
|
+
},
|
|
2838
|
+
getCurrentScope: () => getGlobalHub().getScope(),
|
|
2839
|
+
getIsolationScope: () => getGlobalHub().getIsolationScope(),
|
|
2840
|
+
};
|
|
2841
|
+
}
|
|
2842
|
+
/* eslint-enable deprecation/deprecation */
|
|
2843
|
+
|
|
2844
|
+
/**
|
|
2845
|
+
* Get the currently active scope.
|
|
2846
|
+
*/
|
|
2847
|
+
function getCurrentScope() {
|
|
2848
|
+
const carrier = getMainCarrier();
|
|
2849
|
+
const acs = getAsyncContextStrategy(carrier);
|
|
2850
|
+
return acs.getCurrentScope();
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
/**
|
|
2854
|
+
* Get the currently active client.
|
|
2855
|
+
*/
|
|
2856
|
+
function getClient() {
|
|
2857
|
+
return getCurrentScope().getClient();
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2860
|
+
/**
|
|
2861
|
+
* Fetches the metric summary if it exists for the passed span
|
|
2862
|
+
*/
|
|
2863
|
+
function getMetricSummaryJsonForSpan(span) {
|
|
2864
|
+
|
|
2865
|
+
{
|
|
2866
|
+
return undefined;
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
/**
|
|
2871
|
+
* Use this attribute to represent the operation of a span.
|
|
2872
|
+
*/
|
|
2873
|
+
const SEMANTIC_ATTRIBUTE_SENTRY_OP = 'sentry.op';
|
|
2874
|
+
|
|
2875
|
+
/**
|
|
2876
|
+
* Use this attribute to represent the origin of a span.
|
|
2877
|
+
*/
|
|
2878
|
+
const SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN = 'sentry.origin';
|
|
2879
|
+
|
|
2880
|
+
const SPAN_STATUS_UNSET = 0;
|
|
2881
|
+
const SPAN_STATUS_OK = 1;
|
|
2882
|
+
const TRACE_FLAG_SAMPLED = 0x1;
|
|
2883
|
+
|
|
2884
|
+
/**
|
|
2885
|
+
* Convert a span time input intp a timestamp in seconds.
|
|
2886
|
+
*/
|
|
2887
|
+
function spanTimeInputToSeconds(input) {
|
|
2888
|
+
if (typeof input === 'number') {
|
|
2889
|
+
return ensureTimestampInSeconds(input);
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
if (Array.isArray(input)) {
|
|
2893
|
+
// See {@link HrTime} for the array-based time format
|
|
2894
|
+
return input[0] + input[1] / 1e9;
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
if (input instanceof Date) {
|
|
2898
|
+
return ensureTimestampInSeconds(input.getTime());
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2901
|
+
return timestampInSeconds();
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
/**
|
|
2905
|
+
* Converts a timestamp to second, if it was in milliseconds, or keeps it as second.
|
|
2906
|
+
*/
|
|
2907
|
+
function ensureTimestampInSeconds(timestamp) {
|
|
2908
|
+
const isMs = timestamp > 9999999999;
|
|
2909
|
+
return isMs ? timestamp / 1000 : timestamp;
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
/**
|
|
2913
|
+
* Convert a span to a JSON representation.
|
|
2914
|
+
* Note that all fields returned here are optional and need to be guarded against.
|
|
2915
|
+
*
|
|
2916
|
+
* Note: Because of this, we currently have a circular type dependency (which we opted out of in package.json).
|
|
2917
|
+
* This is not avoidable as we need `spanToJSON` in `spanUtils.ts`, which in turn is needed by `span.ts` for backwards compatibility.
|
|
2918
|
+
* And `spanToJSON` needs the Span class from `span.ts` to check here.
|
|
2919
|
+
*/
|
|
2920
|
+
function spanToJSON(span) {
|
|
2921
|
+
if (spanIsSentrySpan(span)) {
|
|
2922
|
+
return span.getSpanJSON();
|
|
2923
|
+
}
|
|
2924
|
+
|
|
2925
|
+
try {
|
|
2926
|
+
const { spanId: span_id, traceId: trace_id } = span.spanContext();
|
|
2927
|
+
|
|
2928
|
+
// Handle a span from @opentelemetry/sdk-base-trace's `Span` class
|
|
2929
|
+
if (spanIsOpenTelemetrySdkTraceBaseSpan(span)) {
|
|
2930
|
+
const { attributes, startTime, name, endTime, parentSpanId, status } = span;
|
|
2931
|
+
|
|
2932
|
+
return dropUndefinedKeys({
|
|
2933
|
+
span_id,
|
|
2934
|
+
trace_id,
|
|
2935
|
+
data: attributes,
|
|
2936
|
+
description: name,
|
|
2937
|
+
parent_span_id: parentSpanId,
|
|
2938
|
+
start_timestamp: spanTimeInputToSeconds(startTime),
|
|
2939
|
+
// This is [0,0] by default in OTEL, in which case we want to interpret this as no end time
|
|
2940
|
+
timestamp: spanTimeInputToSeconds(endTime) || undefined,
|
|
2941
|
+
status: getStatusMessage(status),
|
|
2942
|
+
op: attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP],
|
|
2943
|
+
origin: attributes[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] ,
|
|
2944
|
+
_metrics_summary: getMetricSummaryJsonForSpan(span),
|
|
2945
|
+
});
|
|
2946
|
+
}
|
|
2947
|
+
|
|
2948
|
+
// Finally, at least we have `spanContext()`....
|
|
2949
|
+
return {
|
|
2950
|
+
span_id,
|
|
2951
|
+
trace_id,
|
|
2952
|
+
};
|
|
2953
|
+
} catch (e) {
|
|
2954
|
+
return {};
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
function spanIsOpenTelemetrySdkTraceBaseSpan(span) {
|
|
2959
|
+
const castSpan = span ;
|
|
2960
|
+
return !!castSpan.attributes && !!castSpan.startTime && !!castSpan.name && !!castSpan.endTime && !!castSpan.status;
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2963
|
+
/** Exported only for tests. */
|
|
2964
|
+
|
|
2965
|
+
/**
|
|
2966
|
+
* Sadly, due to circular dependency checks we cannot actually import the Span class here and check for instanceof.
|
|
2967
|
+
* :( So instead we approximate this by checking if it has the `getSpanJSON` method.
|
|
2968
|
+
*/
|
|
2969
|
+
function spanIsSentrySpan(span) {
|
|
2970
|
+
return typeof (span ).getSpanJSON === 'function';
|
|
2971
|
+
}
|
|
2972
|
+
|
|
2973
|
+
/**
|
|
2974
|
+
* Returns true if a span is sampled.
|
|
2975
|
+
* In most cases, you should just use `span.isRecording()` instead.
|
|
2976
|
+
* However, this has a slightly different semantic, as it also returns false if the span is finished.
|
|
2977
|
+
* So in the case where this distinction is important, use this method.
|
|
2978
|
+
*/
|
|
2979
|
+
function spanIsSampled(span) {
|
|
2980
|
+
// We align our trace flags with the ones OpenTelemetry use
|
|
2981
|
+
// So we also check for sampled the same way they do.
|
|
2982
|
+
const { traceFlags } = span.spanContext();
|
|
2983
|
+
return traceFlags === TRACE_FLAG_SAMPLED;
|
|
2984
|
+
}
|
|
2985
|
+
|
|
2986
|
+
/** Get the status message to use for a JSON representation of a span. */
|
|
2987
|
+
function getStatusMessage(status) {
|
|
2988
|
+
if (!status || status.code === SPAN_STATUS_UNSET) {
|
|
2989
|
+
return undefined;
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2992
|
+
if (status.code === SPAN_STATUS_OK) {
|
|
2993
|
+
return 'ok';
|
|
2994
|
+
}
|
|
2995
|
+
|
|
2996
|
+
return status.message || 'unknown_error';
|
|
2997
|
+
}
|
|
2998
|
+
const ROOT_SPAN_FIELD = '_sentryRootSpan';
|
|
2999
|
+
|
|
3000
|
+
/**
|
|
3001
|
+
* Returns the root span of a given span.
|
|
3002
|
+
*/
|
|
3003
|
+
function getRootSpan(span) {
|
|
3004
|
+
return span[ROOT_SPAN_FIELD] || span;
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
/**
|
|
3008
|
+
* Convert a new integration function to the legacy class syntax.
|
|
3009
|
+
* In v8, we can remove this and instead export the integration functions directly.
|
|
3010
|
+
*
|
|
3011
|
+
* @deprecated This will be removed in v8!
|
|
3012
|
+
*/
|
|
3013
|
+
function convertIntegrationFnToClass(
|
|
3014
|
+
name,
|
|
3015
|
+
fn,
|
|
3016
|
+
) {
|
|
3017
|
+
return Object.assign(
|
|
3018
|
+
function ConvertedIntegration(...args) {
|
|
3019
|
+
return fn(...args);
|
|
3020
|
+
},
|
|
3021
|
+
{ id: name },
|
|
3022
|
+
) ;
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
/**
|
|
3026
|
+
* Define an integration function that can be used to create an integration instance.
|
|
3027
|
+
* Note that this by design hides the implementation details of the integration, as they are considered internal.
|
|
3028
|
+
*/
|
|
3029
|
+
function defineIntegration(fn) {
|
|
3030
|
+
return fn;
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
/** Keys are source filename/url, values are metadata objects. */
|
|
3034
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3035
|
+
const filenameMetadataMap = new Map();
|
|
3036
|
+
/** Set of stack strings that have already been parsed. */
|
|
3037
|
+
const parsedStacks = new Set();
|
|
1862
3038
|
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
defaultCurrentScope = new Scope();
|
|
3039
|
+
function ensureMetadataStacksAreParsed(parser) {
|
|
3040
|
+
if (!GLOBAL_OBJ._sentryModuleMetadata) {
|
|
3041
|
+
return;
|
|
1867
3042
|
}
|
|
1868
3043
|
|
|
1869
|
-
|
|
1870
|
-
|
|
3044
|
+
for (const stack of Object.keys(GLOBAL_OBJ._sentryModuleMetadata)) {
|
|
3045
|
+
const metadata = GLOBAL_OBJ._sentryModuleMetadata[stack];
|
|
1871
3046
|
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
3047
|
+
if (parsedStacks.has(stack)) {
|
|
3048
|
+
continue;
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
// Ensure this stack doesn't get parsed again
|
|
3052
|
+
parsedStacks.add(stack);
|
|
3053
|
+
|
|
3054
|
+
const frames = parser(stack);
|
|
3055
|
+
|
|
3056
|
+
// Go through the frames starting from the top of the stack and find the first one with a filename
|
|
3057
|
+
for (const frame of frames.reverse()) {
|
|
3058
|
+
if (frame.filename) {
|
|
3059
|
+
// Save the metadata for this filename
|
|
3060
|
+
filenameMetadataMap.set(frame.filename, metadata);
|
|
3061
|
+
break;
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
1876
3064
|
}
|
|
3065
|
+
}
|
|
1877
3066
|
|
|
1878
|
-
|
|
3067
|
+
/**
|
|
3068
|
+
* Retrieve metadata for a specific JavaScript file URL.
|
|
3069
|
+
*
|
|
3070
|
+
* Metadata is injected by the Sentry bundler plugins using the `_experiments.moduleMetadata` config option.
|
|
3071
|
+
*/
|
|
3072
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3073
|
+
function getMetadataForUrl(parser, filename) {
|
|
3074
|
+
ensureMetadataStacksAreParsed(parser);
|
|
3075
|
+
return filenameMetadataMap.get(filename);
|
|
1879
3076
|
}
|
|
1880
3077
|
|
|
1881
3078
|
/**
|
|
1882
|
-
*
|
|
1883
|
-
*
|
|
3079
|
+
* Adds metadata to stack frames.
|
|
3080
|
+
*
|
|
3081
|
+
* Metadata is injected by the Sentry bundler plugins using the `_experiments.moduleMetadata` config option.
|
|
1884
3082
|
*/
|
|
1885
|
-
function
|
|
1886
|
-
|
|
1887
|
-
|
|
3083
|
+
function addMetadataToStackFrames(parser, event) {
|
|
3084
|
+
try {
|
|
3085
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
3086
|
+
event.exception.values.forEach(exception => {
|
|
3087
|
+
if (!exception.stacktrace) {
|
|
3088
|
+
return;
|
|
3089
|
+
}
|
|
1888
3090
|
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
3091
|
+
for (const frame of exception.stacktrace.frames || []) {
|
|
3092
|
+
if (!frame.filename) {
|
|
3093
|
+
continue;
|
|
3094
|
+
}
|
|
1893
3095
|
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
3096
|
+
const metadata = getMetadataForUrl(parser, frame.filename);
|
|
3097
|
+
|
|
3098
|
+
if (metadata) {
|
|
3099
|
+
frame.module_metadata = metadata;
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
});
|
|
3103
|
+
} catch (_) {
|
|
3104
|
+
// To save bundle size we're just try catching here instead of checking for the existence of all the different objects.
|
|
3105
|
+
}
|
|
1897
3106
|
}
|
|
1898
3107
|
|
|
1899
3108
|
/**
|
|
1900
|
-
*
|
|
1901
|
-
* If none has been setup, the default will be used.
|
|
3109
|
+
* Strips metadata from stack frames.
|
|
1902
3110
|
*/
|
|
1903
|
-
function
|
|
1904
|
-
|
|
3111
|
+
function stripMetadataFromStackFrames(event) {
|
|
3112
|
+
try {
|
|
3113
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
3114
|
+
event.exception.values.forEach(exception => {
|
|
3115
|
+
if (!exception.stacktrace) {
|
|
3116
|
+
return;
|
|
3117
|
+
}
|
|
1905
3118
|
|
|
1906
|
-
|
|
1907
|
-
|
|
3119
|
+
for (const frame of exception.stacktrace.frames || []) {
|
|
3120
|
+
delete frame.module_metadata;
|
|
3121
|
+
}
|
|
3122
|
+
});
|
|
3123
|
+
} catch (_) {
|
|
3124
|
+
// To save bundle size we're just try catching here instead of checking for the existence of all the different objects.
|
|
1908
3125
|
}
|
|
1909
|
-
|
|
1910
|
-
// Otherwise, use the default one
|
|
1911
|
-
return getHubStackAsyncContextStrategy();
|
|
1912
3126
|
}
|
|
1913
3127
|
|
|
1914
|
-
|
|
1915
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
1916
|
-
return getGlobalHub().withScope(callback);
|
|
1917
|
-
}
|
|
3128
|
+
const INTEGRATION_NAME$a = 'ModuleMetadata';
|
|
1918
3129
|
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
3130
|
+
const _moduleMetadataIntegration = (() => {
|
|
3131
|
+
return {
|
|
3132
|
+
name: INTEGRATION_NAME$a,
|
|
3133
|
+
setup(client) {
|
|
3134
|
+
// We need to strip metadata from stack frames before sending them to Sentry since these are client side only.
|
|
3135
|
+
client.on('beforeEnvelope', envelope => {
|
|
3136
|
+
forEachEnvelopeItem(envelope, (item, type) => {
|
|
3137
|
+
if (type === 'event') {
|
|
3138
|
+
const event = Array.isArray(item) ? (item )[1] : undefined;
|
|
3139
|
+
|
|
3140
|
+
if (event) {
|
|
3141
|
+
stripMetadataFromStackFrames(event);
|
|
3142
|
+
item[1] = event;
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
});
|
|
3146
|
+
});
|
|
3147
|
+
},
|
|
1928
3148
|
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
}
|
|
1935
|
-
}
|
|
3149
|
+
processEvent(event, _hint, client) {
|
|
3150
|
+
const stackParser = client.getOptions().stackParser;
|
|
3151
|
+
addMetadataToStackFrames(stackParser, event);
|
|
3152
|
+
return event;
|
|
3153
|
+
},
|
|
3154
|
+
};
|
|
3155
|
+
}) ;
|
|
3156
|
+
|
|
3157
|
+
const moduleMetadataIntegration = defineIntegration(_moduleMetadataIntegration);
|
|
3158
|
+
|
|
3159
|
+
/**
|
|
3160
|
+
* Adds module metadata to stack frames.
|
|
3161
|
+
*
|
|
3162
|
+
* Metadata can be injected by the Sentry bundler plugins using the `_experiments.moduleMetadata` config option.
|
|
3163
|
+
*
|
|
3164
|
+
* When this integration is added, the metadata passed to the bundler plugin is added to the stack frames of all events
|
|
3165
|
+
* under the `module_metadata` property. This can be used to help in tagging or routing of events from different teams
|
|
3166
|
+
* our sources
|
|
3167
|
+
*
|
|
3168
|
+
* @deprecated Use `moduleMetadataIntegration()` instead.
|
|
3169
|
+
*/
|
|
3170
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
3171
|
+
convertIntegrationFnToClass(
|
|
3172
|
+
INTEGRATION_NAME$a,
|
|
3173
|
+
moduleMetadataIntegration,
|
|
3174
|
+
)
|
|
3175
|
+
|
|
3176
|
+
;
|
|
3177
|
+
|
|
3178
|
+
const DEFAULT_OPTIONS = {
|
|
3179
|
+
include: {
|
|
3180
|
+
cookies: true,
|
|
3181
|
+
data: true,
|
|
3182
|
+
headers: true,
|
|
3183
|
+
ip: false,
|
|
3184
|
+
query_string: true,
|
|
3185
|
+
url: true,
|
|
3186
|
+
user: {
|
|
3187
|
+
id: true,
|
|
3188
|
+
username: true,
|
|
3189
|
+
email: true,
|
|
3190
|
+
},
|
|
3191
|
+
},
|
|
3192
|
+
transactionNamingScheme: 'methodPath' ,
|
|
3193
|
+
};
|
|
3194
|
+
|
|
3195
|
+
const INTEGRATION_NAME$9 = 'RequestData';
|
|
3196
|
+
|
|
3197
|
+
const _requestDataIntegration = ((options = {}) => {
|
|
3198
|
+
const _options = {
|
|
3199
|
+
...DEFAULT_OPTIONS,
|
|
3200
|
+
...options,
|
|
3201
|
+
include: {
|
|
3202
|
+
...DEFAULT_OPTIONS.include,
|
|
3203
|
+
...options.include,
|
|
3204
|
+
user:
|
|
3205
|
+
options.include && typeof options.include.user === 'boolean'
|
|
3206
|
+
? options.include.user
|
|
3207
|
+
: {
|
|
3208
|
+
...DEFAULT_OPTIONS.include.user,
|
|
3209
|
+
// Unclear why TS still thinks `options.include.user` could be a boolean at this point
|
|
3210
|
+
...((options.include || {}).user ),
|
|
3211
|
+
},
|
|
3212
|
+
},
|
|
3213
|
+
};
|
|
1936
3214
|
|
|
1937
|
-
/* eslint-disable deprecation/deprecation */
|
|
1938
|
-
function getHubStackAsyncContextStrategy() {
|
|
1939
3215
|
return {
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
3216
|
+
name: INTEGRATION_NAME$9,
|
|
3217
|
+
processEvent(event, _hint, client) {
|
|
3218
|
+
// Note: In the long run, most of the logic here should probably move into the request data utility functions. For
|
|
3219
|
+
// the moment it lives here, though, until https://github.com/getsentry/sentry-javascript/issues/5718 is addressed.
|
|
3220
|
+
// (TL;DR: Those functions touch many parts of the repo in many different ways, and need to be clened up. Once
|
|
3221
|
+
// that's happened, it will be easier to add this logic in without worrying about unexpected side effects.)
|
|
3222
|
+
const { transactionNamingScheme } = _options;
|
|
3223
|
+
|
|
3224
|
+
const { sdkProcessingMetadata = {} } = event;
|
|
3225
|
+
const req = sdkProcessingMetadata.request;
|
|
3226
|
+
|
|
3227
|
+
if (!req) {
|
|
3228
|
+
return event;
|
|
3229
|
+
}
|
|
3230
|
+
|
|
3231
|
+
const addRequestDataOptions = convertReqDataIntegrationOptsToAddReqDataOpts(_options);
|
|
3232
|
+
|
|
3233
|
+
const processedEvent = addRequestDataToEvent(event, req, addRequestDataOptions);
|
|
3234
|
+
|
|
3235
|
+
// Transaction events already have the right `transaction` value
|
|
3236
|
+
if (event.type === 'transaction' || transactionNamingScheme === 'handler') {
|
|
3237
|
+
return processedEvent;
|
|
3238
|
+
}
|
|
3239
|
+
|
|
3240
|
+
// In all other cases, use the request's associated transaction (if any) to overwrite the event's `transaction`
|
|
3241
|
+
// value with a high-quality one
|
|
3242
|
+
const reqWithTransaction = req ;
|
|
3243
|
+
const transaction = reqWithTransaction._sentryTransaction;
|
|
3244
|
+
if (transaction) {
|
|
3245
|
+
const name = spanToJSON(transaction).description || '';
|
|
3246
|
+
|
|
3247
|
+
// TODO (v8): Remove the nextjs check and just base it on `transactionNamingScheme` for all SDKs. (We have to
|
|
3248
|
+
// keep it the way it is for the moment, because changing the names of transactions in Sentry has the potential
|
|
3249
|
+
// to break things like alert rules.)
|
|
3250
|
+
const shouldIncludeMethodInTransactionName =
|
|
3251
|
+
getSDKName(client) === 'sentry.javascript.nextjs'
|
|
3252
|
+
? name.startsWith('/api')
|
|
3253
|
+
: transactionNamingScheme !== 'path';
|
|
3254
|
+
|
|
3255
|
+
const [transactionValue] = extractPathForTransaction(req, {
|
|
3256
|
+
path: true,
|
|
3257
|
+
method: shouldIncludeMethodInTransactionName,
|
|
3258
|
+
customRoute: name,
|
|
3259
|
+
});
|
|
3260
|
+
|
|
3261
|
+
processedEvent.transaction = transactionValue;
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3264
|
+
return processedEvent;
|
|
1946
3265
|
},
|
|
1947
|
-
getCurrentScope: () => getGlobalHub().getScope(),
|
|
1948
|
-
getIsolationScope: () => getGlobalHub().getIsolationScope(),
|
|
1949
3266
|
};
|
|
1950
|
-
}
|
|
3267
|
+
}) ;
|
|
3268
|
+
|
|
3269
|
+
const requestDataIntegration = defineIntegration(_requestDataIntegration);
|
|
1951
3270
|
|
|
1952
3271
|
/**
|
|
1953
|
-
*
|
|
1954
|
-
*
|
|
1955
|
-
*
|
|
1956
|
-
* Note: Because of this, we currently have a circular type dependency (which we opted out of in package.json).
|
|
1957
|
-
* This is not avoidable as we need `spanToJSON` in `spanUtils.ts`, which in turn is needed by `span.ts` for backwards compatibility.
|
|
1958
|
-
* And `spanToJSON` needs the Span class from `span.ts` to check here.
|
|
3272
|
+
* Add data about a request to an event. Primarily for use in Node-based SDKs, but included in `@sentry/core`
|
|
3273
|
+
* so it can be used in cross-platform SDKs like `@sentry/nextjs`.
|
|
3274
|
+
* @deprecated Use `requestDataIntegration()` instead.
|
|
1959
3275
|
*/
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
3276
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
3277
|
+
convertIntegrationFnToClass(INTEGRATION_NAME$9, requestDataIntegration)
|
|
3278
|
+
|
|
3279
|
+
;
|
|
3280
|
+
|
|
3281
|
+
/** Convert this integration's options to match what `addRequestDataToEvent` expects */
|
|
3282
|
+
/** TODO: Can possibly be deleted once https://github.com/getsentry/sentry-javascript/issues/5718 is fixed */
|
|
3283
|
+
function convertReqDataIntegrationOptsToAddReqDataOpts(
|
|
3284
|
+
integrationOptions,
|
|
3285
|
+
) {
|
|
3286
|
+
const {
|
|
3287
|
+
transactionNamingScheme,
|
|
3288
|
+
include: { ip, user, ...requestOptions },
|
|
3289
|
+
} = integrationOptions;
|
|
3290
|
+
|
|
3291
|
+
const requestIncludeKeys = ['method'];
|
|
3292
|
+
for (const [key, value] of Object.entries(requestOptions)) {
|
|
3293
|
+
if (value) {
|
|
3294
|
+
requestIncludeKeys.push(key);
|
|
3295
|
+
}
|
|
1963
3296
|
}
|
|
1964
3297
|
|
|
1965
|
-
|
|
1966
|
-
if (
|
|
1967
|
-
|
|
3298
|
+
let addReqDataUserOpt;
|
|
3299
|
+
if (user === undefined) {
|
|
3300
|
+
addReqDataUserOpt = true;
|
|
3301
|
+
} else if (typeof user === 'boolean') {
|
|
3302
|
+
addReqDataUserOpt = user;
|
|
3303
|
+
} else {
|
|
3304
|
+
const userIncludeKeys = [];
|
|
3305
|
+
for (const [key, value] of Object.entries(user)) {
|
|
3306
|
+
if (value) {
|
|
3307
|
+
userIncludeKeys.push(key);
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
addReqDataUserOpt = userIncludeKeys;
|
|
1968
3311
|
}
|
|
1969
3312
|
|
|
1970
|
-
|
|
3313
|
+
return {
|
|
3314
|
+
include: {
|
|
3315
|
+
ip,
|
|
3316
|
+
user: addReqDataUserOpt,
|
|
3317
|
+
request: requestIncludeKeys.length !== 0 ? requestIncludeKeys : undefined,
|
|
3318
|
+
transaction: transactionNamingScheme,
|
|
3319
|
+
},
|
|
3320
|
+
};
|
|
3321
|
+
}
|
|
1971
3322
|
|
|
1972
|
-
|
|
3323
|
+
function getSDKName(client) {
|
|
3324
|
+
try {
|
|
3325
|
+
// For a long chain like this, it's fewer bytes to combine a try-catch with assuming everything is there than to
|
|
3326
|
+
// write out a long chain of `a && a.b && a.b.c && ...`
|
|
3327
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
3328
|
+
return client.getOptions()._metadata.sdk.name;
|
|
3329
|
+
} catch (err) {
|
|
3330
|
+
// In theory we should never get here
|
|
3331
|
+
return undefined;
|
|
3332
|
+
}
|
|
1973
3333
|
}
|
|
1974
3334
|
|
|
3335
|
+
let originalFunctionToString;
|
|
3336
|
+
|
|
3337
|
+
const INTEGRATION_NAME$8 = 'FunctionToString';
|
|
3338
|
+
|
|
3339
|
+
const SETUP_CLIENTS = new WeakMap();
|
|
3340
|
+
|
|
3341
|
+
const _functionToStringIntegration = (() => {
|
|
3342
|
+
return {
|
|
3343
|
+
name: INTEGRATION_NAME$8,
|
|
3344
|
+
setupOnce() {
|
|
3345
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
3346
|
+
originalFunctionToString = Function.prototype.toString;
|
|
3347
|
+
|
|
3348
|
+
// intrinsics (like Function.prototype) might be immutable in some environments
|
|
3349
|
+
// e.g. Node with --frozen-intrinsics, XS (an embedded JavaScript engine) or SES (a JavaScript proposal)
|
|
3350
|
+
try {
|
|
3351
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3352
|
+
Function.prototype.toString = function ( ...args) {
|
|
3353
|
+
const originalFunction = getOriginalFunction(this);
|
|
3354
|
+
const context =
|
|
3355
|
+
SETUP_CLIENTS.has(getClient() ) && originalFunction !== undefined ? originalFunction : this;
|
|
3356
|
+
return originalFunctionToString.apply(context, args);
|
|
3357
|
+
};
|
|
3358
|
+
} catch (e) {
|
|
3359
|
+
// ignore errors here, just don't patch this
|
|
3360
|
+
}
|
|
3361
|
+
},
|
|
3362
|
+
setup(client) {
|
|
3363
|
+
SETUP_CLIENTS.set(client, true);
|
|
3364
|
+
},
|
|
3365
|
+
};
|
|
3366
|
+
}) ;
|
|
3367
|
+
|
|
1975
3368
|
/**
|
|
1976
|
-
*
|
|
1977
|
-
*
|
|
3369
|
+
* Patch toString calls to return proper name for wrapped functions.
|
|
3370
|
+
*
|
|
3371
|
+
* ```js
|
|
3372
|
+
* Sentry.init({
|
|
3373
|
+
* integrations: [
|
|
3374
|
+
* functionToStringIntegration(),
|
|
3375
|
+
* ],
|
|
3376
|
+
* });
|
|
3377
|
+
* ```
|
|
1978
3378
|
*/
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
3379
|
+
const functionToStringIntegration = defineIntegration(_functionToStringIntegration);
|
|
3380
|
+
|
|
3381
|
+
/**
|
|
3382
|
+
* Patch toString calls to return proper name for wrapped functions.
|
|
3383
|
+
*
|
|
3384
|
+
* @deprecated Use `functionToStringIntegration()` instead.
|
|
3385
|
+
*/
|
|
3386
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
3387
|
+
convertIntegrationFnToClass(
|
|
3388
|
+
INTEGRATION_NAME$8,
|
|
3389
|
+
functionToStringIntegration,
|
|
3390
|
+
) ;
|
|
1982
3391
|
|
|
1983
3392
|
/**
|
|
1984
3393
|
* This serves as a build time flag that will be true by default, but false in non-debug builds or if users replace `__SENTRY_DEBUG__` in their generated code.
|
|
@@ -2118,7 +3527,6 @@ const CpuProfilerBindings = {
|
|
|
2118
3527
|
},
|
|
2119
3528
|
};
|
|
2120
3529
|
|
|
2121
|
-
/* eslint-disable max-lines */
|
|
2122
3530
|
// We require the file because if we import it, it will be included in the bundle.
|
|
2123
3531
|
// I guess tsc does not check file contents when it's imported.
|
|
2124
3532
|
const THREAD_ID_STRING = String(worker_threads.threadId);
|
|
@@ -2164,84 +3572,6 @@ function enrichWithThreadInformation(profile) {
|
|
|
2164
3572
|
},
|
|
2165
3573
|
};
|
|
2166
3574
|
}
|
|
2167
|
-
/**
|
|
2168
|
-
* Extract sdk info from from the API metadata
|
|
2169
|
-
* @param {SdkMetadata | undefined} metadata
|
|
2170
|
-
* @returns {SdkInfo | undefined}
|
|
2171
|
-
*/
|
|
2172
|
-
function getSdkMetadataForEnvelopeHeader(metadata) {
|
|
2173
|
-
if (!metadata || !metadata.sdk) {
|
|
2174
|
-
return undefined;
|
|
2175
|
-
}
|
|
2176
|
-
return { name: metadata.sdk.name, version: metadata.sdk.version };
|
|
2177
|
-
}
|
|
2178
|
-
/**
|
|
2179
|
-
* Apply SdkInfo (name, version, packages, integrations) to the corresponding event key.
|
|
2180
|
-
* Merge with existing data if any.
|
|
2181
|
-
*
|
|
2182
|
-
* @param {Event} event
|
|
2183
|
-
* @param {SdkInfo | undefined} sdkInfo
|
|
2184
|
-
* @returns {Event}
|
|
2185
|
-
*/
|
|
2186
|
-
function enhanceEventWithSdkInfo(event, sdkInfo) {
|
|
2187
|
-
if (!sdkInfo) {
|
|
2188
|
-
return event;
|
|
2189
|
-
}
|
|
2190
|
-
event.sdk = event.sdk || {};
|
|
2191
|
-
event.sdk.name = event.sdk.name || sdkInfo.name || 'unknown sdk';
|
|
2192
|
-
event.sdk.version = event.sdk.version || sdkInfo.version || 'unknown sdk version';
|
|
2193
|
-
event.sdk.integrations = [...(event.sdk.integrations || []), ...(sdkInfo.integrations || [])];
|
|
2194
|
-
event.sdk.packages = [...(event.sdk.packages || []), ...(sdkInfo.packages || [])];
|
|
2195
|
-
return event;
|
|
2196
|
-
}
|
|
2197
|
-
/**
|
|
2198
|
-
*
|
|
2199
|
-
* @param {Event} event
|
|
2200
|
-
* @param {SdkInfo | undefined} sdkInfo
|
|
2201
|
-
* @param {string | undefined} tunnel
|
|
2202
|
-
* @param {DsnComponents} dsn
|
|
2203
|
-
* @returns {EventEnvelopeHeaders}
|
|
2204
|
-
*/
|
|
2205
|
-
function createEventEnvelopeHeaders(event, sdkInfo, tunnel, dsn) {
|
|
2206
|
-
const dynamicSamplingContext = event.sdkProcessingMetadata && event.sdkProcessingMetadata['dynamicSamplingContext'];
|
|
2207
|
-
return Object.assign(Object.assign(Object.assign({ event_id: event.event_id, sent_at: new Date().toISOString() }, (sdkInfo && { sdk: sdkInfo })), (!!tunnel && { dsn: dsnToString(dsn) })), (event.type === 'transaction' &&
|
|
2208
|
-
dynamicSamplingContext && {
|
|
2209
|
-
trace: dropUndefinedKeys(Object.assign({}, dynamicSamplingContext)),
|
|
2210
|
-
}));
|
|
2211
|
-
}
|
|
2212
|
-
/**
|
|
2213
|
-
* Creates a profiling event envelope from a Sentry event. If profile does not pass
|
|
2214
|
-
* validation, returns null.
|
|
2215
|
-
* @param {Event}
|
|
2216
|
-
* @returns {Profile | null}
|
|
2217
|
-
*/
|
|
2218
|
-
function createProfilingEventFromTransaction(event) {
|
|
2219
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
2220
|
-
if (event.type !== 'transaction') {
|
|
2221
|
-
// createProfilingEventEnvelope should only be called for transactions,
|
|
2222
|
-
// we type guard this behavior with isProfiledTransactionEvent.
|
|
2223
|
-
throw new TypeError('Profiling events may only be attached to transactions, this should never occur.');
|
|
2224
|
-
}
|
|
2225
|
-
const rawProfile = event.sdkProcessingMetadata['profile'];
|
|
2226
|
-
if (rawProfile === undefined || rawProfile === null) {
|
|
2227
|
-
throw new TypeError(`Cannot construct profiling event envelope without a valid profile. Got ${rawProfile} instead.`);
|
|
2228
|
-
}
|
|
2229
|
-
if (!rawProfile.profile_id) {
|
|
2230
|
-
throw new TypeError(`Cannot construct profiling event envelope without a valid profile id. Got ${rawProfile.profile_id} instead.`);
|
|
2231
|
-
}
|
|
2232
|
-
if (!isValidProfile(rawProfile)) {
|
|
2233
|
-
return null;
|
|
2234
|
-
}
|
|
2235
|
-
return createProfilePayload(rawProfile, {
|
|
2236
|
-
release: (_a = event.release) !== null && _a !== void 0 ? _a : '',
|
|
2237
|
-
environment: (_b = event.environment) !== null && _b !== void 0 ? _b : '',
|
|
2238
|
-
event_id: (_c = event.event_id) !== null && _c !== void 0 ? _c : '',
|
|
2239
|
-
transaction: (_d = event.transaction) !== null && _d !== void 0 ? _d : '',
|
|
2240
|
-
start_timestamp: event.start_timestamp ? event.start_timestamp * 1000 : Date.now(),
|
|
2241
|
-
trace_id: (_g = (_f = (_e = event.contexts) === null || _e === void 0 ? void 0 : _e['trace']) === null || _f === void 0 ? void 0 : _f['trace_id']) !== null && _g !== void 0 ? _g : '',
|
|
2242
|
-
profile_id: rawProfile.profile_id,
|
|
2243
|
-
});
|
|
2244
|
-
}
|
|
2245
3575
|
/**
|
|
2246
3576
|
* Creates a profiling envelope item, if the profile does not pass validation, returns null.
|
|
2247
3577
|
* @param {RawThreadCpuProfile}
|
|
@@ -2314,54 +3644,6 @@ function createProfilePayload(cpuProfile, { release, environment, event_id, tran
|
|
|
2314
3644
|
};
|
|
2315
3645
|
return profile;
|
|
2316
3646
|
}
|
|
2317
|
-
/**
|
|
2318
|
-
* Creates an envelope from a profiling event.
|
|
2319
|
-
* @param {Event} Profile
|
|
2320
|
-
* @param {DsnComponents} dsn
|
|
2321
|
-
* @param {SdkMetadata} metadata
|
|
2322
|
-
* @param {string|undefined} tunnel
|
|
2323
|
-
* @returns {Envelope|null}
|
|
2324
|
-
*/
|
|
2325
|
-
function createProfilingEventEnvelope(event, dsn, metadata, tunnel) {
|
|
2326
|
-
const sdkInfo = getSdkMetadataForEnvelopeHeader(metadata);
|
|
2327
|
-
enhanceEventWithSdkInfo(event, metadata && metadata.sdk);
|
|
2328
|
-
const envelopeHeaders = createEventEnvelopeHeaders(event, sdkInfo, tunnel, dsn);
|
|
2329
|
-
const profile = createProfilingEventFromTransaction(event);
|
|
2330
|
-
if (!profile) {
|
|
2331
|
-
return null;
|
|
2332
|
-
}
|
|
2333
|
-
const envelopeItem = [
|
|
2334
|
-
{
|
|
2335
|
-
type: 'profile',
|
|
2336
|
-
},
|
|
2337
|
-
// @ts-expect-error profile is not part of EventItem yet
|
|
2338
|
-
profile,
|
|
2339
|
-
];
|
|
2340
|
-
return createEnvelope(envelopeHeaders, [envelopeItem]);
|
|
2341
|
-
}
|
|
2342
|
-
/**
|
|
2343
|
-
* Check if event metadata contains profile information
|
|
2344
|
-
* @param {Event}
|
|
2345
|
-
* @returns {boolean}
|
|
2346
|
-
*/
|
|
2347
|
-
function isProfiledTransactionEvent(event) {
|
|
2348
|
-
return !!(event.sdkProcessingMetadata && event.sdkProcessingMetadata['profile']);
|
|
2349
|
-
}
|
|
2350
|
-
/**
|
|
2351
|
-
* Due to how profiles are attached to event metadata, we may sometimes want to remove them to ensure
|
|
2352
|
-
* they are not processed by other Sentry integrations. This can be the case when we cannot construct a valid
|
|
2353
|
-
* profile from the data we have or some of the mechanisms to send the event (Hub, Transport etc) are not available to us.
|
|
2354
|
-
*
|
|
2355
|
-
* @param {Event | ProfiledEvent} event
|
|
2356
|
-
* @returns {Event}
|
|
2357
|
-
*/
|
|
2358
|
-
function maybeRemoveProfileFromSdkMetadata(event) {
|
|
2359
|
-
if (!isProfiledTransactionEvent(event)) {
|
|
2360
|
-
return event;
|
|
2361
|
-
}
|
|
2362
|
-
delete event.sdkProcessingMetadata.profile;
|
|
2363
|
-
return event;
|
|
2364
|
-
}
|
|
2365
3647
|
/**
|
|
2366
3648
|
* Checks the given sample rate to make sure it is valid type and value (a boolean, or a number between 0 and 1).
|
|
2367
3649
|
* @param {unknown} rate
|
|
@@ -2515,12 +3797,11 @@ const MAX_PROFILE_DURATION_MS = 30 * 1000;
|
|
|
2515
3797
|
* Takes a transaction and determines if it should be profiled or not. If it should be profiled, it returns the
|
|
2516
3798
|
* profile_id, otherwise returns undefined. Takes care of setting profile context on transaction as well
|
|
2517
3799
|
*/
|
|
2518
|
-
function
|
|
3800
|
+
function maybeProfileSpan(client, span, customSamplingContext) {
|
|
2519
3801
|
// profilesSampleRate is multiplied with tracesSampleRate to get the final sampling rate. We dont perform
|
|
2520
|
-
// the actual multiplication to get the final rate, but we discard the profile if the
|
|
2521
|
-
// so anything after this block from here is based on the
|
|
2522
|
-
|
|
2523
|
-
if (!transaction.sampled) {
|
|
3802
|
+
// the actual multiplication to get the final rate, but we discard the profile if the span was sampled,
|
|
3803
|
+
// so anything after this block from here is based on the span sampling.
|
|
3804
|
+
if (!spanIsSampled(span)) {
|
|
2524
3805
|
return;
|
|
2525
3806
|
}
|
|
2526
3807
|
// Client and options are required for profiling
|
|
@@ -2537,8 +3818,13 @@ function maybeProfileTransaction(client, transaction, customSamplingContext) {
|
|
|
2537
3818
|
let profilesSampleRate = options.profilesSampleRate;
|
|
2538
3819
|
// Prefer sampler to sample rate if both are provided.
|
|
2539
3820
|
if (typeof profilesSampler === 'function') {
|
|
2540
|
-
|
|
2541
|
-
|
|
3821
|
+
const { description: spanName = '<unknown>', data } = spanToJSON(span);
|
|
3822
|
+
// We bail out early if that is not the case
|
|
3823
|
+
const parentSampled = true;
|
|
3824
|
+
profilesSampleRate = profilesSampler(Object.assign({ name: spanName, attributes: data, transactionContext: {
|
|
3825
|
+
name: spanName,
|
|
3826
|
+
parentSampled,
|
|
3827
|
+
}, parentSampled }, customSamplingContext));
|
|
2542
3828
|
}
|
|
2543
3829
|
// Since this is coming from the user (or from a function provided by the user), who knows what we might get. (The
|
|
2544
3830
|
// only valid values are booleans or numbers between 0 and 1.)
|
|
@@ -2565,7 +3851,7 @@ function maybeProfileTransaction(client, transaction, customSamplingContext) {
|
|
|
2565
3851
|
}
|
|
2566
3852
|
const profile_id = uuid4();
|
|
2567
3853
|
CpuProfilerBindings.startProfiling(profile_id);
|
|
2568
|
-
DEBUG_BUILD && logger.log(`[Profiling] started profiling transaction: ${spanToJSON(
|
|
3854
|
+
DEBUG_BUILD && logger.log(`[Profiling] started profiling transaction: ${spanToJSON(span).description}`);
|
|
2569
3855
|
// set transaction context - do this regardless if profiling fails down the line
|
|
2570
3856
|
// so that we can still see the profile_id in the transaction context
|
|
2571
3857
|
return profile_id;
|
|
@@ -2576,113 +3862,23 @@ function maybeProfileTransaction(client, transaction, customSamplingContext) {
|
|
|
2576
3862
|
* @param profile_id
|
|
2577
3863
|
* @returns
|
|
2578
3864
|
*/
|
|
2579
|
-
function
|
|
3865
|
+
function stopSpanProfile(span, profile_id) {
|
|
2580
3866
|
// Should not happen, but satisfy the type checker and be safe regardless.
|
|
2581
3867
|
if (!profile_id) {
|
|
2582
3868
|
return null;
|
|
2583
3869
|
}
|
|
2584
3870
|
const profile = CpuProfilerBindings.stopProfiling(profile_id);
|
|
2585
|
-
DEBUG_BUILD && logger.log(`[Profiling] stopped profiling of transaction: ${spanToJSON(
|
|
2586
|
-
// In case of an overlapping
|
|
3871
|
+
DEBUG_BUILD && logger.log(`[Profiling] stopped profiling of transaction: ${spanToJSON(span).description}`);
|
|
3872
|
+
// In case of an overlapping span, stopProfiling may return null and silently ignore the overlapping profile.
|
|
2587
3873
|
if (!profile) {
|
|
2588
3874
|
DEBUG_BUILD &&
|
|
2589
|
-
logger.log(`[Profiling] profiler returned null profile for: ${spanToJSON(
|
|
3875
|
+
logger.log(`[Profiling] profiler returned null profile for: ${spanToJSON(span).description}`, 'this may indicate an overlapping span or a call to stopProfiling with a profile title that was never started');
|
|
2590
3876
|
return null;
|
|
2591
3877
|
}
|
|
2592
3878
|
// Assign profile_id to the profile
|
|
2593
3879
|
profile.profile_id = profile_id;
|
|
2594
3880
|
return profile;
|
|
2595
3881
|
}
|
|
2596
|
-
/**
|
|
2597
|
-
* Wraps startTransaction and stopTransaction with profiling related logic.
|
|
2598
|
-
* startProfiling is called after the call to startTransaction in order to avoid our own code from
|
|
2599
|
-
* being profiled. Because of that same reason, stopProfiling is called before the call to stopTransaction.
|
|
2600
|
-
*/
|
|
2601
|
-
function __PRIVATE__wrapStartTransactionWithProfiling(startTransaction) {
|
|
2602
|
-
return function wrappedStartTransaction(transactionContext, customSamplingContext) {
|
|
2603
|
-
const transaction = startTransaction.call(this, transactionContext, customSamplingContext);
|
|
2604
|
-
// Client is required if we want to profile
|
|
2605
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2606
|
-
const client = this.getClient();
|
|
2607
|
-
if (!client) {
|
|
2608
|
-
return transaction;
|
|
2609
|
-
}
|
|
2610
|
-
// Check if we should profile this transaction. If a profile_id is returned, then profiling has been started.
|
|
2611
|
-
const profile_id = maybeProfileTransaction(client, transaction, customSamplingContext);
|
|
2612
|
-
if (!profile_id) {
|
|
2613
|
-
return transaction;
|
|
2614
|
-
}
|
|
2615
|
-
// A couple of important things to note here:
|
|
2616
|
-
// `CpuProfilerBindings.stopProfiling` will be scheduled to run in 30seconds in order to exceed max profile duration.
|
|
2617
|
-
// Whichever of the two (transaction.finish/timeout) is first to run, the profiling will be stopped and the gathered profile
|
|
2618
|
-
// will be processed when the original transaction is finished. Since onProfileHandler can be invoked multiple times in the
|
|
2619
|
-
// event of an error or user mistake (calling transaction.finish multiple times), it is important that the behavior of onProfileHandler
|
|
2620
|
-
// is idempotent as we do not want any timings or profiles to be overriden by the last call to onProfileHandler.
|
|
2621
|
-
// After the original finish method is called, the event will be reported through the integration and delegated to transport.
|
|
2622
|
-
let profile = null;
|
|
2623
|
-
const options = client.getOptions();
|
|
2624
|
-
// Not intended for external use, hence missing types, but we want to profile a couple of things at Sentry that
|
|
2625
|
-
// currently exceed the default timeout set by the SDKs.
|
|
2626
|
-
const maxProfileDurationMs = (options._experiments && options._experiments['maxProfileDurationMs']) || MAX_PROFILE_DURATION_MS;
|
|
2627
|
-
// Enqueue a timeout to prevent profiles from running over max duration.
|
|
2628
|
-
let maxDurationTimeoutID = global.setTimeout(() => {
|
|
2629
|
-
DEBUG_BUILD &&
|
|
2630
|
-
logger.log('[Profiling] max profile duration elapsed, stopping profiling for:', spanToJSON(transaction).description);
|
|
2631
|
-
profile = stopTransactionProfile(transaction, profile_id);
|
|
2632
|
-
}, maxProfileDurationMs);
|
|
2633
|
-
// We need to reference the original finish call to avoid creating an infinite loop
|
|
2634
|
-
const originalEnd = transaction.end.bind(transaction);
|
|
2635
|
-
// Wrap the transaction finish method to stop profiling and set the profile on the transaction.
|
|
2636
|
-
function profilingWrappedTransactionEnd() {
|
|
2637
|
-
if (!profile_id) {
|
|
2638
|
-
return originalEnd();
|
|
2639
|
-
}
|
|
2640
|
-
// We stop the handler first to ensure that the timeout is cleared and the profile is stopped.
|
|
2641
|
-
if (maxDurationTimeoutID) {
|
|
2642
|
-
global.clearTimeout(maxDurationTimeoutID);
|
|
2643
|
-
maxDurationTimeoutID = undefined;
|
|
2644
|
-
}
|
|
2645
|
-
// onProfileHandler should always return the same profile even if this is called multiple times.
|
|
2646
|
-
// Always call onProfileHandler to ensure stopProfiling is called and the timeout is cleared.
|
|
2647
|
-
if (!profile) {
|
|
2648
|
-
profile = stopTransactionProfile(transaction, profile_id);
|
|
2649
|
-
}
|
|
2650
|
-
// @ts-expect-error profile is not part of metadata
|
|
2651
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2652
|
-
transaction.setMetadata({ profile });
|
|
2653
|
-
return originalEnd();
|
|
2654
|
-
}
|
|
2655
|
-
transaction.end = profilingWrappedTransactionEnd;
|
|
2656
|
-
return transaction;
|
|
2657
|
-
};
|
|
2658
|
-
}
|
|
2659
|
-
/**
|
|
2660
|
-
* Patches startTransaction and stopTransaction with profiling logic.
|
|
2661
|
-
* This is used by the SDK's that do not support event hooks.
|
|
2662
|
-
* @private
|
|
2663
|
-
*/
|
|
2664
|
-
function _addProfilingExtensionMethods() {
|
|
2665
|
-
const carrier = getMainCarrier();
|
|
2666
|
-
if (!carrier.__SENTRY__) {
|
|
2667
|
-
DEBUG_BUILD && logger.log("[Profiling] Can't find main carrier, profiling won't work.");
|
|
2668
|
-
return;
|
|
2669
|
-
}
|
|
2670
|
-
carrier.__SENTRY__.extensions = carrier.__SENTRY__.extensions || {};
|
|
2671
|
-
if (!carrier.__SENTRY__.extensions['startTransaction']) {
|
|
2672
|
-
DEBUG_BUILD && logger.log('[Profiling] startTransaction does not exists, profiling will not work.');
|
|
2673
|
-
return;
|
|
2674
|
-
}
|
|
2675
|
-
DEBUG_BUILD && logger.log('[Profiling] startTransaction exists, patching it with profiling functionality...');
|
|
2676
|
-
carrier.__SENTRY__.extensions['startTransaction'] = __PRIVATE__wrapStartTransactionWithProfiling(
|
|
2677
|
-
// This is patched by sentry/tracing, we are going to re-patch it...
|
|
2678
|
-
carrier.__SENTRY__.extensions['startTransaction']);
|
|
2679
|
-
}
|
|
2680
|
-
/**
|
|
2681
|
-
* This patches the global object and injects the Profiling extensions methods
|
|
2682
|
-
*/
|
|
2683
|
-
function addProfilingExtensionMethods() {
|
|
2684
|
-
_addProfilingExtensionMethods();
|
|
2685
|
-
}
|
|
2686
3882
|
|
|
2687
3883
|
const MAX_PROFILE_QUEUE_LENGTH = 50;
|
|
2688
3884
|
const PROFILE_QUEUE = [];
|
|
@@ -2694,28 +3890,17 @@ function addToProfileQueue(profile) {
|
|
|
2694
3890
|
PROFILE_QUEUE.shift();
|
|
2695
3891
|
}
|
|
2696
3892
|
}
|
|
2697
|
-
/**
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
}
|
|
2709
|
-
/**
|
|
2710
|
-
* @inheritDoc
|
|
2711
|
-
*/
|
|
2712
|
-
setupOnce(addGlobalEventProcessor, getCurrentHub) {
|
|
2713
|
-
this.getCurrentHub = getCurrentHub;
|
|
2714
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2715
|
-
const client = this.getCurrentHub().getClient();
|
|
2716
|
-
if (client && typeof client.on === 'function') {
|
|
2717
|
-
client.on('startTransaction', (transaction) => {
|
|
2718
|
-
const profile_id = maybeProfileTransaction(client, transaction, undefined);
|
|
3893
|
+
/** Exported only for tests. */
|
|
3894
|
+
const _nodeProfilingIntegration = (() => {
|
|
3895
|
+
return {
|
|
3896
|
+
name: 'ProfilingIntegration',
|
|
3897
|
+
setup(client) {
|
|
3898
|
+
const spanToProfileIdMap = new WeakMap();
|
|
3899
|
+
client.on('spanStart', span => {
|
|
3900
|
+
if (span !== getRootSpan(span)) {
|
|
3901
|
+
return;
|
|
3902
|
+
}
|
|
3903
|
+
const profile_id = maybeProfileSpan(client, span, undefined);
|
|
2719
3904
|
if (profile_id) {
|
|
2720
3905
|
const options = client.getOptions();
|
|
2721
3906
|
// Not intended for external use, hence missing types, but we want to profile a couple of things at Sentry that
|
|
@@ -2729,30 +3914,25 @@ class ProfilingIntegration {
|
|
|
2729
3914
|
// Enqueue a timeout to prevent profiles from running over max duration.
|
|
2730
3915
|
PROFILE_TIMEOUTS[profile_id] = global.setTimeout(() => {
|
|
2731
3916
|
DEBUG_BUILD &&
|
|
2732
|
-
logger.log('[Profiling] max profile duration elapsed, stopping profiling for:', spanToJSON(
|
|
2733
|
-
const profile =
|
|
3917
|
+
logger.log('[Profiling] max profile duration elapsed, stopping profiling for:', spanToJSON(span).description);
|
|
3918
|
+
const profile = stopSpanProfile(span, profile_id);
|
|
2734
3919
|
if (profile) {
|
|
2735
3920
|
addToProfileQueue(profile);
|
|
2736
3921
|
}
|
|
2737
3922
|
}, maxProfileDurationMs);
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
// @ts-expect-error profile_id is not part of the metadata type
|
|
2741
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2742
|
-
transaction.setMetadata({ profile_id: profile_id });
|
|
3923
|
+
getCurrentScope().setContext('profile', { profile_id });
|
|
3924
|
+
spanToProfileIdMap.set(span, profile_id);
|
|
2743
3925
|
}
|
|
2744
3926
|
});
|
|
2745
|
-
client.on('
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
const profile_id = transaction.metadata.profile_id;
|
|
2749
|
-
if (profile_id && typeof profile_id === 'string') {
|
|
3927
|
+
client.on('spanEnd', span => {
|
|
3928
|
+
const profile_id = spanToProfileIdMap.get(span);
|
|
3929
|
+
if (profile_id) {
|
|
2750
3930
|
if (PROFILE_TIMEOUTS[profile_id]) {
|
|
2751
3931
|
global.clearTimeout(PROFILE_TIMEOUTS[profile_id]);
|
|
2752
3932
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
2753
3933
|
delete PROFILE_TIMEOUTS[profile_id];
|
|
2754
3934
|
}
|
|
2755
|
-
const profile =
|
|
3935
|
+
const profile = stopSpanProfile(span, profile_id);
|
|
2756
3936
|
if (profile) {
|
|
2757
3937
|
addToProfileQueue(profile);
|
|
2758
3938
|
}
|
|
@@ -2809,70 +3989,16 @@ class ProfilingIntegration {
|
|
|
2809
3989
|
}
|
|
2810
3990
|
addProfilesToEnvelope(envelope, profilesToAddToEnvelope);
|
|
2811
3991
|
});
|
|
2812
|
-
}
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
addProfilingExtensionMethods();
|
|
2816
|
-
addGlobalEventProcessor(this.handleGlobalEvent.bind(this));
|
|
2817
|
-
}
|
|
2818
|
-
}
|
|
2819
|
-
/**
|
|
2820
|
-
* @inheritDoc
|
|
2821
|
-
*/
|
|
2822
|
-
handleGlobalEvent(event) {
|
|
2823
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
2824
|
-
if (this.getCurrentHub === undefined) {
|
|
2825
|
-
return maybeRemoveProfileFromSdkMetadata(event);
|
|
2826
|
-
}
|
|
2827
|
-
if (isProfiledTransactionEvent(event)) {
|
|
2828
|
-
// Client, Dsn and Transport are all required to be able to send the profiling event to Sentry.
|
|
2829
|
-
// If either of them is not available, we remove the profile from the transaction event.
|
|
2830
|
-
// and forward it to the next event processor.
|
|
2831
|
-
const hub = this.getCurrentHub();
|
|
2832
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2833
|
-
const client = hub.getClient();
|
|
2834
|
-
if (!client) {
|
|
2835
|
-
DEBUG_BUILD &&
|
|
2836
|
-
logger.log('[Profiling] getClient did not return a Client, removing profile from event and forwarding to next event processors.');
|
|
2837
|
-
return maybeRemoveProfileFromSdkMetadata(event);
|
|
2838
|
-
}
|
|
2839
|
-
const dsn = client.getDsn();
|
|
2840
|
-
if (!dsn) {
|
|
2841
|
-
DEBUG_BUILD &&
|
|
2842
|
-
logger.log('[Profiling] getDsn did not return a Dsn, removing profile from event and forwarding to next event processors.');
|
|
2843
|
-
return maybeRemoveProfileFromSdkMetadata(event);
|
|
2844
|
-
}
|
|
2845
|
-
const transport = client.getTransport();
|
|
2846
|
-
if (!transport) {
|
|
2847
|
-
DEBUG_BUILD &&
|
|
2848
|
-
logger.log('[Profiling] getTransport did not return a Transport, removing profile from event and forwarding to next event processors.');
|
|
2849
|
-
return maybeRemoveProfileFromSdkMetadata(event);
|
|
2850
|
-
}
|
|
2851
|
-
// If all required components are available, we construct a profiling event envelope and send it to Sentry.
|
|
2852
|
-
DEBUG_BUILD && logger.log('[Profiling] Preparing envelope and sending a profiling event');
|
|
2853
|
-
const envelope = createProfilingEventEnvelope(event, dsn);
|
|
2854
|
-
if (envelope) {
|
|
2855
|
-
// Fire and forget, we don't want to block the main event processing flow.
|
|
2856
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
2857
|
-
transport.send(envelope);
|
|
2858
|
-
}
|
|
2859
|
-
}
|
|
2860
|
-
// Ensure sdkProcessingMetadata["profile"] is removed from the event before forwarding it to the next event processor.
|
|
2861
|
-
return maybeRemoveProfileFromSdkMetadata(event);
|
|
2862
|
-
});
|
|
2863
|
-
}
|
|
2864
|
-
}
|
|
3992
|
+
},
|
|
3993
|
+
};
|
|
3994
|
+
});
|
|
2865
3995
|
/**
|
|
2866
3996
|
* We need this integration in order to send data to Sentry. We hook into the event processor
|
|
2867
3997
|
* and inspect each event to see if it is a transaction event and if that transaction event
|
|
2868
3998
|
* contains a profile on it's metadata. If that is the case, we create a profiling event envelope
|
|
2869
3999
|
* and delete the profile from the transaction metadata.
|
|
2870
4000
|
*/
|
|
2871
|
-
const nodeProfilingIntegration = (
|
|
2872
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2873
|
-
return new ProfilingIntegration();
|
|
2874
|
-
});
|
|
4001
|
+
const nodeProfilingIntegration = defineIntegration(_nodeProfilingIntegration);
|
|
2875
4002
|
|
|
2876
|
-
exports.ProfilingIntegration = ProfilingIntegration;
|
|
2877
4003
|
exports.nodeProfilingIntegration = nodeProfilingIntegration;
|
|
2878
4004
|
//# sourceMappingURL=index.js.map
|