@sentry/profiling-node 7.107.0 → 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 +1749 -1190
- 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/cpu_profiler.d.ts.map +1 -1
- 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,13 @@ 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
|
+
|
|
111
193
|
/**
|
|
112
194
|
* Returns a global singleton contained in the global `__SENTRY__` object.
|
|
113
195
|
*
|
|
@@ -126,6 +208,128 @@ function getGlobalSingleton(name, creator, obj) {
|
|
|
126
208
|
return singleton;
|
|
127
209
|
}
|
|
128
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
|
+
|
|
129
333
|
/**
|
|
130
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.
|
|
131
335
|
*
|
|
@@ -220,21 +424,97 @@ function makeLogger() {
|
|
|
220
424
|
|
|
221
425
|
const logger = makeLogger();
|
|
222
426
|
|
|
427
|
+
const defaultFunctionName = '<anonymous>';
|
|
428
|
+
|
|
429
|
+
/**
|
|
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
|
+
|
|
223
445
|
/**
|
|
224
|
-
*
|
|
446
|
+
* This extracts the original function if available. See
|
|
447
|
+
* `markFunctionWrapped` for more information.
|
|
225
448
|
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
|
|
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.
|
|
229
459
|
*
|
|
230
|
-
* @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.
|
|
231
463
|
*/
|
|
232
|
-
function
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
)
|
|
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
|
+
}
|
|
238
518
|
}
|
|
239
519
|
|
|
240
520
|
/**
|
|
@@ -346,200 +626,693 @@ function uuid4() {
|
|
|
346
626
|
);
|
|
347
627
|
}
|
|
348
628
|
|
|
629
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
630
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
631
|
+
|
|
349
632
|
/**
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
* @param maybeArray Input to turn into an array, if necessary
|
|
353
|
-
* @returns The input, if already an array, or an array with the input as the only element, if not
|
|
633
|
+
* Helper to decycle json objects
|
|
354
634
|
*/
|
|
355
|
-
function
|
|
356
|
-
|
|
357
|
-
|
|
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
|
+
}
|
|
358
656
|
|
|
359
|
-
|
|
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
|
+
}
|
|
360
671
|
|
|
361
|
-
/**
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
+
}
|
|
370
700
|
|
|
371
701
|
/**
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
|
|
375
|
-
|
|
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;
|
|
376
718
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
+
}
|
|
382
726
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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;
|
|
388
733
|
}
|
|
389
734
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
false,
|
|
398
|
-
result => {
|
|
399
|
-
if (!onfulfilled) {
|
|
400
|
-
// TODO: ¯\_(ツ)_/¯
|
|
401
|
-
// TODO: FIXME
|
|
402
|
-
resolve(result );
|
|
403
|
-
} else {
|
|
404
|
-
try {
|
|
405
|
-
resolve(onfulfilled(result));
|
|
406
|
-
} catch (e) {
|
|
407
|
-
reject(e);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
},
|
|
411
|
-
reason => {
|
|
412
|
-
if (!onrejected) {
|
|
413
|
-
reject(reason);
|
|
414
|
-
} else {
|
|
415
|
-
try {
|
|
416
|
-
resolve(onrejected(reason));
|
|
417
|
-
} catch (e) {
|
|
418
|
-
reject(e);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
},
|
|
422
|
-
]);
|
|
423
|
-
this._executeHandlers();
|
|
424
|
-
});
|
|
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 ;
|
|
425
742
|
}
|
|
426
743
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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 ', '');
|
|
432
756
|
}
|
|
433
757
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
return
|
|
437
|
-
|
|
438
|
-
let isRejected;
|
|
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
|
+
}
|
|
439
762
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
if (onfinally) {
|
|
452
|
-
onfinally();
|
|
453
|
-
}
|
|
454
|
-
},
|
|
455
|
-
).then(() => {
|
|
456
|
-
if (isRejected) {
|
|
457
|
-
reject(val);
|
|
458
|
-
return;
|
|
459
|
-
}
|
|
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
|
+
}
|
|
460
774
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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++;
|
|
464
801
|
}
|
|
465
802
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
this._setResult(States.RESOLVED, value);
|
|
469
|
-
};}
|
|
803
|
+
// Once we've visited all the branches, remove the parent from memo storage
|
|
804
|
+
unmemoize(value);
|
|
470
805
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
};}
|
|
806
|
+
// Return accumulated values
|
|
807
|
+
return normalized;
|
|
808
|
+
}
|
|
475
809
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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]';
|
|
480
829
|
}
|
|
481
830
|
|
|
482
|
-
if (
|
|
483
|
-
|
|
484
|
-
return;
|
|
831
|
+
if (key === 'domainEmitter') {
|
|
832
|
+
return '[DomainEmitter]';
|
|
485
833
|
}
|
|
486
834
|
|
|
487
|
-
this
|
|
488
|
-
|
|
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.
|
|
489
837
|
|
|
490
|
-
|
|
491
|
-
|
|
838
|
+
if (typeof global !== 'undefined' && value === global) {
|
|
839
|
+
return '[Global]';
|
|
840
|
+
}
|
|
492
841
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
return;
|
|
842
|
+
// eslint-disable-next-line no-restricted-globals
|
|
843
|
+
if (typeof window !== 'undefined' && value === window) {
|
|
844
|
+
return '[Window]';
|
|
497
845
|
}
|
|
498
846
|
|
|
499
|
-
|
|
500
|
-
|
|
847
|
+
// eslint-disable-next-line no-restricted-globals
|
|
848
|
+
if (typeof document !== 'undefined' && value === document) {
|
|
849
|
+
return '[Document]';
|
|
850
|
+
}
|
|
501
851
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
}
|
|
852
|
+
if (isVueViewModel(value)) {
|
|
853
|
+
return '[VueViewModel]';
|
|
854
|
+
}
|
|
506
855
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
856
|
+
// React's SyntheticEvent thingy
|
|
857
|
+
if (isSyntheticEvent(value)) {
|
|
858
|
+
return '[SyntheticEvent]';
|
|
859
|
+
}
|
|
511
860
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
861
|
+
if (typeof value === 'number' && value !== value) {
|
|
862
|
+
return '[NaN]';
|
|
863
|
+
}
|
|
515
864
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
}
|
|
865
|
+
if (typeof value === 'function') {
|
|
866
|
+
return `[Function: ${getFunctionName(value)}]`;
|
|
867
|
+
}
|
|
520
868
|
|
|
521
|
-
|
|
869
|
+
if (typeof value === 'symbol') {
|
|
870
|
+
return `[${String(value)}]`;
|
|
871
|
+
}
|
|
522
872
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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
|
+
}
|
|
527
877
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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
|
+
}
|
|
535
893
|
}
|
|
894
|
+
/* eslint-enable complexity */
|
|
536
895
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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,
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
|
|
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
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
const ONE_SECOND_IN_MS = 1000;
|
|
1295
|
+
|
|
1296
|
+
/**
|
|
1297
|
+
* A partial definition of the [Performance Web API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Performance}
|
|
1298
|
+
* for accessing a high-resolution monotonic clock.
|
|
1299
|
+
*/
|
|
1300
|
+
|
|
1301
|
+
/**
|
|
1302
|
+
* Returns a timestamp in seconds since the UNIX epoch using the Date API.
|
|
1303
|
+
*
|
|
1304
|
+
* TODO(v8): Return type should be rounded.
|
|
1305
|
+
*/
|
|
1306
|
+
function dateTimestampInSeconds() {
|
|
1307
|
+
return Date.now() / ONE_SECOND_IN_MS;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
/**
|
|
1311
|
+
* Returns a wrapper around the native Performance API browser implementation, or undefined for browsers that do not
|
|
1312
|
+
* support the API.
|
|
1313
|
+
*
|
|
1314
|
+
* Wrapping the native API works around differences in behavior from different browsers.
|
|
1315
|
+
*/
|
|
543
1316
|
function createUnixTimestampInSecondsFunc() {
|
|
544
1317
|
const { performance } = GLOBAL_OBJ ;
|
|
545
1318
|
if (!performance || !performance.now) {
|
|
@@ -623,15 +1396,6 @@ const timestampInSeconds = createUnixTimestampInSecondsFunc();
|
|
|
623
1396
|
return dateNow;
|
|
624
1397
|
})();
|
|
625
1398
|
|
|
626
|
-
/**
|
|
627
|
-
* Creates an envelope.
|
|
628
|
-
* Make sure to always explicitly provide the generic to this function
|
|
629
|
-
* so that the envelope types resolve correctly.
|
|
630
|
-
*/
|
|
631
|
-
function createEnvelope(headers, items = []) {
|
|
632
|
-
return [headers, items] ;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
1399
|
/**
|
|
636
1400
|
* Convenience function to loop through the items and item types of an envelope.
|
|
637
1401
|
* (This function was mostly created because working with envelope types is painful at the moment)
|
|
@@ -664,53 +1428,36 @@ function forEachEnvelopeItem(
|
|
|
664
1428
|
const DEBUG_BUILD$1 = (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__);
|
|
665
1429
|
|
|
666
1430
|
/**
|
|
667
|
-
*
|
|
1431
|
+
* @private Private API with no semver guarantees!
|
|
668
1432
|
*
|
|
669
|
-
*
|
|
1433
|
+
* Strategy used to track async context.
|
|
670
1434
|
*/
|
|
671
|
-
const DEBUG_BUILD = (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__);
|
|
672
|
-
|
|
673
|
-
const DEFAULT_ENVIRONMENT = 'production';
|
|
674
1435
|
|
|
675
1436
|
/**
|
|
676
|
-
* Returns the global
|
|
677
|
-
*
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
1437
|
+
* Returns the global shim registry.
|
|
1438
|
+
*
|
|
1439
|
+
* FIXME: This function is problematic, because despite always returning a valid Carrier,
|
|
1440
|
+
* it has an optional `__SENTRY__` property, which then in turn requires us to always perform an unnecessary check
|
|
1441
|
+
* at the call-site. We always access the carrier through this function, so we can guarantee that `__SENTRY__` is there.
|
|
1442
|
+
**/
|
|
1443
|
+
function getMainCarrier() {
|
|
1444
|
+
// This ensures a Sentry carrier exists
|
|
1445
|
+
getSentryCarrier(GLOBAL_OBJ);
|
|
1446
|
+
return GLOBAL_OBJ;
|
|
681
1447
|
}
|
|
682
1448
|
|
|
683
|
-
/**
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
) {
|
|
692
|
-
return new SyncPromise((resolve, reject) => {
|
|
693
|
-
const processor = processors[index];
|
|
694
|
-
if (event === null || typeof processor !== 'function') {
|
|
695
|
-
resolve(event);
|
|
696
|
-
} else {
|
|
697
|
-
const result = processor({ ...event }, hint) ;
|
|
698
|
-
|
|
699
|
-
DEBUG_BUILD && processor.id && result === null && logger.log(`Event processor "${processor.id}" dropped event`);
|
|
700
|
-
|
|
701
|
-
if (isThenable(result)) {
|
|
702
|
-
void result
|
|
703
|
-
.then(final => notifyEventProcessors(processors, final, hint, index + 1).then(resolve))
|
|
704
|
-
.then(null, reject);
|
|
705
|
-
} else {
|
|
706
|
-
void notifyEventProcessors(processors, result, hint, index + 1)
|
|
707
|
-
.then(resolve)
|
|
708
|
-
.then(null, reject);
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
});
|
|
1449
|
+
/** Will either get the existing sentry carrier, or create a new one. */
|
|
1450
|
+
function getSentryCarrier(carrier) {
|
|
1451
|
+
if (!carrier.__SENTRY__) {
|
|
1452
|
+
carrier.__SENTRY__ = {
|
|
1453
|
+
extensions: {},
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
return carrier.__SENTRY__;
|
|
712
1457
|
}
|
|
713
1458
|
|
|
1459
|
+
const DEFAULT_ENVIRONMENT = 'production';
|
|
1460
|
+
|
|
714
1461
|
/**
|
|
715
1462
|
* Creates a new `Session` object by setting certain default parameters. If optional @param context
|
|
716
1463
|
* is passed, the passed properties are applied to the session object.
|
|
@@ -867,320 +1614,28 @@ function sessionToJSON(session) {
|
|
|
867
1614
|
});
|
|
868
1615
|
}
|
|
869
1616
|
|
|
870
|
-
const TRACE_FLAG_SAMPLED = 0x1;
|
|
871
|
-
|
|
872
1617
|
/**
|
|
873
|
-
*
|
|
1618
|
+
* Default value for maximum number of breadcrumbs added to an event.
|
|
874
1619
|
*/
|
|
875
|
-
|
|
876
|
-
const { spanId: span_id, traceId: trace_id } = span.spanContext();
|
|
877
|
-
const { data, op, parent_span_id, status, tags, origin } = spanToJSON(span);
|
|
878
|
-
|
|
879
|
-
return dropUndefinedKeys({
|
|
880
|
-
data,
|
|
881
|
-
op,
|
|
882
|
-
parent_span_id,
|
|
883
|
-
span_id,
|
|
884
|
-
status,
|
|
885
|
-
tags,
|
|
886
|
-
trace_id,
|
|
887
|
-
origin,
|
|
888
|
-
});
|
|
889
|
-
}
|
|
1620
|
+
const DEFAULT_MAX_BREADCRUMBS = 100;
|
|
890
1621
|
|
|
891
1622
|
/**
|
|
892
|
-
*
|
|
893
|
-
* Note that all fields returned here are optional and need to be guarded against.
|
|
894
|
-
*
|
|
895
|
-
* Note: Because of this, we currently have a circular type dependency (which we opted out of in package.json).
|
|
896
|
-
* This is not avoidable as we need `spanToJSON` in `spanUtils.ts`, which in turn is needed by `span.ts` for backwards compatibility.
|
|
897
|
-
* And `spanToJSON` needs the Span class from `span.ts` to check here.
|
|
898
|
-
* TODO v8: When we remove the deprecated stuff from `span.ts`, we can remove the circular dependency again.
|
|
1623
|
+
* Holds additional event information.
|
|
899
1624
|
*/
|
|
900
|
-
|
|
901
|
-
if
|
|
902
|
-
return span.getSpanJSON();
|
|
903
|
-
}
|
|
1625
|
+
class Scope {
|
|
1626
|
+
/** Flag if notifying is happening. */
|
|
904
1627
|
|
|
905
|
-
|
|
906
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
907
|
-
if (typeof span.toJSON === 'function') {
|
|
908
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
909
|
-
return span.toJSON();
|
|
910
|
-
}
|
|
1628
|
+
/** Callback for client to receive scope changes. */
|
|
911
1629
|
|
|
912
|
-
|
|
913
|
-
}
|
|
1630
|
+
/** Callback list that will be called during event processing. */
|
|
914
1631
|
|
|
915
|
-
/**
|
|
916
|
-
* Sadly, due to circular dependency checks we cannot actually import the Span class here and check for instanceof.
|
|
917
|
-
* :( So instead we approximate this by checking if it has the `getSpanJSON` method.
|
|
918
|
-
*/
|
|
919
|
-
function spanIsSpanClass(span) {
|
|
920
|
-
return typeof (span ).getSpanJSON === 'function';
|
|
921
|
-
}
|
|
1632
|
+
/** Array of breadcrumbs. */
|
|
922
1633
|
|
|
923
|
-
/**
|
|
924
|
-
* Returns true if a span is sampled.
|
|
925
|
-
* In most cases, you should just use `span.isRecording()` instead.
|
|
926
|
-
* However, this has a slightly different semantic, as it also returns false if the span is finished.
|
|
927
|
-
* So in the case where this distinction is important, use this method.
|
|
928
|
-
*/
|
|
929
|
-
function spanIsSampled(span) {
|
|
930
|
-
// We align our trace flags with the ones OpenTelemetry use
|
|
931
|
-
// So we also check for sampled the same way they do.
|
|
932
|
-
const { traceFlags } = span.spanContext();
|
|
933
|
-
// eslint-disable-next-line no-bitwise
|
|
934
|
-
return Boolean(traceFlags & TRACE_FLAG_SAMPLED);
|
|
935
|
-
}
|
|
1634
|
+
/** User */
|
|
936
1635
|
|
|
937
|
-
/**
|
|
938
|
-
* Get the currently active client.
|
|
939
|
-
*/
|
|
940
|
-
function getClient() {
|
|
941
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
942
|
-
return getCurrentHub().getClient();
|
|
943
|
-
}
|
|
1636
|
+
/** Tags */
|
|
944
1637
|
|
|
945
|
-
/**
|
|
946
|
-
* Get the currently active scope.
|
|
947
|
-
*/
|
|
948
|
-
function getCurrentScope() {
|
|
949
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
950
|
-
return getCurrentHub().getScope();
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
/**
|
|
954
|
-
* Returns the root span of a given span.
|
|
955
|
-
*
|
|
956
|
-
* As long as we use `Transaction`s internally, the returned root span
|
|
957
|
-
* will be a `Transaction` but be aware that this might change in the future.
|
|
958
|
-
*
|
|
959
|
-
* If the given span has no root span or transaction, `undefined` is returned.
|
|
960
|
-
*/
|
|
961
|
-
function getRootSpan(span) {
|
|
962
|
-
// TODO (v8): Remove this check and just return span
|
|
963
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
964
|
-
return span.transaction;
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
/**
|
|
968
|
-
* Creates a dynamic sampling context from a client.
|
|
969
|
-
*
|
|
970
|
-
* Dispatches the `createDsc` lifecycle hook as a side effect.
|
|
971
|
-
*/
|
|
972
|
-
function getDynamicSamplingContextFromClient(
|
|
973
|
-
trace_id,
|
|
974
|
-
client,
|
|
975
|
-
scope,
|
|
976
|
-
) {
|
|
977
|
-
const options = client.getOptions();
|
|
978
|
-
|
|
979
|
-
const { publicKey: public_key } = client.getDsn() || {};
|
|
980
|
-
// TODO(v8): Remove segment from User
|
|
981
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
982
|
-
const { segment: user_segment } = (scope && scope.getUser()) || {};
|
|
983
|
-
|
|
984
|
-
const dsc = dropUndefinedKeys({
|
|
985
|
-
environment: options.environment || DEFAULT_ENVIRONMENT,
|
|
986
|
-
release: options.release,
|
|
987
|
-
user_segment,
|
|
988
|
-
public_key,
|
|
989
|
-
trace_id,
|
|
990
|
-
}) ;
|
|
991
|
-
|
|
992
|
-
client.emit && client.emit('createDsc', dsc);
|
|
993
|
-
|
|
994
|
-
return dsc;
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
/**
|
|
998
|
-
* A Span with a frozen dynamic sampling context.
|
|
999
|
-
*/
|
|
1000
|
-
|
|
1001
|
-
/**
|
|
1002
|
-
* Creates a dynamic sampling context from a span (and client and scope)
|
|
1003
|
-
*
|
|
1004
|
-
* @param span the span from which a few values like the root span name and sample rate are extracted.
|
|
1005
|
-
*
|
|
1006
|
-
* @returns a dynamic sampling context
|
|
1007
|
-
*/
|
|
1008
|
-
function getDynamicSamplingContextFromSpan(span) {
|
|
1009
|
-
const client = getClient();
|
|
1010
|
-
if (!client) {
|
|
1011
|
-
return {};
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
// passing emit=false here to only emit later once the DSC is actually populated
|
|
1015
|
-
const dsc = getDynamicSamplingContextFromClient(spanToJSON(span).trace_id || '', client, getCurrentScope());
|
|
1016
|
-
|
|
1017
|
-
// TODO (v8): Remove v7FrozenDsc as a Transaction will no longer have _frozenDynamicSamplingContext
|
|
1018
|
-
const txn = getRootSpan(span) ;
|
|
1019
|
-
if (!txn) {
|
|
1020
|
-
return dsc;
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
// TODO (v8): Remove v7FrozenDsc as a Transaction will no longer have _frozenDynamicSamplingContext
|
|
1024
|
-
// For now we need to avoid breaking users who directly created a txn with a DSC, where this field is still set.
|
|
1025
|
-
// @see Transaction class constructor
|
|
1026
|
-
const v7FrozenDsc = txn && txn._frozenDynamicSamplingContext;
|
|
1027
|
-
if (v7FrozenDsc) {
|
|
1028
|
-
return v7FrozenDsc;
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
// TODO (v8): Replace txn.metadata with txn.attributes[]
|
|
1032
|
-
// We can't do this yet because attributes aren't always set yet.
|
|
1033
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
1034
|
-
const { sampleRate: maybeSampleRate, source } = txn.metadata;
|
|
1035
|
-
if (maybeSampleRate != null) {
|
|
1036
|
-
dsc.sample_rate = `${maybeSampleRate}`;
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
// We don't want to have a transaction name in the DSC if the source is "url" because URLs might contain PII
|
|
1040
|
-
const jsonSpan = spanToJSON(txn);
|
|
1041
|
-
|
|
1042
|
-
// after JSON conversion, txn.name becomes jsonSpan.description
|
|
1043
|
-
if (source && source !== 'url') {
|
|
1044
|
-
dsc.transaction = jsonSpan.description;
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
dsc.sampled = String(spanIsSampled(txn));
|
|
1048
|
-
|
|
1049
|
-
client.emit && client.emit('createDsc', dsc);
|
|
1050
|
-
|
|
1051
|
-
return dsc;
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
/**
|
|
1055
|
-
* Applies data from the scope to the event and runs all event processors on it.
|
|
1056
|
-
*/
|
|
1057
|
-
function applyScopeDataToEvent(event, data) {
|
|
1058
|
-
const { fingerprint, span, breadcrumbs, sdkProcessingMetadata } = data;
|
|
1059
|
-
|
|
1060
|
-
// Apply general data
|
|
1061
|
-
applyDataToEvent(event, data);
|
|
1062
|
-
|
|
1063
|
-
// We want to set the trace context for normal events only if there isn't already
|
|
1064
|
-
// a trace context on the event. There is a product feature in place where we link
|
|
1065
|
-
// errors with transaction and it relies on that.
|
|
1066
|
-
if (span) {
|
|
1067
|
-
applySpanToEvent(event, span);
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
applyFingerprintToEvent(event, fingerprint);
|
|
1071
|
-
applyBreadcrumbsToEvent(event, breadcrumbs);
|
|
1072
|
-
applySdkMetadataToEvent(event, sdkProcessingMetadata);
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
function applyDataToEvent(event, data) {
|
|
1076
|
-
const {
|
|
1077
|
-
extra,
|
|
1078
|
-
tags,
|
|
1079
|
-
user,
|
|
1080
|
-
contexts,
|
|
1081
|
-
level,
|
|
1082
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
1083
|
-
transactionName,
|
|
1084
|
-
} = data;
|
|
1085
|
-
|
|
1086
|
-
const cleanedExtra = dropUndefinedKeys(extra);
|
|
1087
|
-
if (cleanedExtra && Object.keys(cleanedExtra).length) {
|
|
1088
|
-
event.extra = { ...cleanedExtra, ...event.extra };
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
const cleanedTags = dropUndefinedKeys(tags);
|
|
1092
|
-
if (cleanedTags && Object.keys(cleanedTags).length) {
|
|
1093
|
-
event.tags = { ...cleanedTags, ...event.tags };
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
const cleanedUser = dropUndefinedKeys(user);
|
|
1097
|
-
if (cleanedUser && Object.keys(cleanedUser).length) {
|
|
1098
|
-
event.user = { ...cleanedUser, ...event.user };
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
const cleanedContexts = dropUndefinedKeys(contexts);
|
|
1102
|
-
if (cleanedContexts && Object.keys(cleanedContexts).length) {
|
|
1103
|
-
event.contexts = { ...cleanedContexts, ...event.contexts };
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
if (level) {
|
|
1107
|
-
event.level = level;
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
if (transactionName) {
|
|
1111
|
-
event.transaction = transactionName;
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
function applyBreadcrumbsToEvent(event, breadcrumbs) {
|
|
1116
|
-
const mergedBreadcrumbs = [...(event.breadcrumbs || []), ...breadcrumbs];
|
|
1117
|
-
event.breadcrumbs = mergedBreadcrumbs.length ? mergedBreadcrumbs : undefined;
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
function applySdkMetadataToEvent(event, sdkProcessingMetadata) {
|
|
1121
|
-
event.sdkProcessingMetadata = {
|
|
1122
|
-
...event.sdkProcessingMetadata,
|
|
1123
|
-
...sdkProcessingMetadata,
|
|
1124
|
-
};
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
function applySpanToEvent(event, span) {
|
|
1128
|
-
event.contexts = { trace: spanToTraceContext(span), ...event.contexts };
|
|
1129
|
-
const rootSpan = getRootSpan(span);
|
|
1130
|
-
if (rootSpan) {
|
|
1131
|
-
event.sdkProcessingMetadata = {
|
|
1132
|
-
dynamicSamplingContext: getDynamicSamplingContextFromSpan(span),
|
|
1133
|
-
...event.sdkProcessingMetadata,
|
|
1134
|
-
};
|
|
1135
|
-
const transactionName = spanToJSON(rootSpan).description;
|
|
1136
|
-
if (transactionName) {
|
|
1137
|
-
event.tags = { transaction: transactionName, ...event.tags };
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
/**
|
|
1143
|
-
* Applies fingerprint from the scope to the event if there's one,
|
|
1144
|
-
* uses message if there's one instead or get rid of empty fingerprint
|
|
1145
|
-
*/
|
|
1146
|
-
function applyFingerprintToEvent(event, fingerprint) {
|
|
1147
|
-
// Make sure it's an array first and we actually have something in place
|
|
1148
|
-
event.fingerprint = event.fingerprint ? arrayify(event.fingerprint) : [];
|
|
1149
|
-
|
|
1150
|
-
// If we have something on the scope, then merge it with event
|
|
1151
|
-
if (fingerprint) {
|
|
1152
|
-
event.fingerprint = event.fingerprint.concat(fingerprint);
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
// If we have no data at all, remove empty array default
|
|
1156
|
-
if (event.fingerprint && !event.fingerprint.length) {
|
|
1157
|
-
delete event.fingerprint;
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
/**
|
|
1162
|
-
* Default value for maximum number of breadcrumbs added to an event.
|
|
1163
|
-
*/
|
|
1164
|
-
const DEFAULT_MAX_BREADCRUMBS = 100;
|
|
1165
|
-
|
|
1166
|
-
/**
|
|
1167
|
-
* Holds additional event information. {@link Scope.applyToEvent} will be
|
|
1168
|
-
* called by the client before an event will be sent.
|
|
1169
|
-
*/
|
|
1170
|
-
class Scope {
|
|
1171
|
-
/** Flag if notifying is happening. */
|
|
1172
|
-
|
|
1173
|
-
/** Callback for client to receive scope changes. */
|
|
1174
|
-
|
|
1175
|
-
/** Callback list that will be called after {@link applyToEvent}. */
|
|
1176
|
-
|
|
1177
|
-
/** Array of breadcrumbs. */
|
|
1178
|
-
|
|
1179
|
-
/** User */
|
|
1180
|
-
|
|
1181
|
-
/** Tags */
|
|
1182
|
-
|
|
1183
|
-
/** Extra */
|
|
1638
|
+
/** Extra */
|
|
1184
1639
|
|
|
1185
1640
|
/** Contexts */
|
|
1186
1641
|
|
|
@@ -1196,10 +1651,12 @@ class Scope {
|
|
|
1196
1651
|
/** Fingerprint */
|
|
1197
1652
|
|
|
1198
1653
|
/** Severity */
|
|
1199
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
1200
1654
|
|
|
1201
1655
|
/**
|
|
1202
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.
|
|
1203
1660
|
*/
|
|
1204
1661
|
|
|
1205
1662
|
/** Span */
|
|
@@ -1235,7 +1692,7 @@ class Scope {
|
|
|
1235
1692
|
}
|
|
1236
1693
|
|
|
1237
1694
|
/**
|
|
1238
|
-
*
|
|
1695
|
+
* @inheritDoc
|
|
1239
1696
|
*/
|
|
1240
1697
|
clone() {
|
|
1241
1698
|
const newScope = new Scope();
|
|
@@ -1259,23 +1716,22 @@ class Scope {
|
|
|
1259
1716
|
return newScope;
|
|
1260
1717
|
}
|
|
1261
1718
|
|
|
1262
|
-
/**
|
|
1719
|
+
/**
|
|
1720
|
+
* @inheritDoc
|
|
1721
|
+
*/
|
|
1263
1722
|
setClient(client) {
|
|
1264
1723
|
this._client = client;
|
|
1265
1724
|
}
|
|
1266
1725
|
|
|
1267
1726
|
/**
|
|
1268
|
-
*
|
|
1269
|
-
*
|
|
1270
|
-
* It is generally recommended to use the global function `Sentry.getClient()` instead, unless you know what you are doing.
|
|
1727
|
+
* @inheritDoc
|
|
1271
1728
|
*/
|
|
1272
1729
|
getClient() {
|
|
1273
|
-
return this._client;
|
|
1730
|
+
return this._client ;
|
|
1274
1731
|
}
|
|
1275
1732
|
|
|
1276
1733
|
/**
|
|
1277
|
-
*
|
|
1278
|
-
* @hidden
|
|
1734
|
+
* @inheritDoc
|
|
1279
1735
|
*/
|
|
1280
1736
|
addScopeListener(callback) {
|
|
1281
1737
|
this._scopeListeners.push(callback);
|
|
@@ -1299,7 +1755,6 @@ class Scope {
|
|
|
1299
1755
|
email: undefined,
|
|
1300
1756
|
id: undefined,
|
|
1301
1757
|
ip_address: undefined,
|
|
1302
|
-
segment: undefined,
|
|
1303
1758
|
username: undefined,
|
|
1304
1759
|
};
|
|
1305
1760
|
|
|
@@ -1387,17 +1842,14 @@ class Scope {
|
|
|
1387
1842
|
/**
|
|
1388
1843
|
* @inheritDoc
|
|
1389
1844
|
*/
|
|
1390
|
-
setLevel(
|
|
1391
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
1392
|
-
level,
|
|
1393
|
-
) {
|
|
1845
|
+
setLevel(level) {
|
|
1394
1846
|
this._level = level;
|
|
1395
1847
|
this._notifyScopeListeners();
|
|
1396
1848
|
return this;
|
|
1397
1849
|
}
|
|
1398
1850
|
|
|
1399
1851
|
/**
|
|
1400
|
-
*
|
|
1852
|
+
* @inheritDoc
|
|
1401
1853
|
*/
|
|
1402
1854
|
setTransactionName(name) {
|
|
1403
1855
|
this._transactionName = name;
|
|
@@ -1447,10 +1899,15 @@ class Scope {
|
|
|
1447
1899
|
// Often, this span (if it exists at all) will be a transaction, but it's not guaranteed to be. Regardless, it will
|
|
1448
1900
|
// have a pointer to the currently-active transaction.
|
|
1449
1901
|
const span = this._span;
|
|
1902
|
+
|
|
1450
1903
|
// Cannot replace with getRootSpan because getRootSpan returns a span, not a transaction
|
|
1451
1904
|
// Also, this method will be removed anyway.
|
|
1452
1905
|
// eslint-disable-next-line deprecation/deprecation
|
|
1453
|
-
|
|
1906
|
+
if (span && (span ).transaction) {
|
|
1907
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
1908
|
+
return (span ).transaction;
|
|
1909
|
+
}
|
|
1910
|
+
return undefined;
|
|
1454
1911
|
}
|
|
1455
1912
|
|
|
1456
1913
|
/**
|
|
@@ -1533,6 +1990,7 @@ class Scope {
|
|
|
1533
1990
|
* @inheritDoc
|
|
1534
1991
|
*/
|
|
1535
1992
|
clear() {
|
|
1993
|
+
// client is not cleared here on purpose!
|
|
1536
1994
|
this._breadcrumbs = [];
|
|
1537
1995
|
this._tags = {};
|
|
1538
1996
|
this._extra = {};
|
|
@@ -1653,33 +2111,7 @@ class Scope {
|
|
|
1653
2111
|
}
|
|
1654
2112
|
|
|
1655
2113
|
/**
|
|
1656
|
-
*
|
|
1657
|
-
*
|
|
1658
|
-
* @param event Event
|
|
1659
|
-
* @param hint Object containing additional information about the original exception, for use by the event processors.
|
|
1660
|
-
* @hidden
|
|
1661
|
-
* @deprecated Use `applyScopeDataToEvent()` directly
|
|
1662
|
-
*/
|
|
1663
|
-
applyToEvent(
|
|
1664
|
-
event,
|
|
1665
|
-
hint = {},
|
|
1666
|
-
additionalEventProcessors = [],
|
|
1667
|
-
) {
|
|
1668
|
-
applyScopeDataToEvent(event, this.getScopeData());
|
|
1669
|
-
|
|
1670
|
-
// TODO (v8): Update this order to be: Global > Client > Scope
|
|
1671
|
-
const eventProcessors = [
|
|
1672
|
-
...additionalEventProcessors,
|
|
1673
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
1674
|
-
...getGlobalEventProcessors(),
|
|
1675
|
-
...this._eventProcessors,
|
|
1676
|
-
];
|
|
1677
|
-
|
|
1678
|
-
return notifyEventProcessors(eventProcessors, event, hint);
|
|
1679
|
-
}
|
|
1680
|
-
|
|
1681
|
-
/**
|
|
1682
|
-
* Add data which will be accessible during event processing but won't get sent to Sentry
|
|
2114
|
+
* @inheritDoc
|
|
1683
2115
|
*/
|
|
1684
2116
|
setSDKProcessingMetadata(newData) {
|
|
1685
2117
|
this._sdkProcessingMetadata = { ...this._sdkProcessingMetadata, ...newData };
|
|
@@ -1703,11 +2135,7 @@ class Scope {
|
|
|
1703
2135
|
}
|
|
1704
2136
|
|
|
1705
2137
|
/**
|
|
1706
|
-
*
|
|
1707
|
-
*
|
|
1708
|
-
* @param exception The exception to capture.
|
|
1709
|
-
* @param hint Optinal additional data to attach to the Sentry event.
|
|
1710
|
-
* @returns the id of the captured Sentry event.
|
|
2138
|
+
* @inheritDoc
|
|
1711
2139
|
*/
|
|
1712
2140
|
captureException(exception, hint) {
|
|
1713
2141
|
const eventId = hint && hint.event_id ? hint.event_id : uuid4();
|
|
@@ -1734,12 +2162,7 @@ class Scope {
|
|
|
1734
2162
|
}
|
|
1735
2163
|
|
|
1736
2164
|
/**
|
|
1737
|
-
*
|
|
1738
|
-
*
|
|
1739
|
-
* @param message The message to capture.
|
|
1740
|
-
* @param level An optional severity level to report the message with.
|
|
1741
|
-
* @param hint Optional additional data to attach to the Sentry event.
|
|
1742
|
-
* @returns the id of the captured message.
|
|
2165
|
+
* @inheritDoc
|
|
1743
2166
|
*/
|
|
1744
2167
|
captureMessage(message, level, hint) {
|
|
1745
2168
|
const eventId = hint && hint.event_id ? hint.event_id : uuid4();
|
|
@@ -1767,11 +2190,7 @@ class Scope {
|
|
|
1767
2190
|
}
|
|
1768
2191
|
|
|
1769
2192
|
/**
|
|
1770
|
-
*
|
|
1771
|
-
*
|
|
1772
|
-
* @param exception The event to capture.
|
|
1773
|
-
* @param hint Optional additional data to attach to the Sentry event.
|
|
1774
|
-
* @returns the id of the captured event.
|
|
2193
|
+
* @inheritDoc
|
|
1775
2194
|
*/
|
|
1776
2195
|
captureEvent(event, hint) {
|
|
1777
2196
|
const eventId = hint && hint.event_id ? hint.event_id : uuid4();
|
|
@@ -1810,7 +2229,7 @@ function generatePropagationContext() {
|
|
|
1810
2229
|
};
|
|
1811
2230
|
}
|
|
1812
2231
|
|
|
1813
|
-
const SDK_VERSION = '
|
|
2232
|
+
const SDK_VERSION = '8.0.0-alpha.3';
|
|
1814
2233
|
|
|
1815
2234
|
/**
|
|
1816
2235
|
* API compatibility version of this hub.
|
|
@@ -1826,7 +2245,12 @@ const API_VERSION = parseFloat(SDK_VERSION);
|
|
|
1826
2245
|
* Default maximum number of breadcrumbs added to an event. Can be overwritten
|
|
1827
2246
|
* with {@link Options.maxBreadcrumbs}.
|
|
1828
2247
|
*/
|
|
1829
|
-
const DEFAULT_BREADCRUMBS = 100;
|
|
2248
|
+
const DEFAULT_BREADCRUMBS$1 = 100;
|
|
2249
|
+
|
|
2250
|
+
/**
|
|
2251
|
+
* A layer in the process stack.
|
|
2252
|
+
* @hidden
|
|
2253
|
+
*/
|
|
1830
2254
|
|
|
1831
2255
|
/**
|
|
1832
2256
|
* @inheritDoc
|
|
@@ -1834,8 +2258,6 @@ const DEFAULT_BREADCRUMBS = 100;
|
|
|
1834
2258
|
class Hub {
|
|
1835
2259
|
/** Is a {@link Layer}[] containing the client and scope */
|
|
1836
2260
|
|
|
1837
|
-
/** Contains the last event id of a captured event. */
|
|
1838
|
-
|
|
1839
2261
|
/**
|
|
1840
2262
|
* Creates a new instance of the hub, will push one {@link Layer} into the
|
|
1841
2263
|
* internal stack on creation.
|
|
@@ -2064,7 +2486,7 @@ class Hub {
|
|
|
2064
2486
|
* @deprecated Use `Sentry.captureException()` instead.
|
|
2065
2487
|
*/
|
|
2066
2488
|
captureException(exception, hint) {
|
|
2067
|
-
const eventId =
|
|
2489
|
+
const eventId = hint && hint.event_id ? hint.event_id : uuid4();
|
|
2068
2490
|
const syntheticException = new Error('Sentry syntheticException');
|
|
2069
2491
|
// eslint-disable-next-line deprecation/deprecation
|
|
2070
2492
|
this.getScope().captureException(exception, {
|
|
@@ -2082,13 +2504,8 @@ class Hub {
|
|
|
2082
2504
|
*
|
|
2083
2505
|
* @deprecated Use `Sentry.captureMessage()` instead.
|
|
2084
2506
|
*/
|
|
2085
|
-
captureMessage(
|
|
2086
|
-
|
|
2087
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2088
|
-
level,
|
|
2089
|
-
hint,
|
|
2090
|
-
) {
|
|
2091
|
-
const eventId = (this._lastEventId = hint && hint.event_id ? hint.event_id : uuid4());
|
|
2507
|
+
captureMessage(message, level, hint) {
|
|
2508
|
+
const eventId = hint && hint.event_id ? hint.event_id : uuid4();
|
|
2092
2509
|
const syntheticException = new Error(message);
|
|
2093
2510
|
// eslint-disable-next-line deprecation/deprecation
|
|
2094
2511
|
this.getScope().captureMessage(message, level, {
|
|
@@ -2108,23 +2525,11 @@ class Hub {
|
|
|
2108
2525
|
*/
|
|
2109
2526
|
captureEvent(event, hint) {
|
|
2110
2527
|
const eventId = hint && hint.event_id ? hint.event_id : uuid4();
|
|
2111
|
-
if (!event.type) {
|
|
2112
|
-
this._lastEventId = eventId;
|
|
2113
|
-
}
|
|
2114
2528
|
// eslint-disable-next-line deprecation/deprecation
|
|
2115
2529
|
this.getScope().captureEvent(event, { ...hint, event_id: eventId });
|
|
2116
2530
|
return eventId;
|
|
2117
2531
|
}
|
|
2118
2532
|
|
|
2119
|
-
/**
|
|
2120
|
-
* @inheritDoc
|
|
2121
|
-
*
|
|
2122
|
-
* @deprecated This will be removed in v8.
|
|
2123
|
-
*/
|
|
2124
|
-
lastEventId() {
|
|
2125
|
-
return this._lastEventId;
|
|
2126
|
-
}
|
|
2127
|
-
|
|
2128
2533
|
/**
|
|
2129
2534
|
* @inheritDoc
|
|
2130
2535
|
*
|
|
@@ -2132,11 +2537,11 @@ class Hub {
|
|
|
2132
2537
|
*/
|
|
2133
2538
|
addBreadcrumb(breadcrumb, hint) {
|
|
2134
2539
|
// eslint-disable-next-line deprecation/deprecation
|
|
2135
|
-
const {
|
|
2540
|
+
const { client } = this.getStackTop();
|
|
2136
2541
|
|
|
2137
2542
|
if (!client) return;
|
|
2138
2543
|
|
|
2139
|
-
const { beforeBreadcrumb = null, maxBreadcrumbs = DEFAULT_BREADCRUMBS } =
|
|
2544
|
+
const { beforeBreadcrumb = null, maxBreadcrumbs = DEFAULT_BREADCRUMBS$1 } =
|
|
2140
2545
|
(client.getOptions && client.getOptions()) || {};
|
|
2141
2546
|
|
|
2142
2547
|
if (maxBreadcrumbs <= 0) return;
|
|
@@ -2149,19 +2554,10 @@ class Hub {
|
|
|
2149
2554
|
|
|
2150
2555
|
if (finalBreadcrumb === null) return;
|
|
2151
2556
|
|
|
2152
|
-
|
|
2153
|
-
client.emit('beforeAddBreadcrumb', finalBreadcrumb, hint);
|
|
2154
|
-
}
|
|
2155
|
-
|
|
2156
|
-
// TODO(v8): I know this comment doesn't make much sense because the hub will be deprecated but I still wanted to
|
|
2157
|
-
// write it down. In theory, we would have to add the breadcrumbs to the isolation scope here, however, that would
|
|
2158
|
-
// duplicate all of the breadcrumbs. There was the possibility of adding breadcrumbs to both, the isolation scope
|
|
2159
|
-
// and the normal scope, and deduplicating it down the line in the event processing pipeline. However, that would
|
|
2160
|
-
// have been very fragile, because the breadcrumb objects would have needed to keep their identity all throughout
|
|
2161
|
-
// the event processing pipeline.
|
|
2162
|
-
// In the new implementation, the top level `Sentry.addBreadcrumb()` should ONLY write to the isolation scope.
|
|
2557
|
+
client.emit('beforeAddBreadcrumb', finalBreadcrumb, hint);
|
|
2163
2558
|
|
|
2164
|
-
|
|
2559
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
2560
|
+
this.getIsolationScope().addBreadcrumb(finalBreadcrumb, maxBreadcrumbs);
|
|
2165
2561
|
}
|
|
2166
2562
|
|
|
2167
2563
|
/**
|
|
@@ -2169,9 +2565,6 @@ class Hub {
|
|
|
2169
2565
|
* @deprecated Use `Sentry.setUser()` instead.
|
|
2170
2566
|
*/
|
|
2171
2567
|
setUser(user) {
|
|
2172
|
-
// TODO(v8): The top level `Sentry.setUser()` function should write ONLY to the isolation scope.
|
|
2173
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2174
|
-
this.getScope().setUser(user);
|
|
2175
2568
|
// eslint-disable-next-line deprecation/deprecation
|
|
2176
2569
|
this.getIsolationScope().setUser(user);
|
|
2177
2570
|
}
|
|
@@ -2181,9 +2574,6 @@ class Hub {
|
|
|
2181
2574
|
* @deprecated Use `Sentry.setTags()` instead.
|
|
2182
2575
|
*/
|
|
2183
2576
|
setTags(tags) {
|
|
2184
|
-
// TODO(v8): The top level `Sentry.setTags()` function should write ONLY to the isolation scope.
|
|
2185
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2186
|
-
this.getScope().setTags(tags);
|
|
2187
2577
|
// eslint-disable-next-line deprecation/deprecation
|
|
2188
2578
|
this.getIsolationScope().setTags(tags);
|
|
2189
2579
|
}
|
|
@@ -2193,9 +2583,6 @@ class Hub {
|
|
|
2193
2583
|
* @deprecated Use `Sentry.setExtras()` instead.
|
|
2194
2584
|
*/
|
|
2195
2585
|
setExtras(extras) {
|
|
2196
|
-
// TODO(v8): The top level `Sentry.setExtras()` function should write ONLY to the isolation scope.
|
|
2197
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2198
|
-
this.getScope().setExtras(extras);
|
|
2199
2586
|
// eslint-disable-next-line deprecation/deprecation
|
|
2200
2587
|
this.getIsolationScope().setExtras(extras);
|
|
2201
2588
|
}
|
|
@@ -2205,9 +2592,6 @@ class Hub {
|
|
|
2205
2592
|
* @deprecated Use `Sentry.setTag()` instead.
|
|
2206
2593
|
*/
|
|
2207
2594
|
setTag(key, value) {
|
|
2208
|
-
// TODO(v8): The top level `Sentry.setTag()` function should write ONLY to the isolation scope.
|
|
2209
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2210
|
-
this.getScope().setTag(key, value);
|
|
2211
2595
|
// eslint-disable-next-line deprecation/deprecation
|
|
2212
2596
|
this.getIsolationScope().setTag(key, value);
|
|
2213
2597
|
}
|
|
@@ -2217,9 +2601,6 @@ class Hub {
|
|
|
2217
2601
|
* @deprecated Use `Sentry.setExtra()` instead.
|
|
2218
2602
|
*/
|
|
2219
2603
|
setExtra(key, extra) {
|
|
2220
|
-
// TODO(v8): The top level `Sentry.setExtra()` function should write ONLY to the isolation scope.
|
|
2221
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2222
|
-
this.getScope().setExtra(key, extra);
|
|
2223
2604
|
// eslint-disable-next-line deprecation/deprecation
|
|
2224
2605
|
this.getIsolationScope().setExtra(key, extra);
|
|
2225
2606
|
}
|
|
@@ -2230,40 +2611,10 @@ class Hub {
|
|
|
2230
2611
|
*/
|
|
2231
2612
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2232
2613
|
setContext(name, context) {
|
|
2233
|
-
// TODO(v8): The top level `Sentry.setContext()` function should write ONLY to the isolation scope.
|
|
2234
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2235
|
-
this.getScope().setContext(name, context);
|
|
2236
2614
|
// eslint-disable-next-line deprecation/deprecation
|
|
2237
2615
|
this.getIsolationScope().setContext(name, context);
|
|
2238
2616
|
}
|
|
2239
2617
|
|
|
2240
|
-
/**
|
|
2241
|
-
* @inheritDoc
|
|
2242
|
-
*
|
|
2243
|
-
* @deprecated Use `getScope()` directly.
|
|
2244
|
-
*/
|
|
2245
|
-
configureScope(callback) {
|
|
2246
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2247
|
-
const { scope, client } = this.getStackTop();
|
|
2248
|
-
if (client) {
|
|
2249
|
-
callback(scope);
|
|
2250
|
-
}
|
|
2251
|
-
}
|
|
2252
|
-
|
|
2253
|
-
/**
|
|
2254
|
-
* @inheritDoc
|
|
2255
|
-
*/
|
|
2256
|
-
run(callback) {
|
|
2257
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2258
|
-
const oldHub = makeMain(this);
|
|
2259
|
-
try {
|
|
2260
|
-
callback(this);
|
|
2261
|
-
} finally {
|
|
2262
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2263
|
-
makeMain(oldHub);
|
|
2264
|
-
}
|
|
2265
|
-
}
|
|
2266
|
-
|
|
2267
2618
|
/**
|
|
2268
2619
|
* @inheritDoc
|
|
2269
2620
|
* @deprecated Use `Sentry.getClient().getIntegrationByName()` instead.
|
|
@@ -2276,59 +2627,11 @@ class Hub {
|
|
|
2276
2627
|
// eslint-disable-next-line deprecation/deprecation
|
|
2277
2628
|
return client.getIntegration(integration);
|
|
2278
2629
|
} catch (_oO) {
|
|
2279
|
-
DEBUG_BUILD && logger.warn(`Cannot retrieve integration ${integration.id} from the current Hub`);
|
|
2630
|
+
DEBUG_BUILD$1 && logger.warn(`Cannot retrieve integration ${integration.id} from the current Hub`);
|
|
2280
2631
|
return null;
|
|
2281
2632
|
}
|
|
2282
2633
|
}
|
|
2283
2634
|
|
|
2284
|
-
/**
|
|
2285
|
-
* Starts a new `Transaction` and returns it. This is the entry point to manual tracing instrumentation.
|
|
2286
|
-
*
|
|
2287
|
-
* A tree structure can be built by adding child spans to the transaction, and child spans to other spans. To start a
|
|
2288
|
-
* new child span within the transaction or any span, call the respective `.startChild()` method.
|
|
2289
|
-
*
|
|
2290
|
-
* Every child span must be finished before the transaction is finished, otherwise the unfinished spans are discarded.
|
|
2291
|
-
*
|
|
2292
|
-
* The transaction must be finished with a call to its `.end()` method, at which point the transaction with all its
|
|
2293
|
-
* finished child spans will be sent to Sentry.
|
|
2294
|
-
*
|
|
2295
|
-
* @param context Properties of the new `Transaction`.
|
|
2296
|
-
* @param customSamplingContext Information given to the transaction sampling function (along with context-dependent
|
|
2297
|
-
* default values). See {@link Options.tracesSampler}.
|
|
2298
|
-
*
|
|
2299
|
-
* @returns The transaction which was just started
|
|
2300
|
-
*
|
|
2301
|
-
* @deprecated Use `startSpan()`, `startSpanManual()` or `startInactiveSpan()` instead.
|
|
2302
|
-
*/
|
|
2303
|
-
startTransaction(context, customSamplingContext) {
|
|
2304
|
-
const result = this._callExtensionMethod('startTransaction', context, customSamplingContext);
|
|
2305
|
-
|
|
2306
|
-
if (DEBUG_BUILD && !result) {
|
|
2307
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2308
|
-
const client = this.getClient();
|
|
2309
|
-
if (!client) {
|
|
2310
|
-
logger.warn(
|
|
2311
|
-
"Tracing extension 'startTransaction' is missing. You should 'init' the SDK before calling 'startTransaction'",
|
|
2312
|
-
);
|
|
2313
|
-
} else {
|
|
2314
|
-
logger.warn(`Tracing extension 'startTransaction' has not been added. Call 'addTracingExtensions' before calling 'init':
|
|
2315
|
-
Sentry.addTracingExtensions();
|
|
2316
|
-
Sentry.init({...});
|
|
2317
|
-
`);
|
|
2318
|
-
}
|
|
2319
|
-
}
|
|
2320
|
-
|
|
2321
|
-
return result;
|
|
2322
|
-
}
|
|
2323
|
-
|
|
2324
|
-
/**
|
|
2325
|
-
* @inheritDoc
|
|
2326
|
-
* @deprecated Use `spanToTraceHeader()` instead.
|
|
2327
|
-
*/
|
|
2328
|
-
traceHeaders() {
|
|
2329
|
-
return this._callExtensionMethod('traceHeaders');
|
|
2330
|
-
}
|
|
2331
|
-
|
|
2332
2635
|
/**
|
|
2333
2636
|
* @inheritDoc
|
|
2334
2637
|
*
|
|
@@ -2394,152 +2697,704 @@ Sentry.init({...});
|
|
|
2394
2697
|
// Afterwards we set the new session on the scope
|
|
2395
2698
|
scope.setSession(session);
|
|
2396
2699
|
|
|
2397
|
-
return session;
|
|
2398
|
-
}
|
|
2700
|
+
return session;
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
/**
|
|
2704
|
+
* Returns if default PII should be sent to Sentry and propagated in ourgoing requests
|
|
2705
|
+
* when Tracing is used.
|
|
2706
|
+
*
|
|
2707
|
+
* @deprecated Use top-level `getClient().getOptions().sendDefaultPii` instead. This function
|
|
2708
|
+
* only unnecessarily increased API surface but only wrapped accessing the option.
|
|
2709
|
+
*/
|
|
2710
|
+
shouldSendDefaultPii() {
|
|
2711
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
2712
|
+
const client = this.getClient();
|
|
2713
|
+
const options = client && client.getOptions();
|
|
2714
|
+
return Boolean(options && options.sendDefaultPii);
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
/**
|
|
2718
|
+
* Sends the current Session on the scope
|
|
2719
|
+
*/
|
|
2720
|
+
_sendSessionUpdate() {
|
|
2721
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
2722
|
+
const { scope, client } = this.getStackTop();
|
|
2723
|
+
|
|
2724
|
+
const session = scope.getSession();
|
|
2725
|
+
if (session && client && client.captureSession) {
|
|
2726
|
+
client.captureSession(session);
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
/**
|
|
2731
|
+
* Calls global extension method and binding current instance to the function call
|
|
2732
|
+
*/
|
|
2733
|
+
// @ts-expect-error Function lacks ending return statement and return type does not include 'undefined'. ts(2366)
|
|
2734
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2735
|
+
_callExtensionMethod(method, ...args) {
|
|
2736
|
+
const carrier = getMainCarrier();
|
|
2737
|
+
const sentry = getSentryCarrier(carrier);
|
|
2738
|
+
if (sentry.extensions && typeof sentry.extensions[method] === 'function') {
|
|
2739
|
+
return sentry.extensions[method].apply(this, args);
|
|
2740
|
+
}
|
|
2741
|
+
DEBUG_BUILD$1 && logger.warn(`Extension method ${method} couldn't be found, doing nothing.`);
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2745
|
+
/**
|
|
2746
|
+
* Returns the default hub instance.
|
|
2747
|
+
*
|
|
2748
|
+
* If a hub is already registered in the global carrier but this module
|
|
2749
|
+
* contains a more recent version, it replaces the registered version.
|
|
2750
|
+
* Otherwise, the currently registered hub will be returned.
|
|
2751
|
+
*
|
|
2752
|
+
* @deprecated Use the respective replacement method directly instead.
|
|
2753
|
+
*/
|
|
2754
|
+
function getCurrentHub() {
|
|
2755
|
+
// Get main carrier (global for every environment)
|
|
2756
|
+
const carrier = getMainCarrier();
|
|
2757
|
+
|
|
2758
|
+
const acs = getAsyncContextStrategy(carrier);
|
|
2759
|
+
return acs.getCurrentHub() || getGlobalHub();
|
|
2760
|
+
}
|
|
2761
|
+
|
|
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();
|
|
2399
3038
|
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
*
|
|
2404
|
-
* @deprecated Use top-level `getClient().getOptions().sendDefaultPii` instead. This function
|
|
2405
|
-
* only unnecessarily increased API surface but only wrapped accessing the option.
|
|
2406
|
-
*/
|
|
2407
|
-
shouldSendDefaultPii() {
|
|
2408
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2409
|
-
const client = this.getClient();
|
|
2410
|
-
const options = client && client.getOptions();
|
|
2411
|
-
return Boolean(options && options.sendDefaultPii);
|
|
3039
|
+
function ensureMetadataStacksAreParsed(parser) {
|
|
3040
|
+
if (!GLOBAL_OBJ._sentryModuleMetadata) {
|
|
3041
|
+
return;
|
|
2412
3042
|
}
|
|
2413
3043
|
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
*/
|
|
2417
|
-
_sendSessionUpdate() {
|
|
2418
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2419
|
-
const { scope, client } = this.getStackTop();
|
|
3044
|
+
for (const stack of Object.keys(GLOBAL_OBJ._sentryModuleMetadata)) {
|
|
3045
|
+
const metadata = GLOBAL_OBJ._sentryModuleMetadata[stack];
|
|
2420
3046
|
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
client.captureSession(session);
|
|
3047
|
+
if (parsedStacks.has(stack)) {
|
|
3048
|
+
continue;
|
|
2424
3049
|
}
|
|
2425
|
-
}
|
|
2426
3050
|
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
const
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
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
|
+
}
|
|
2437
3063
|
}
|
|
2438
|
-
DEBUG_BUILD && logger.warn(`Extension method ${method} couldn't be found, doing nothing.`);
|
|
2439
3064
|
}
|
|
2440
3065
|
}
|
|
2441
3066
|
|
|
2442
3067
|
/**
|
|
2443
|
-
*
|
|
3068
|
+
* Retrieve metadata for a specific JavaScript file URL.
|
|
2444
3069
|
*
|
|
2445
|
-
*
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
extensions: {},
|
|
2452
|
-
hub: undefined,
|
|
2453
|
-
};
|
|
2454
|
-
return GLOBAL_OBJ;
|
|
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);
|
|
2455
3076
|
}
|
|
2456
3077
|
|
|
2457
3078
|
/**
|
|
2458
|
-
*
|
|
3079
|
+
* Adds metadata to stack frames.
|
|
2459
3080
|
*
|
|
2460
|
-
*
|
|
2461
|
-
*
|
|
2462
|
-
* @deprecated Use `setCurrentClient()` instead.
|
|
3081
|
+
* Metadata is injected by the Sentry bundler plugins using the `_experiments.moduleMetadata` config option.
|
|
2463
3082
|
*/
|
|
2464
|
-
function
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
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
|
+
}
|
|
3090
|
+
|
|
3091
|
+
for (const frame of exception.stacktrace.frames || []) {
|
|
3092
|
+
if (!frame.filename) {
|
|
3093
|
+
continue;
|
|
3094
|
+
}
|
|
3095
|
+
|
|
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
|
+
}
|
|
2469
3106
|
}
|
|
2470
3107
|
|
|
2471
3108
|
/**
|
|
2472
|
-
*
|
|
3109
|
+
* Strips metadata from stack frames.
|
|
3110
|
+
*/
|
|
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
|
+
}
|
|
3118
|
+
|
|
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.
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
|
|
3128
|
+
const INTEGRATION_NAME$a = 'ModuleMetadata';
|
|
3129
|
+
|
|
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
|
+
},
|
|
3148
|
+
|
|
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.
|
|
2473
3161
|
*
|
|
2474
|
-
*
|
|
2475
|
-
* contains a more recent version, it replaces the registered version.
|
|
2476
|
-
* Otherwise, the currently registered hub will be returned.
|
|
3162
|
+
* Metadata can be injected by the Sentry bundler plugins using the `_experiments.moduleMetadata` config option.
|
|
2477
3163
|
*
|
|
2478
|
-
*
|
|
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
|
+
};
|
|
3214
|
+
|
|
3215
|
+
return {
|
|
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;
|
|
3265
|
+
},
|
|
3266
|
+
};
|
|
3267
|
+
}) ;
|
|
3268
|
+
|
|
3269
|
+
const requestDataIntegration = defineIntegration(_requestDataIntegration);
|
|
3270
|
+
|
|
3271
|
+
/**
|
|
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.
|
|
2479
3275
|
*/
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
3276
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
3277
|
+
convertIntegrationFnToClass(INTEGRATION_NAME$9, requestDataIntegration)
|
|
3278
|
+
|
|
3279
|
+
;
|
|
2483
3280
|
|
|
2484
|
-
|
|
2485
|
-
|
|
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;
|
|
2486
3290
|
|
|
2487
|
-
|
|
2488
|
-
|
|
3291
|
+
const requestIncludeKeys = ['method'];
|
|
3292
|
+
for (const [key, value] of Object.entries(requestOptions)) {
|
|
3293
|
+
if (value) {
|
|
3294
|
+
requestIncludeKeys.push(key);
|
|
2489
3295
|
}
|
|
2490
3296
|
}
|
|
2491
3297
|
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
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;
|
|
3311
|
+
}
|
|
2495
3312
|
|
|
2496
|
-
|
|
2497
|
-
|
|
3313
|
+
return {
|
|
3314
|
+
include: {
|
|
3315
|
+
ip,
|
|
3316
|
+
user: addReqDataUserOpt,
|
|
3317
|
+
request: requestIncludeKeys.length !== 0 ? requestIncludeKeys : undefined,
|
|
3318
|
+
transaction: transactionNamingScheme,
|
|
3319
|
+
},
|
|
3320
|
+
};
|
|
3321
|
+
}
|
|
2498
3322
|
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
//
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
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;
|
|
2506
3332
|
}
|
|
2507
|
-
|
|
2508
|
-
// Return hub that lives on a global object
|
|
2509
|
-
return getHubFromCarrier(registry);
|
|
2510
3333
|
}
|
|
2511
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
|
+
|
|
2512
3368
|
/**
|
|
2513
|
-
*
|
|
2514
|
-
*
|
|
3369
|
+
* Patch toString calls to return proper name for wrapped functions.
|
|
3370
|
+
*
|
|
3371
|
+
* ```js
|
|
3372
|
+
* Sentry.init({
|
|
3373
|
+
* integrations: [
|
|
3374
|
+
* functionToStringIntegration(),
|
|
3375
|
+
* ],
|
|
3376
|
+
* });
|
|
3377
|
+
* ```
|
|
2515
3378
|
*/
|
|
2516
|
-
|
|
2517
|
-
return !!(carrier && carrier.__SENTRY__ && carrier.__SENTRY__.hub);
|
|
2518
|
-
}
|
|
3379
|
+
const functionToStringIntegration = defineIntegration(_functionToStringIntegration);
|
|
2519
3380
|
|
|
2520
3381
|
/**
|
|
2521
|
-
*
|
|
2522
|
-
*
|
|
2523
|
-
* @
|
|
2524
|
-
* @hidden
|
|
3382
|
+
* Patch toString calls to return proper name for wrapped functions.
|
|
3383
|
+
*
|
|
3384
|
+
* @deprecated Use `functionToStringIntegration()` instead.
|
|
2525
3385
|
*/
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
3386
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
3387
|
+
convertIntegrationFnToClass(
|
|
3388
|
+
INTEGRATION_NAME$8,
|
|
3389
|
+
functionToStringIntegration,
|
|
3390
|
+
) ;
|
|
2530
3391
|
|
|
2531
3392
|
/**
|
|
2532
|
-
* This will
|
|
2533
|
-
*
|
|
2534
|
-
*
|
|
2535
|
-
* @returns A boolean indicating success or failure
|
|
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.
|
|
3394
|
+
*
|
|
3395
|
+
* ATTENTION: This constant must never cross package boundaries (i.e. be exported) to guarantee that it can be used for tree shaking.
|
|
2536
3396
|
*/
|
|
2537
|
-
|
|
2538
|
-
if (!carrier) return false;
|
|
2539
|
-
const __SENTRY__ = (carrier.__SENTRY__ = carrier.__SENTRY__ || {});
|
|
2540
|
-
__SENTRY__.hub = hub;
|
|
2541
|
-
return true;
|
|
2542
|
-
}
|
|
3397
|
+
const DEBUG_BUILD = (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__);
|
|
2543
3398
|
|
|
2544
3399
|
const stdlib = detectLibc.familySync();
|
|
2545
3400
|
const platform = process.env['BUILD_PLATFORM'] || os.platform();
|
|
@@ -2562,7 +3417,6 @@ function importCppBindingsModule() {
|
|
|
2562
3417
|
const binaryPath = path.join(path.resolve(process$1.env['SENTRY_PROFILER_BINARY_DIR']), `sentry_cpu_profiler-${identifier}`);
|
|
2563
3418
|
return require(`${binaryPath}.node`);
|
|
2564
3419
|
}
|
|
2565
|
-
/* eslint-disable no-fallthrough */
|
|
2566
3420
|
// We need the fallthrough so that in the end, we can fallback to the require dynamice require.
|
|
2567
3421
|
// This is for cases where precompiled binaries were not provided, but may have been compiled from source.
|
|
2568
3422
|
if (platform === 'darwin') {
|
|
@@ -2658,14 +3512,14 @@ const PrivateCpuProfilerBindings = importCppBindingsModule();
|
|
|
2658
3512
|
const CpuProfilerBindings = {
|
|
2659
3513
|
startProfiling(name) {
|
|
2660
3514
|
if (!PrivateCpuProfilerBindings) {
|
|
2661
|
-
DEBUG_BUILD
|
|
3515
|
+
DEBUG_BUILD && logger.log('[Profiling] Bindings not loaded, ignoring call to startProfiling.');
|
|
2662
3516
|
return;
|
|
2663
3517
|
}
|
|
2664
3518
|
return PrivateCpuProfilerBindings.startProfiling(name);
|
|
2665
3519
|
},
|
|
2666
3520
|
stopProfiling(name) {
|
|
2667
3521
|
if (!PrivateCpuProfilerBindings) {
|
|
2668
|
-
DEBUG_BUILD
|
|
3522
|
+
DEBUG_BUILD &&
|
|
2669
3523
|
logger.log('[Profiling] Bindings not loaded or profile was never started, ignoring call to stopProfiling.');
|
|
2670
3524
|
return null;
|
|
2671
3525
|
}
|
|
@@ -2673,10 +3527,8 @@ const CpuProfilerBindings = {
|
|
|
2673
3527
|
},
|
|
2674
3528
|
};
|
|
2675
3529
|
|
|
2676
|
-
/* eslint-disable max-lines */
|
|
2677
3530
|
// We require the file because if we import it, it will be included in the bundle.
|
|
2678
3531
|
// I guess tsc does not check file contents when it's imported.
|
|
2679
|
-
// eslint-disable-next-line
|
|
2680
3532
|
const THREAD_ID_STRING = String(worker_threads.threadId);
|
|
2681
3533
|
const THREAD_NAME = worker_threads.isMainThread ? 'main' : 'worker';
|
|
2682
3534
|
const FORMAT_VERSION = '1';
|
|
@@ -2720,84 +3572,6 @@ function enrichWithThreadInformation(profile) {
|
|
|
2720
3572
|
},
|
|
2721
3573
|
};
|
|
2722
3574
|
}
|
|
2723
|
-
/**
|
|
2724
|
-
* Extract sdk info from from the API metadata
|
|
2725
|
-
* @param {SdkMetadata | undefined} metadata
|
|
2726
|
-
* @returns {SdkInfo | undefined}
|
|
2727
|
-
*/
|
|
2728
|
-
function getSdkMetadataForEnvelopeHeader(metadata) {
|
|
2729
|
-
if (!metadata || !metadata.sdk) {
|
|
2730
|
-
return undefined;
|
|
2731
|
-
}
|
|
2732
|
-
return { name: metadata.sdk.name, version: metadata.sdk.version };
|
|
2733
|
-
}
|
|
2734
|
-
/**
|
|
2735
|
-
* Apply SdkInfo (name, version, packages, integrations) to the corresponding event key.
|
|
2736
|
-
* Merge with existing data if any.
|
|
2737
|
-
*
|
|
2738
|
-
* @param {Event} event
|
|
2739
|
-
* @param {SdkInfo | undefined} sdkInfo
|
|
2740
|
-
* @returns {Event}
|
|
2741
|
-
*/
|
|
2742
|
-
function enhanceEventWithSdkInfo(event, sdkInfo) {
|
|
2743
|
-
if (!sdkInfo) {
|
|
2744
|
-
return event;
|
|
2745
|
-
}
|
|
2746
|
-
event.sdk = event.sdk || {};
|
|
2747
|
-
event.sdk.name = event.sdk.name || sdkInfo.name || 'unknown sdk';
|
|
2748
|
-
event.sdk.version = event.sdk.version || sdkInfo.version || 'unknown sdk version';
|
|
2749
|
-
event.sdk.integrations = [...(event.sdk.integrations || []), ...(sdkInfo.integrations || [])];
|
|
2750
|
-
event.sdk.packages = [...(event.sdk.packages || []), ...(sdkInfo.packages || [])];
|
|
2751
|
-
return event;
|
|
2752
|
-
}
|
|
2753
|
-
/**
|
|
2754
|
-
*
|
|
2755
|
-
* @param {Event} event
|
|
2756
|
-
* @param {SdkInfo | undefined} sdkInfo
|
|
2757
|
-
* @param {string | undefined} tunnel
|
|
2758
|
-
* @param {DsnComponents} dsn
|
|
2759
|
-
* @returns {EventEnvelopeHeaders}
|
|
2760
|
-
*/
|
|
2761
|
-
function createEventEnvelopeHeaders(event, sdkInfo, tunnel, dsn) {
|
|
2762
|
-
const dynamicSamplingContext = event.sdkProcessingMetadata && event.sdkProcessingMetadata['dynamicSamplingContext'];
|
|
2763
|
-
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' &&
|
|
2764
|
-
dynamicSamplingContext && {
|
|
2765
|
-
trace: dropUndefinedKeys(Object.assign({}, dynamicSamplingContext)),
|
|
2766
|
-
}));
|
|
2767
|
-
}
|
|
2768
|
-
/**
|
|
2769
|
-
* Creates a profiling event envelope from a Sentry event. If profile does not pass
|
|
2770
|
-
* validation, returns null.
|
|
2771
|
-
* @param {Event}
|
|
2772
|
-
* @returns {Profile | null}
|
|
2773
|
-
*/
|
|
2774
|
-
function createProfilingEventFromTransaction(event) {
|
|
2775
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
2776
|
-
if (event.type !== 'transaction') {
|
|
2777
|
-
// createProfilingEventEnvelope should only be called for transactions,
|
|
2778
|
-
// we type guard this behavior with isProfiledTransactionEvent.
|
|
2779
|
-
throw new TypeError('Profiling events may only be attached to transactions, this should never occur.');
|
|
2780
|
-
}
|
|
2781
|
-
const rawProfile = event.sdkProcessingMetadata['profile'];
|
|
2782
|
-
if (rawProfile === undefined || rawProfile === null) {
|
|
2783
|
-
throw new TypeError(`Cannot construct profiling event envelope without a valid profile. Got ${rawProfile} instead.`);
|
|
2784
|
-
}
|
|
2785
|
-
if (!rawProfile.profile_id) {
|
|
2786
|
-
throw new TypeError(`Cannot construct profiling event envelope without a valid profile id. Got ${rawProfile.profile_id} instead.`);
|
|
2787
|
-
}
|
|
2788
|
-
if (!isValidProfile(rawProfile)) {
|
|
2789
|
-
return null;
|
|
2790
|
-
}
|
|
2791
|
-
return createProfilePayload(rawProfile, {
|
|
2792
|
-
release: (_a = event.release) !== null && _a !== void 0 ? _a : '',
|
|
2793
|
-
environment: (_b = event.environment) !== null && _b !== void 0 ? _b : '',
|
|
2794
|
-
event_id: (_c = event.event_id) !== null && _c !== void 0 ? _c : '',
|
|
2795
|
-
transaction: (_d = event.transaction) !== null && _d !== void 0 ? _d : '',
|
|
2796
|
-
start_timestamp: event.start_timestamp ? event.start_timestamp * 1000 : Date.now(),
|
|
2797
|
-
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 : '',
|
|
2798
|
-
profile_id: rawProfile.profile_id,
|
|
2799
|
-
});
|
|
2800
|
-
}
|
|
2801
3575
|
/**
|
|
2802
3576
|
* Creates a profiling envelope item, if the profile does not pass validation, returns null.
|
|
2803
3577
|
* @param {RawThreadCpuProfile}
|
|
@@ -2830,7 +3604,7 @@ function createProfilePayload(cpuProfile, { release, environment, event_id, tran
|
|
|
2830
3604
|
// All profiles and transactions are rejected if this is the case and we want to
|
|
2831
3605
|
// warn users that this is happening if they enable debug flag
|
|
2832
3606
|
if (trace_id && trace_id.length !== 32) {
|
|
2833
|
-
DEBUG_BUILD
|
|
3607
|
+
DEBUG_BUILD && logger.log(`[Profiling] Invalid traceId: ${trace_id} on profiled event`);
|
|
2834
3608
|
}
|
|
2835
3609
|
const enrichedThreadProfile = enrichWithThreadInformation(cpuProfile);
|
|
2836
3610
|
const profile = {
|
|
@@ -2870,54 +3644,6 @@ function createProfilePayload(cpuProfile, { release, environment, event_id, tran
|
|
|
2870
3644
|
};
|
|
2871
3645
|
return profile;
|
|
2872
3646
|
}
|
|
2873
|
-
/**
|
|
2874
|
-
* Creates an envelope from a profiling event.
|
|
2875
|
-
* @param {Event} Profile
|
|
2876
|
-
* @param {DsnComponents} dsn
|
|
2877
|
-
* @param {SdkMetadata} metadata
|
|
2878
|
-
* @param {string|undefined} tunnel
|
|
2879
|
-
* @returns {Envelope|null}
|
|
2880
|
-
*/
|
|
2881
|
-
function createProfilingEventEnvelope(event, dsn, metadata, tunnel) {
|
|
2882
|
-
const sdkInfo = getSdkMetadataForEnvelopeHeader(metadata);
|
|
2883
|
-
enhanceEventWithSdkInfo(event, metadata && metadata.sdk);
|
|
2884
|
-
const envelopeHeaders = createEventEnvelopeHeaders(event, sdkInfo, tunnel, dsn);
|
|
2885
|
-
const profile = createProfilingEventFromTransaction(event);
|
|
2886
|
-
if (!profile) {
|
|
2887
|
-
return null;
|
|
2888
|
-
}
|
|
2889
|
-
const envelopeItem = [
|
|
2890
|
-
{
|
|
2891
|
-
type: 'profile',
|
|
2892
|
-
},
|
|
2893
|
-
// @ts-expect-error profile is not part of EventItem yet
|
|
2894
|
-
profile,
|
|
2895
|
-
];
|
|
2896
|
-
return createEnvelope(envelopeHeaders, [envelopeItem]);
|
|
2897
|
-
}
|
|
2898
|
-
/**
|
|
2899
|
-
* Check if event metadata contains profile information
|
|
2900
|
-
* @param {Event}
|
|
2901
|
-
* @returns {boolean}
|
|
2902
|
-
*/
|
|
2903
|
-
function isProfiledTransactionEvent(event) {
|
|
2904
|
-
return !!(event.sdkProcessingMetadata && event.sdkProcessingMetadata['profile']);
|
|
2905
|
-
}
|
|
2906
|
-
/**
|
|
2907
|
-
* Due to how profiles are attached to event metadata, we may sometimes want to remove them to ensure
|
|
2908
|
-
* they are not processed by other Sentry integrations. This can be the case when we cannot construct a valid
|
|
2909
|
-
* profile from the data we have or some of the mechanisms to send the event (Hub, Transport etc) are not available to us.
|
|
2910
|
-
*
|
|
2911
|
-
* @param {Event | ProfiledEvent} event
|
|
2912
|
-
* @returns {Event}
|
|
2913
|
-
*/
|
|
2914
|
-
function maybeRemoveProfileFromSdkMetadata(event) {
|
|
2915
|
-
if (!isProfiledTransactionEvent(event)) {
|
|
2916
|
-
return event;
|
|
2917
|
-
}
|
|
2918
|
-
delete event.sdkProcessingMetadata.profile;
|
|
2919
|
-
return event;
|
|
2920
|
-
}
|
|
2921
3647
|
/**
|
|
2922
3648
|
* Checks the given sample rate to make sure it is valid type and value (a boolean, or a number between 0 and 1).
|
|
2923
3649
|
* @param {unknown} rate
|
|
@@ -2926,7 +3652,7 @@ function maybeRemoveProfileFromSdkMetadata(event) {
|
|
|
2926
3652
|
function isValidSampleRate(rate) {
|
|
2927
3653
|
// we need to check NaN explicitly because it's of type 'number' and therefore wouldn't get caught by this typecheck
|
|
2928
3654
|
if ((typeof rate !== 'number' && typeof rate !== 'boolean') || (typeof rate === 'number' && isNaN(rate))) {
|
|
2929
|
-
DEBUG_BUILD
|
|
3655
|
+
DEBUG_BUILD &&
|
|
2930
3656
|
logger.warn(`[Profiling] Invalid sample rate. Sample rate must be a boolean or a number between 0 and 1. Got ${JSON.stringify(rate)} of type ${JSON.stringify(typeof rate)}.`);
|
|
2931
3657
|
return false;
|
|
2932
3658
|
}
|
|
@@ -2936,7 +3662,7 @@ function isValidSampleRate(rate) {
|
|
|
2936
3662
|
}
|
|
2937
3663
|
// in case sampleRate is a boolean, it will get automatically cast to 1 if it's true and 0 if it's false
|
|
2938
3664
|
if (rate < 0 || rate > 1) {
|
|
2939
|
-
DEBUG_BUILD
|
|
3665
|
+
DEBUG_BUILD && logger.warn(`[Profiling] Invalid sample rate. Sample rate must be between 0 and 1. Got ${rate}.`);
|
|
2940
3666
|
return false;
|
|
2941
3667
|
}
|
|
2942
3668
|
return true;
|
|
@@ -2948,7 +3674,7 @@ function isValidSampleRate(rate) {
|
|
|
2948
3674
|
*/
|
|
2949
3675
|
function isValidProfile(profile) {
|
|
2950
3676
|
if (profile.samples.length <= 1) {
|
|
2951
|
-
DEBUG_BUILD
|
|
3677
|
+
DEBUG_BUILD &&
|
|
2952
3678
|
// Log a warning if the profile has less than 2 samples so users can know why
|
|
2953
3679
|
// they are not seeing any profiling data and we cant avoid the back and forth
|
|
2954
3680
|
// of asking them to provide us with a dump of the profile data.
|
|
@@ -3071,40 +3797,44 @@ const MAX_PROFILE_DURATION_MS = 30 * 1000;
|
|
|
3071
3797
|
* Takes a transaction and determines if it should be profiled or not. If it should be profiled, it returns the
|
|
3072
3798
|
* profile_id, otherwise returns undefined. Takes care of setting profile context on transaction as well
|
|
3073
3799
|
*/
|
|
3074
|
-
function
|
|
3800
|
+
function maybeProfileSpan(client, span, customSamplingContext) {
|
|
3075
3801
|
// profilesSampleRate is multiplied with tracesSampleRate to get the final sampling rate. We dont perform
|
|
3076
|
-
// the actual multiplication to get the final rate, but we discard the profile if the
|
|
3077
|
-
// so anything after this block from here is based on the
|
|
3078
|
-
|
|
3079
|
-
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)) {
|
|
3080
3805
|
return;
|
|
3081
3806
|
}
|
|
3082
3807
|
// Client and options are required for profiling
|
|
3083
3808
|
if (!client) {
|
|
3084
|
-
DEBUG_BUILD
|
|
3809
|
+
DEBUG_BUILD && logger.log('[Profiling] Profiling disabled, no client found.');
|
|
3085
3810
|
return;
|
|
3086
3811
|
}
|
|
3087
3812
|
const options = client.getOptions();
|
|
3088
3813
|
if (!options) {
|
|
3089
|
-
DEBUG_BUILD
|
|
3814
|
+
DEBUG_BUILD && logger.log('[Profiling] Profiling disabled, no options found.');
|
|
3090
3815
|
return;
|
|
3091
3816
|
}
|
|
3092
3817
|
const profilesSampler = options.profilesSampler;
|
|
3093
3818
|
let profilesSampleRate = options.profilesSampleRate;
|
|
3094
3819
|
// Prefer sampler to sample rate if both are provided.
|
|
3095
3820
|
if (typeof profilesSampler === 'function') {
|
|
3096
|
-
|
|
3097
|
-
|
|
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));
|
|
3098
3828
|
}
|
|
3099
3829
|
// Since this is coming from the user (or from a function provided by the user), who knows what we might get. (The
|
|
3100
3830
|
// only valid values are booleans or numbers between 0 and 1.)
|
|
3101
3831
|
if (!isValidSampleRate(profilesSampleRate)) {
|
|
3102
|
-
DEBUG_BUILD
|
|
3832
|
+
DEBUG_BUILD && logger.warn('[Profiling] Discarding profile because of invalid sample rate.');
|
|
3103
3833
|
return;
|
|
3104
3834
|
}
|
|
3105
3835
|
// if the function returned 0 (or false), or if `profileSampleRate` is 0, it's a sign the profile should be dropped
|
|
3106
3836
|
if (!profilesSampleRate) {
|
|
3107
|
-
DEBUG_BUILD
|
|
3837
|
+
DEBUG_BUILD &&
|
|
3108
3838
|
logger.log(`[Profiling] Discarding profile because ${typeof profilesSampler === 'function'
|
|
3109
3839
|
? 'profileSampler returned 0 or false'
|
|
3110
3840
|
: 'a negative sampling decision was inherited or profileSampleRate is set to 0'}`);
|
|
@@ -3115,15 +3845,13 @@ function maybeProfileTransaction(client, transaction, customSamplingContext) {
|
|
|
3115
3845
|
const sampled = profilesSampleRate === true ? true : Math.random() < profilesSampleRate;
|
|
3116
3846
|
// Check if we should sample this profile
|
|
3117
3847
|
if (!sampled) {
|
|
3118
|
-
DEBUG_BUILD
|
|
3848
|
+
DEBUG_BUILD &&
|
|
3119
3849
|
logger.log(`[Profiling] Discarding profile because it's not included in the random sample (sampling rate = ${Number(profilesSampleRate)})`);
|
|
3120
3850
|
return;
|
|
3121
3851
|
}
|
|
3122
3852
|
const profile_id = uuid4();
|
|
3123
3853
|
CpuProfilerBindings.startProfiling(profile_id);
|
|
3124
|
-
DEBUG_BUILD
|
|
3125
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
3126
|
-
logger.log(`[Profiling] started profiling transaction: ${transaction.name}`);
|
|
3854
|
+
DEBUG_BUILD && logger.log(`[Profiling] started profiling transaction: ${spanToJSON(span).description}`);
|
|
3127
3855
|
// set transaction context - do this regardless if profiling fails down the line
|
|
3128
3856
|
// so that we can still see the profile_id in the transaction context
|
|
3129
3857
|
return profile_id;
|
|
@@ -3134,120 +3862,23 @@ function maybeProfileTransaction(client, transaction, customSamplingContext) {
|
|
|
3134
3862
|
* @param profile_id
|
|
3135
3863
|
* @returns
|
|
3136
3864
|
*/
|
|
3137
|
-
function
|
|
3865
|
+
function stopSpanProfile(span, profile_id) {
|
|
3138
3866
|
// Should not happen, but satisfy the type checker and be safe regardless.
|
|
3139
3867
|
if (!profile_id) {
|
|
3140
3868
|
return null;
|
|
3141
3869
|
}
|
|
3142
3870
|
const profile = CpuProfilerBindings.stopProfiling(profile_id);
|
|
3143
|
-
DEBUG_BUILD
|
|
3144
|
-
|
|
3145
|
-
logger.log(`[Profiling] stopped profiling of transaction: ${transaction.name}`);
|
|
3146
|
-
// In case of an overlapping transaction, stopProfiling may return null and silently ignore the overlapping profile.
|
|
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.
|
|
3147
3873
|
if (!profile) {
|
|
3148
|
-
DEBUG_BUILD
|
|
3149
|
-
logger.log(
|
|
3150
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
3151
|
-
`[Profiling] profiler returned null profile for: ${transaction.name}`, 'this may indicate an overlapping transaction or a call to stopProfiling with a profile title that was never started');
|
|
3874
|
+
DEBUG_BUILD &&
|
|
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');
|
|
3152
3876
|
return null;
|
|
3153
3877
|
}
|
|
3154
3878
|
// Assign profile_id to the profile
|
|
3155
3879
|
profile.profile_id = profile_id;
|
|
3156
3880
|
return profile;
|
|
3157
3881
|
}
|
|
3158
|
-
/**
|
|
3159
|
-
* Wraps startTransaction and stopTransaction with profiling related logic.
|
|
3160
|
-
* startProfiling is called after the call to startTransaction in order to avoid our own code from
|
|
3161
|
-
* being profiled. Because of that same reason, stopProfiling is called before the call to stopTransaction.
|
|
3162
|
-
*/
|
|
3163
|
-
function __PRIVATE__wrapStartTransactionWithProfiling(startTransaction) {
|
|
3164
|
-
return function wrappedStartTransaction(transactionContext, customSamplingContext) {
|
|
3165
|
-
const transaction = startTransaction.call(this, transactionContext, customSamplingContext);
|
|
3166
|
-
// Client is required if we want to profile
|
|
3167
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
3168
|
-
const client = this.getClient();
|
|
3169
|
-
if (!client) {
|
|
3170
|
-
return transaction;
|
|
3171
|
-
}
|
|
3172
|
-
// Check if we should profile this transaction. If a profile_id is returned, then profiling has been started.
|
|
3173
|
-
const profile_id = maybeProfileTransaction(client, transaction, customSamplingContext);
|
|
3174
|
-
if (!profile_id) {
|
|
3175
|
-
return transaction;
|
|
3176
|
-
}
|
|
3177
|
-
// A couple of important things to note here:
|
|
3178
|
-
// `CpuProfilerBindings.stopProfiling` will be scheduled to run in 30seconds in order to exceed max profile duration.
|
|
3179
|
-
// Whichever of the two (transaction.finish/timeout) is first to run, the profiling will be stopped and the gathered profile
|
|
3180
|
-
// will be processed when the original transaction is finished. Since onProfileHandler can be invoked multiple times in the
|
|
3181
|
-
// event of an error or user mistake (calling transaction.finish multiple times), it is important that the behavior of onProfileHandler
|
|
3182
|
-
// is idempotent as we do not want any timings or profiles to be overriden by the last call to onProfileHandler.
|
|
3183
|
-
// After the original finish method is called, the event will be reported through the integration and delegated to transport.
|
|
3184
|
-
let profile = null;
|
|
3185
|
-
const options = client.getOptions();
|
|
3186
|
-
// Not intended for external use, hence missing types, but we want to profile a couple of things at Sentry that
|
|
3187
|
-
// currently exceed the default timeout set by the SDKs.
|
|
3188
|
-
const maxProfileDurationMs = (options._experiments && options._experiments['maxProfileDurationMs']) || MAX_PROFILE_DURATION_MS;
|
|
3189
|
-
// Enqueue a timeout to prevent profiles from running over max duration.
|
|
3190
|
-
let maxDurationTimeoutID = global.setTimeout(() => {
|
|
3191
|
-
DEBUG_BUILD$1 &&
|
|
3192
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
3193
|
-
logger.log('[Profiling] max profile duration elapsed, stopping profiling for:', transaction.name);
|
|
3194
|
-
profile = stopTransactionProfile(transaction, profile_id);
|
|
3195
|
-
}, maxProfileDurationMs);
|
|
3196
|
-
// We need to reference the original finish call to avoid creating an infinite loop
|
|
3197
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
3198
|
-
const originalFinish = transaction.finish.bind(transaction);
|
|
3199
|
-
// Wrap the transaction finish method to stop profiling and set the profile on the transaction.
|
|
3200
|
-
function profilingWrappedTransactionFinish() {
|
|
3201
|
-
if (!profile_id) {
|
|
3202
|
-
return originalFinish();
|
|
3203
|
-
}
|
|
3204
|
-
// We stop the handler first to ensure that the timeout is cleared and the profile is stopped.
|
|
3205
|
-
if (maxDurationTimeoutID) {
|
|
3206
|
-
global.clearTimeout(maxDurationTimeoutID);
|
|
3207
|
-
maxDurationTimeoutID = undefined;
|
|
3208
|
-
}
|
|
3209
|
-
// onProfileHandler should always return the same profile even if this is called multiple times.
|
|
3210
|
-
// Always call onProfileHandler to ensure stopProfiling is called and the timeout is cleared.
|
|
3211
|
-
if (!profile) {
|
|
3212
|
-
profile = stopTransactionProfile(transaction, profile_id);
|
|
3213
|
-
}
|
|
3214
|
-
// @ts-expect-error profile is not part of metadata
|
|
3215
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
3216
|
-
transaction.setMetadata({ profile });
|
|
3217
|
-
return originalFinish();
|
|
3218
|
-
}
|
|
3219
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
3220
|
-
transaction.finish = profilingWrappedTransactionFinish;
|
|
3221
|
-
return transaction;
|
|
3222
|
-
};
|
|
3223
|
-
}
|
|
3224
|
-
/**
|
|
3225
|
-
* Patches startTransaction and stopTransaction with profiling logic.
|
|
3226
|
-
* This is used by the SDK's that do not support event hooks.
|
|
3227
|
-
* @private
|
|
3228
|
-
*/
|
|
3229
|
-
function _addProfilingExtensionMethods() {
|
|
3230
|
-
const carrier = getMainCarrier();
|
|
3231
|
-
if (!carrier.__SENTRY__) {
|
|
3232
|
-
DEBUG_BUILD$1 && logger.log("[Profiling] Can't find main carrier, profiling won't work.");
|
|
3233
|
-
return;
|
|
3234
|
-
}
|
|
3235
|
-
carrier.__SENTRY__.extensions = carrier.__SENTRY__.extensions || {};
|
|
3236
|
-
if (!carrier.__SENTRY__.extensions['startTransaction']) {
|
|
3237
|
-
DEBUG_BUILD$1 && logger.log('[Profiling] startTransaction does not exists, profiling will not work.');
|
|
3238
|
-
return;
|
|
3239
|
-
}
|
|
3240
|
-
DEBUG_BUILD$1 && logger.log('[Profiling] startTransaction exists, patching it with profiling functionality...');
|
|
3241
|
-
carrier.__SENTRY__.extensions['startTransaction'] = __PRIVATE__wrapStartTransactionWithProfiling(
|
|
3242
|
-
// This is patched by sentry/tracing, we are going to re-patch it...
|
|
3243
|
-
carrier.__SENTRY__.extensions['startTransaction']);
|
|
3244
|
-
}
|
|
3245
|
-
/**
|
|
3246
|
-
* This patches the global object and injects the Profiling extensions methods
|
|
3247
|
-
*/
|
|
3248
|
-
function addProfilingExtensionMethods() {
|
|
3249
|
-
_addProfilingExtensionMethods();
|
|
3250
|
-
}
|
|
3251
3882
|
|
|
3252
3883
|
const MAX_PROFILE_QUEUE_LENGTH = 50;
|
|
3253
3884
|
const PROFILE_QUEUE = [];
|
|
@@ -3259,28 +3890,17 @@ function addToProfileQueue(profile) {
|
|
|
3259
3890
|
PROFILE_QUEUE.shift();
|
|
3260
3891
|
}
|
|
3261
3892
|
}
|
|
3262
|
-
/**
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
}
|
|
3274
|
-
/**
|
|
3275
|
-
* @inheritDoc
|
|
3276
|
-
*/
|
|
3277
|
-
setupOnce(addGlobalEventProcessor, getCurrentHub) {
|
|
3278
|
-
this.getCurrentHub = getCurrentHub;
|
|
3279
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
3280
|
-
const client = this.getCurrentHub().getClient();
|
|
3281
|
-
if (client && typeof client.on === 'function') {
|
|
3282
|
-
client.on('startTransaction', (transaction) => {
|
|
3283
|
-
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);
|
|
3284
3904
|
if (profile_id) {
|
|
3285
3905
|
const options = client.getOptions();
|
|
3286
3906
|
// Not intended for external use, hence missing types, but we want to profile a couple of things at Sentry that
|
|
@@ -3293,32 +3913,26 @@ class ProfilingIntegration {
|
|
|
3293
3913
|
}
|
|
3294
3914
|
// Enqueue a timeout to prevent profiles from running over max duration.
|
|
3295
3915
|
PROFILE_TIMEOUTS[profile_id] = global.setTimeout(() => {
|
|
3296
|
-
DEBUG_BUILD
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
const profile = stopTransactionProfile(transaction, profile_id);
|
|
3916
|
+
DEBUG_BUILD &&
|
|
3917
|
+
logger.log('[Profiling] max profile duration elapsed, stopping profiling for:', spanToJSON(span).description);
|
|
3918
|
+
const profile = stopSpanProfile(span, profile_id);
|
|
3300
3919
|
if (profile) {
|
|
3301
3920
|
addToProfileQueue(profile);
|
|
3302
3921
|
}
|
|
3303
3922
|
}, maxProfileDurationMs);
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
// @ts-expect-error profile_id is not part of the metadata type
|
|
3307
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
3308
|
-
transaction.setMetadata({ profile_id: profile_id });
|
|
3923
|
+
getCurrentScope().setContext('profile', { profile_id });
|
|
3924
|
+
spanToProfileIdMap.set(span, profile_id);
|
|
3309
3925
|
}
|
|
3310
3926
|
});
|
|
3311
|
-
client.on('
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
const profile_id = transaction.metadata.profile_id;
|
|
3315
|
-
if (profile_id && typeof profile_id === 'string') {
|
|
3927
|
+
client.on('spanEnd', span => {
|
|
3928
|
+
const profile_id = spanToProfileIdMap.get(span);
|
|
3929
|
+
if (profile_id) {
|
|
3316
3930
|
if (PROFILE_TIMEOUTS[profile_id]) {
|
|
3317
3931
|
global.clearTimeout(PROFILE_TIMEOUTS[profile_id]);
|
|
3318
3932
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
3319
3933
|
delete PROFILE_TIMEOUTS[profile_id];
|
|
3320
3934
|
}
|
|
3321
|
-
const profile =
|
|
3935
|
+
const profile = stopSpanProfile(span, profile_id);
|
|
3322
3936
|
if (profile) {
|
|
3323
3937
|
addToProfileQueue(profile);
|
|
3324
3938
|
}
|
|
@@ -3343,18 +3957,17 @@ class ProfilingIntegration {
|
|
|
3343
3957
|
}
|
|
3344
3958
|
// Remove the profile from the transaction context before sending, relay will take care of the rest.
|
|
3345
3959
|
if (profileContext) {
|
|
3346
|
-
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
3347
3960
|
(_b = profiledTransaction.contexts) === null || _b === void 0 ? true : delete _b['profile'];
|
|
3348
3961
|
}
|
|
3349
3962
|
// We need to find both a profile and a transaction event for the same profile_id.
|
|
3350
3963
|
const profileIndex = PROFILE_QUEUE.findIndex(p => p.profile_id === profile_id);
|
|
3351
3964
|
if (profileIndex === -1) {
|
|
3352
|
-
DEBUG_BUILD
|
|
3965
|
+
DEBUG_BUILD && logger.log(`[Profiling] Could not retrieve profile for transaction: ${profile_id}`);
|
|
3353
3966
|
continue;
|
|
3354
3967
|
}
|
|
3355
3968
|
const cpuProfile = PROFILE_QUEUE[profileIndex];
|
|
3356
3969
|
if (!cpuProfile) {
|
|
3357
|
-
DEBUG_BUILD
|
|
3970
|
+
DEBUG_BUILD && logger.log(`[Profiling] Could not retrieve profile for transaction: ${profile_id}`);
|
|
3358
3971
|
continue;
|
|
3359
3972
|
}
|
|
3360
3973
|
// Remove the profile from the queue.
|
|
@@ -3376,70 +3989,16 @@ class ProfilingIntegration {
|
|
|
3376
3989
|
}
|
|
3377
3990
|
addProfilesToEnvelope(envelope, profilesToAddToEnvelope);
|
|
3378
3991
|
});
|
|
3379
|
-
}
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
addProfilingExtensionMethods();
|
|
3383
|
-
addGlobalEventProcessor(this.handleGlobalEvent.bind(this));
|
|
3384
|
-
}
|
|
3385
|
-
}
|
|
3386
|
-
/**
|
|
3387
|
-
* @inheritDoc
|
|
3388
|
-
*/
|
|
3389
|
-
handleGlobalEvent(event) {
|
|
3390
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
3391
|
-
if (this.getCurrentHub === undefined) {
|
|
3392
|
-
return maybeRemoveProfileFromSdkMetadata(event);
|
|
3393
|
-
}
|
|
3394
|
-
if (isProfiledTransactionEvent(event)) {
|
|
3395
|
-
// Client, Dsn and Transport are all required to be able to send the profiling event to Sentry.
|
|
3396
|
-
// If either of them is not available, we remove the profile from the transaction event.
|
|
3397
|
-
// and forward it to the next event processor.
|
|
3398
|
-
const hub = this.getCurrentHub();
|
|
3399
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
3400
|
-
const client = hub.getClient();
|
|
3401
|
-
if (!client) {
|
|
3402
|
-
DEBUG_BUILD$1 &&
|
|
3403
|
-
logger.log('[Profiling] getClient did not return a Client, removing profile from event and forwarding to next event processors.');
|
|
3404
|
-
return maybeRemoveProfileFromSdkMetadata(event);
|
|
3405
|
-
}
|
|
3406
|
-
const dsn = client.getDsn();
|
|
3407
|
-
if (!dsn) {
|
|
3408
|
-
DEBUG_BUILD$1 &&
|
|
3409
|
-
logger.log('[Profiling] getDsn did not return a Dsn, removing profile from event and forwarding to next event processors.');
|
|
3410
|
-
return maybeRemoveProfileFromSdkMetadata(event);
|
|
3411
|
-
}
|
|
3412
|
-
const transport = client.getTransport();
|
|
3413
|
-
if (!transport) {
|
|
3414
|
-
DEBUG_BUILD$1 &&
|
|
3415
|
-
logger.log('[Profiling] getTransport did not return a Transport, removing profile from event and forwarding to next event processors.');
|
|
3416
|
-
return maybeRemoveProfileFromSdkMetadata(event);
|
|
3417
|
-
}
|
|
3418
|
-
// If all required components are available, we construct a profiling event envelope and send it to Sentry.
|
|
3419
|
-
DEBUG_BUILD$1 && logger.log('[Profiling] Preparing envelope and sending a profiling event');
|
|
3420
|
-
const envelope = createProfilingEventEnvelope(event, dsn);
|
|
3421
|
-
if (envelope) {
|
|
3422
|
-
// Fire and forget, we don't want to block the main event processing flow.
|
|
3423
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
3424
|
-
transport.send(envelope);
|
|
3425
|
-
}
|
|
3426
|
-
}
|
|
3427
|
-
// Ensure sdkProcessingMetadata["profile"] is removed from the event before forwarding it to the next event processor.
|
|
3428
|
-
return maybeRemoveProfileFromSdkMetadata(event);
|
|
3429
|
-
});
|
|
3430
|
-
}
|
|
3431
|
-
}
|
|
3992
|
+
},
|
|
3993
|
+
};
|
|
3994
|
+
});
|
|
3432
3995
|
/**
|
|
3433
3996
|
* We need this integration in order to send data to Sentry. We hook into the event processor
|
|
3434
3997
|
* and inspect each event to see if it is a transaction event and if that transaction event
|
|
3435
3998
|
* contains a profile on it's metadata. If that is the case, we create a profiling event envelope
|
|
3436
3999
|
* and delete the profile from the transaction metadata.
|
|
3437
4000
|
*/
|
|
3438
|
-
const nodeProfilingIntegration = (
|
|
3439
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
3440
|
-
return new ProfilingIntegration();
|
|
3441
|
-
});
|
|
4001
|
+
const nodeProfilingIntegration = defineIntegration(_nodeProfilingIntegration);
|
|
3442
4002
|
|
|
3443
|
-
exports.ProfilingIntegration = ProfilingIntegration;
|
|
3444
4003
|
exports.nodeProfilingIntegration = nodeProfilingIntegration;
|
|
3445
4004
|
//# sourceMappingURL=index.js.map
|