@sentry/profiling-node 8.0.0-alpha.3 → 8.0.0-alpha.5
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/lib/cjs/index.js +766 -0
- package/lib/cjs/index.js.map +1 -0
- package/lib/esm/index.js +768 -0
- package/lib/esm/index.js.map +1 -0
- package/lib/esm/package.json +1 -0
- 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/package.json +11 -7
- package/lib/index.js +0 -4004
- package/lib/index.js.map +0 -1
package/lib/index.js
DELETED
|
@@ -1,4004 +0,0 @@
|
|
|
1
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
-
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const detectLibc = require('detect-libc');
|
|
6
|
-
const nodeAbi = require('node-abi');
|
|
7
|
-
const process$1 = require('process');
|
|
8
|
-
const worker_threads = require('worker_threads');
|
|
9
|
-
|
|
10
|
-
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
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
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Checks whether given value is an instance of the given built-in class.
|
|
32
|
-
*
|
|
33
|
-
* @param wat The value to be checked
|
|
34
|
-
* @param className
|
|
35
|
-
* @returns A boolean representing the result.
|
|
36
|
-
*/
|
|
37
|
-
function isBuiltin(wat, className) {
|
|
38
|
-
return objectToString.call(wat) === `[object ${className}]`;
|
|
39
|
-
}
|
|
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
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Checks whether given value's type is an object literal, or a class instance.
|
|
54
|
-
* {@link isPlainObject}.
|
|
55
|
-
*
|
|
56
|
-
* @param wat A value to be checked.
|
|
57
|
-
* @returns A boolean representing the result.
|
|
58
|
-
*/
|
|
59
|
-
function isPlainObject(wat) {
|
|
60
|
-
return isBuiltin(wat, 'Object');
|
|
61
|
-
}
|
|
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
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Checks whether given value has a then function.
|
|
87
|
-
* @param wat A value to be checked.
|
|
88
|
-
*/
|
|
89
|
-
function isThenable(wat) {
|
|
90
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
91
|
-
return Boolean(wat && wat.then && typeof wat.then === 'function');
|
|
92
|
-
}
|
|
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
|
-
|
|
143
|
-
/** Internal global with common properties and Sentry extensions */
|
|
144
|
-
|
|
145
|
-
// The code below for 'isGlobalObj' and 'GLOBAL_OBJ' was copied from core-js before modification
|
|
146
|
-
// https://github.com/zloirock/core-js/blob/1b944df55282cdc99c90db5f49eb0b6eda2cc0a3/packages/core-js/internals/global.js
|
|
147
|
-
// core-js has the following licence:
|
|
148
|
-
//
|
|
149
|
-
// Copyright (c) 2014-2022 Denis Pushkarev
|
|
150
|
-
//
|
|
151
|
-
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
152
|
-
// of this software and associated documentation files (the "Software"), to deal
|
|
153
|
-
// in the Software without restriction, including without limitation the rights
|
|
154
|
-
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
155
|
-
// copies of the Software, and to permit persons to whom the Software is
|
|
156
|
-
// furnished to do so, subject to the following conditions:
|
|
157
|
-
//
|
|
158
|
-
// The above copyright notice and this permission notice shall be included in
|
|
159
|
-
// all copies or substantial portions of the Software.
|
|
160
|
-
//
|
|
161
|
-
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
162
|
-
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
163
|
-
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
164
|
-
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
165
|
-
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
166
|
-
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
167
|
-
// THE SOFTWARE.
|
|
168
|
-
|
|
169
|
-
/** Returns 'obj' if it's the global object, otherwise returns undefined */
|
|
170
|
-
function isGlobalObj(obj) {
|
|
171
|
-
return obj && obj.Math == Math ? obj : undefined;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/** Get's the global object for the current JavaScript runtime */
|
|
175
|
-
const GLOBAL_OBJ =
|
|
176
|
-
(typeof globalThis == 'object' && isGlobalObj(globalThis)) ||
|
|
177
|
-
// eslint-disable-next-line no-restricted-globals
|
|
178
|
-
(typeof window == 'object' && isGlobalObj(window)) ||
|
|
179
|
-
(typeof self == 'object' && isGlobalObj(self)) ||
|
|
180
|
-
(typeof global == 'object' && isGlobalObj(global)) ||
|
|
181
|
-
(function () {
|
|
182
|
-
return this;
|
|
183
|
-
})() ||
|
|
184
|
-
{};
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* @deprecated Use GLOBAL_OBJ instead or WINDOW from @sentry/browser. This will be removed in v8
|
|
188
|
-
*/
|
|
189
|
-
function getGlobalObject() {
|
|
190
|
-
return GLOBAL_OBJ ;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Returns a global singleton contained in the global `__SENTRY__` object.
|
|
195
|
-
*
|
|
196
|
-
* If the singleton doesn't already exist in `__SENTRY__`, it will be created using the given factory
|
|
197
|
-
* function and added to the `__SENTRY__` object.
|
|
198
|
-
*
|
|
199
|
-
* @param name name of the global singleton on __SENTRY__
|
|
200
|
-
* @param creator creator Factory function to create the singleton if it doesn't already exist on `__SENTRY__`
|
|
201
|
-
* @param obj (Optional) The global object on which to look for `__SENTRY__`, if not `GLOBAL_OBJ`'s return value
|
|
202
|
-
* @returns the singleton
|
|
203
|
-
*/
|
|
204
|
-
function getGlobalSingleton(name, creator, obj) {
|
|
205
|
-
const gbl = (obj || GLOBAL_OBJ) ;
|
|
206
|
-
const __SENTRY__ = (gbl.__SENTRY__ = gbl.__SENTRY__ || {});
|
|
207
|
-
const singleton = __SENTRY__[name] || (__SENTRY__[name] = creator());
|
|
208
|
-
return singleton;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
212
|
-
const WINDOW$5 = getGlobalObject();
|
|
213
|
-
|
|
214
|
-
const DEFAULT_MAX_STRING_LENGTH = 80;
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Given a child DOM element, returns a query-selector statement describing that
|
|
218
|
-
* and its ancestors
|
|
219
|
-
* e.g. [HTMLElement] => body > div > input#foo.btn[name=baz]
|
|
220
|
-
* @returns generated DOM path
|
|
221
|
-
*/
|
|
222
|
-
function htmlTreeAsString(
|
|
223
|
-
elem,
|
|
224
|
-
options = {},
|
|
225
|
-
) {
|
|
226
|
-
if (!elem) {
|
|
227
|
-
return '<unknown>';
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// try/catch both:
|
|
231
|
-
// - accessing event.target (see getsentry/raven-js#838, #768)
|
|
232
|
-
// - `htmlTreeAsString` because it's complex, and just accessing the DOM incorrectly
|
|
233
|
-
// - can throw an exception in some circumstances.
|
|
234
|
-
try {
|
|
235
|
-
let currentElem = elem ;
|
|
236
|
-
const MAX_TRAVERSE_HEIGHT = 5;
|
|
237
|
-
const out = [];
|
|
238
|
-
let height = 0;
|
|
239
|
-
let len = 0;
|
|
240
|
-
const separator = ' > ';
|
|
241
|
-
const sepLength = separator.length;
|
|
242
|
-
let nextStr;
|
|
243
|
-
const keyAttrs = Array.isArray(options) ? options : options.keyAttrs;
|
|
244
|
-
const maxStringLength = (!Array.isArray(options) && options.maxStringLength) || DEFAULT_MAX_STRING_LENGTH;
|
|
245
|
-
|
|
246
|
-
while (currentElem && height++ < MAX_TRAVERSE_HEIGHT) {
|
|
247
|
-
nextStr = _htmlElementAsString(currentElem, keyAttrs);
|
|
248
|
-
// bail out if
|
|
249
|
-
// - nextStr is the 'html' element
|
|
250
|
-
// - the length of the string that would be created exceeds maxStringLength
|
|
251
|
-
// (ignore this limit if we are on the first iteration)
|
|
252
|
-
if (nextStr === 'html' || (height > 1 && len + out.length * sepLength + nextStr.length >= maxStringLength)) {
|
|
253
|
-
break;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
out.push(nextStr);
|
|
257
|
-
|
|
258
|
-
len += nextStr.length;
|
|
259
|
-
currentElem = currentElem.parentNode;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return out.reverse().join(separator);
|
|
263
|
-
} catch (_oO) {
|
|
264
|
-
return '<unknown>';
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Returns a simple, query-selector representation of a DOM element
|
|
270
|
-
* e.g. [HTMLElement] => input#foo.btn[name=baz]
|
|
271
|
-
* @returns generated DOM path
|
|
272
|
-
*/
|
|
273
|
-
function _htmlElementAsString(el, keyAttrs) {
|
|
274
|
-
const elem = el
|
|
275
|
-
|
|
276
|
-
;
|
|
277
|
-
|
|
278
|
-
const out = [];
|
|
279
|
-
let className;
|
|
280
|
-
let classes;
|
|
281
|
-
let key;
|
|
282
|
-
let attr;
|
|
283
|
-
let i;
|
|
284
|
-
|
|
285
|
-
if (!elem || !elem.tagName) {
|
|
286
|
-
return '';
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// @ts-expect-error WINDOW has HTMLElement
|
|
290
|
-
if (WINDOW$5.HTMLElement) {
|
|
291
|
-
// If using the component name annotation plugin, this value may be available on the DOM node
|
|
292
|
-
if (elem instanceof HTMLElement && elem.dataset && elem.dataset['sentryComponent']) {
|
|
293
|
-
return elem.dataset['sentryComponent'];
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
out.push(elem.tagName.toLowerCase());
|
|
298
|
-
|
|
299
|
-
// Pairs of attribute keys defined in `serializeAttribute` and their values on element.
|
|
300
|
-
const keyAttrPairs =
|
|
301
|
-
keyAttrs && keyAttrs.length
|
|
302
|
-
? keyAttrs.filter(keyAttr => elem.getAttribute(keyAttr)).map(keyAttr => [keyAttr, elem.getAttribute(keyAttr)])
|
|
303
|
-
: null;
|
|
304
|
-
|
|
305
|
-
if (keyAttrPairs && keyAttrPairs.length) {
|
|
306
|
-
keyAttrPairs.forEach(keyAttrPair => {
|
|
307
|
-
out.push(`[${keyAttrPair[0]}="${keyAttrPair[1]}"]`);
|
|
308
|
-
});
|
|
309
|
-
} else {
|
|
310
|
-
if (elem.id) {
|
|
311
|
-
out.push(`#${elem.id}`);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
className = elem.className;
|
|
315
|
-
if (className && isString(className)) {
|
|
316
|
-
classes = className.split(/\s+/);
|
|
317
|
-
for (i = 0; i < classes.length; i++) {
|
|
318
|
-
out.push(`.${classes[i]}`);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
const allowedAttrs = ['aria-label', 'type', 'name', 'title', 'alt'];
|
|
323
|
-
for (i = 0; i < allowedAttrs.length; i++) {
|
|
324
|
-
key = allowedAttrs[i];
|
|
325
|
-
attr = elem.getAttribute(key);
|
|
326
|
-
if (attr) {
|
|
327
|
-
out.push(`[${key}="${attr}"]`);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
return out.join('');
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
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.
|
|
335
|
-
*
|
|
336
|
-
* ATTENTION: This constant must never cross package boundaries (i.e. be exported) to guarantee that it can be used for tree shaking.
|
|
337
|
-
*/
|
|
338
|
-
const DEBUG_BUILD$2 = (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__);
|
|
339
|
-
|
|
340
|
-
/** Prefix for logging strings */
|
|
341
|
-
const PREFIX = 'Sentry Logger ';
|
|
342
|
-
|
|
343
|
-
const CONSOLE_LEVELS = [
|
|
344
|
-
'debug',
|
|
345
|
-
'info',
|
|
346
|
-
'warn',
|
|
347
|
-
'error',
|
|
348
|
-
'log',
|
|
349
|
-
'assert',
|
|
350
|
-
'trace',
|
|
351
|
-
] ;
|
|
352
|
-
|
|
353
|
-
/** This may be mutated by the console instrumentation. */
|
|
354
|
-
const originalConsoleMethods
|
|
355
|
-
|
|
356
|
-
= {};
|
|
357
|
-
|
|
358
|
-
/** JSDoc */
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Temporarily disable sentry console instrumentations.
|
|
362
|
-
*
|
|
363
|
-
* @param callback The function to run against the original `console` messages
|
|
364
|
-
* @returns The results of the callback
|
|
365
|
-
*/
|
|
366
|
-
function consoleSandbox(callback) {
|
|
367
|
-
if (!('console' in GLOBAL_OBJ)) {
|
|
368
|
-
return callback();
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
const console = GLOBAL_OBJ.console ;
|
|
372
|
-
const wrappedFuncs = {};
|
|
373
|
-
|
|
374
|
-
const wrappedLevels = Object.keys(originalConsoleMethods) ;
|
|
375
|
-
|
|
376
|
-
// Restore all wrapped console methods
|
|
377
|
-
wrappedLevels.forEach(level => {
|
|
378
|
-
const originalConsoleMethod = originalConsoleMethods[level] ;
|
|
379
|
-
wrappedFuncs[level] = console[level] ;
|
|
380
|
-
console[level] = originalConsoleMethod;
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
try {
|
|
384
|
-
return callback();
|
|
385
|
-
} finally {
|
|
386
|
-
// Revert restoration to wrapped state
|
|
387
|
-
wrappedLevels.forEach(level => {
|
|
388
|
-
console[level] = wrappedFuncs[level] ;
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
function makeLogger() {
|
|
394
|
-
let enabled = false;
|
|
395
|
-
const logger = {
|
|
396
|
-
enable: () => {
|
|
397
|
-
enabled = true;
|
|
398
|
-
},
|
|
399
|
-
disable: () => {
|
|
400
|
-
enabled = false;
|
|
401
|
-
},
|
|
402
|
-
isEnabled: () => enabled,
|
|
403
|
-
};
|
|
404
|
-
|
|
405
|
-
if (DEBUG_BUILD$2) {
|
|
406
|
-
CONSOLE_LEVELS.forEach(name => {
|
|
407
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
408
|
-
logger[name] = (...args) => {
|
|
409
|
-
if (enabled) {
|
|
410
|
-
consoleSandbox(() => {
|
|
411
|
-
GLOBAL_OBJ.console[name](`${PREFIX}[${name}]:`, ...args);
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
};
|
|
415
|
-
});
|
|
416
|
-
} else {
|
|
417
|
-
CONSOLE_LEVELS.forEach(name => {
|
|
418
|
-
logger[name] = () => undefined;
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
return logger ;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
const logger = makeLogger();
|
|
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
|
-
|
|
445
|
-
/**
|
|
446
|
-
* This extracts the original function if available. See
|
|
447
|
-
* `markFunctionWrapped` for more information.
|
|
448
|
-
*
|
|
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.
|
|
459
|
-
*
|
|
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.
|
|
463
|
-
*/
|
|
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
|
-
}
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
/**
|
|
521
|
-
* Given any object, return a new object having removed all fields whose value was `undefined`.
|
|
522
|
-
* Works recursively on objects and arrays.
|
|
523
|
-
*
|
|
524
|
-
* Attention: This function keeps circular references in the returned object.
|
|
525
|
-
*/
|
|
526
|
-
function dropUndefinedKeys(inputValue) {
|
|
527
|
-
// This map keeps track of what already visited nodes map to.
|
|
528
|
-
// Our Set - based memoBuilder doesn't work here because we want to the output object to have the same circular
|
|
529
|
-
// references as the input object.
|
|
530
|
-
const memoizationMap = new Map();
|
|
531
|
-
|
|
532
|
-
// This function just proxies `_dropUndefinedKeys` to keep the `memoBuilder` out of this function's API
|
|
533
|
-
return _dropUndefinedKeys(inputValue, memoizationMap);
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
function _dropUndefinedKeys(inputValue, memoizationMap) {
|
|
537
|
-
if (isPojo(inputValue)) {
|
|
538
|
-
// If this node has already been visited due to a circular reference, return the object it was mapped to in the new object
|
|
539
|
-
const memoVal = memoizationMap.get(inputValue);
|
|
540
|
-
if (memoVal !== undefined) {
|
|
541
|
-
return memoVal ;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
const returnValue = {};
|
|
545
|
-
// Store the mapping of this value in case we visit it again, in case of circular data
|
|
546
|
-
memoizationMap.set(inputValue, returnValue);
|
|
547
|
-
|
|
548
|
-
for (const key of Object.keys(inputValue)) {
|
|
549
|
-
if (typeof inputValue[key] !== 'undefined') {
|
|
550
|
-
returnValue[key] = _dropUndefinedKeys(inputValue[key], memoizationMap);
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
return returnValue ;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
if (Array.isArray(inputValue)) {
|
|
558
|
-
// If this node has already been visited due to a circular reference, return the array it was mapped to in the new object
|
|
559
|
-
const memoVal = memoizationMap.get(inputValue);
|
|
560
|
-
if (memoVal !== undefined) {
|
|
561
|
-
return memoVal ;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
const returnValue = [];
|
|
565
|
-
// Store the mapping of this value in case we visit it again, in case of circular data
|
|
566
|
-
memoizationMap.set(inputValue, returnValue);
|
|
567
|
-
|
|
568
|
-
inputValue.forEach((item) => {
|
|
569
|
-
returnValue.push(_dropUndefinedKeys(item, memoizationMap));
|
|
570
|
-
});
|
|
571
|
-
|
|
572
|
-
return returnValue ;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
return inputValue;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
function isPojo(input) {
|
|
579
|
-
if (!isPlainObject(input)) {
|
|
580
|
-
return false;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
try {
|
|
584
|
-
const name = (Object.getPrototypeOf(input) ).constructor.name;
|
|
585
|
-
return !name || name === 'Object';
|
|
586
|
-
} catch (e) {
|
|
587
|
-
return true;
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
/**
|
|
592
|
-
* UUID4 generator
|
|
593
|
-
*
|
|
594
|
-
* @returns string Generated UUID4.
|
|
595
|
-
*/
|
|
596
|
-
function uuid4() {
|
|
597
|
-
const gbl = GLOBAL_OBJ ;
|
|
598
|
-
const crypto = gbl.crypto || gbl.msCrypto;
|
|
599
|
-
|
|
600
|
-
let getRandomByte = () => Math.random() * 16;
|
|
601
|
-
try {
|
|
602
|
-
if (crypto && crypto.randomUUID) {
|
|
603
|
-
return crypto.randomUUID().replace(/-/g, '');
|
|
604
|
-
}
|
|
605
|
-
if (crypto && crypto.getRandomValues) {
|
|
606
|
-
getRandomByte = () => {
|
|
607
|
-
// crypto.getRandomValues might return undefined instead of the typed array
|
|
608
|
-
// in old Chromium versions (e.g. 23.0.1235.0 (151422))
|
|
609
|
-
// However, `typedArray` is still filled in-place.
|
|
610
|
-
// @see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues#typedarray
|
|
611
|
-
const typedArray = new Uint8Array(1);
|
|
612
|
-
crypto.getRandomValues(typedArray);
|
|
613
|
-
return typedArray[0];
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
|
-
} catch (_) {
|
|
617
|
-
// some runtimes can crash invoking crypto
|
|
618
|
-
// https://github.com/getsentry/sentry-javascript/issues/8935
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
|
|
622
|
-
// Concatenating the following numbers as strings results in '10000000100040008000100000000000'
|
|
623
|
-
return (([1e7] ) + 1e3 + 4e3 + 8e3 + 1e11).replace(/[018]/g, c =>
|
|
624
|
-
// eslint-disable-next-line no-bitwise
|
|
625
|
-
((c ) ^ ((getRandomByte() & 15) >> ((c ) / 4))).toString(16),
|
|
626
|
-
);
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
630
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
631
|
-
|
|
632
|
-
/**
|
|
633
|
-
* Helper to decycle json objects
|
|
634
|
-
*/
|
|
635
|
-
function memoBuilder() {
|
|
636
|
-
const hasWeakSet = typeof WeakSet === 'function';
|
|
637
|
-
const inner = hasWeakSet ? new WeakSet() : [];
|
|
638
|
-
function memoize(obj) {
|
|
639
|
-
if (hasWeakSet) {
|
|
640
|
-
if (inner.has(obj)) {
|
|
641
|
-
return true;
|
|
642
|
-
}
|
|
643
|
-
inner.add(obj);
|
|
644
|
-
return false;
|
|
645
|
-
}
|
|
646
|
-
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
|
647
|
-
for (let i = 0; i < inner.length; i++) {
|
|
648
|
-
const value = inner[i];
|
|
649
|
-
if (value === obj) {
|
|
650
|
-
return true;
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
inner.push(obj);
|
|
654
|
-
return false;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
function unmemoize(obj) {
|
|
658
|
-
if (hasWeakSet) {
|
|
659
|
-
inner.delete(obj);
|
|
660
|
-
} else {
|
|
661
|
-
for (let i = 0; i < inner.length; i++) {
|
|
662
|
-
if (inner[i] === obj) {
|
|
663
|
-
inner.splice(i, 1);
|
|
664
|
-
break;
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
return [memoize, unmemoize];
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
/**
|
|
673
|
-
* Recursively normalizes the given object.
|
|
674
|
-
*
|
|
675
|
-
* - Creates a copy to prevent original input mutation
|
|
676
|
-
* - Skips non-enumerable properties
|
|
677
|
-
* - When stringifying, calls `toJSON` if implemented
|
|
678
|
-
* - Removes circular references
|
|
679
|
-
* - Translates non-serializable values (`undefined`/`NaN`/functions) to serializable format
|
|
680
|
-
* - Translates known global objects/classes to a string representations
|
|
681
|
-
* - Takes care of `Error` object serialization
|
|
682
|
-
* - Optionally limits depth of final output
|
|
683
|
-
* - Optionally limits number of properties/elements included in any single object/array
|
|
684
|
-
*
|
|
685
|
-
* @param input The object to be normalized.
|
|
686
|
-
* @param depth The max depth to which to normalize the object. (Anything deeper stringified whole.)
|
|
687
|
-
* @param maxProperties The max number of elements or properties to be included in any single array or
|
|
688
|
-
* object in the normallized output.
|
|
689
|
-
* @returns A normalized version of the object, or `"**non-serializable**"` if any errors are thrown during normalization.
|
|
690
|
-
*/
|
|
691
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
692
|
-
function normalize(input, depth = 100, maxProperties = +Infinity) {
|
|
693
|
-
try {
|
|
694
|
-
// since we're at the outermost level, we don't provide a key
|
|
695
|
-
return visit('', input, depth, maxProperties);
|
|
696
|
-
} catch (err) {
|
|
697
|
-
return { ERROR: `**non-serializable** (${err})` };
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
/**
|
|
702
|
-
* Visits a node to perform normalization on it
|
|
703
|
-
*
|
|
704
|
-
* @param key The key corresponding to the given node
|
|
705
|
-
* @param value The node to be visited
|
|
706
|
-
* @param depth Optional number indicating the maximum recursion depth
|
|
707
|
-
* @param maxProperties Optional maximum number of properties/elements included in any single object/array
|
|
708
|
-
* @param memo Optional Memo class handling decycling
|
|
709
|
-
*/
|
|
710
|
-
function visit(
|
|
711
|
-
key,
|
|
712
|
-
value,
|
|
713
|
-
depth = +Infinity,
|
|
714
|
-
maxProperties = +Infinity,
|
|
715
|
-
memo = memoBuilder(),
|
|
716
|
-
) {
|
|
717
|
-
const [memoize, unmemoize] = memo;
|
|
718
|
-
|
|
719
|
-
// Get the simple cases out of the way first
|
|
720
|
-
if (
|
|
721
|
-
value == null || // this matches null and undefined -> eqeq not eqeqeq
|
|
722
|
-
(['number', 'boolean', 'string'].includes(typeof value) && !isNaN$1(value))
|
|
723
|
-
) {
|
|
724
|
-
return value ;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
const stringified = stringifyValue(key, value);
|
|
728
|
-
|
|
729
|
-
// Anything we could potentially dig into more (objects or arrays) will have come back as `"[object XXXX]"`.
|
|
730
|
-
// Everything else will have already been serialized, so if we don't see that pattern, we're done.
|
|
731
|
-
if (!stringified.startsWith('[object ')) {
|
|
732
|
-
return stringified;
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
// From here on, we can assert that `value` is either an object or an array.
|
|
736
|
-
|
|
737
|
-
// Do not normalize objects that we know have already been normalized. As a general rule, the
|
|
738
|
-
// "__sentry_skip_normalization__" property should only be used sparingly and only should only be set on objects that
|
|
739
|
-
// have already been normalized.
|
|
740
|
-
if ((value )['__sentry_skip_normalization__']) {
|
|
741
|
-
return value ;
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
// We can set `__sentry_override_normalization_depth__` on an object to ensure that from there
|
|
745
|
-
// We keep a certain amount of depth.
|
|
746
|
-
// This should be used sparingly, e.g. we use it for the redux integration to ensure we get a certain amount of state.
|
|
747
|
-
const remainingDepth =
|
|
748
|
-
typeof (value )['__sentry_override_normalization_depth__'] === 'number'
|
|
749
|
-
? ((value )['__sentry_override_normalization_depth__'] )
|
|
750
|
-
: depth;
|
|
751
|
-
|
|
752
|
-
// We're also done if we've reached the max depth
|
|
753
|
-
if (remainingDepth === 0) {
|
|
754
|
-
// At this point we know `serialized` is a string of the form `"[object XXXX]"`. Clean it up so it's just `"[XXXX]"`.
|
|
755
|
-
return stringified.replace('object ', '');
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
// If we've already visited this branch, bail out, as it's circular reference. If not, note that we're seeing it now.
|
|
759
|
-
if (memoize(value)) {
|
|
760
|
-
return '[Circular ~]';
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
// If the value has a `toJSON` method, we call it to extract more information
|
|
764
|
-
const valueWithToJSON = value ;
|
|
765
|
-
if (valueWithToJSON && typeof valueWithToJSON.toJSON === 'function') {
|
|
766
|
-
try {
|
|
767
|
-
const jsonValue = valueWithToJSON.toJSON();
|
|
768
|
-
// We need to normalize the return value of `.toJSON()` in case it has circular references
|
|
769
|
-
return visit('', jsonValue, remainingDepth - 1, maxProperties, memo);
|
|
770
|
-
} catch (err) {
|
|
771
|
-
// pass (The built-in `toJSON` failed, but we can still try to do it ourselves)
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
// At this point we know we either have an object or an array, we haven't seen it before, and we're going to recurse
|
|
776
|
-
// because we haven't yet reached the max depth. Create an accumulator to hold the results of visiting each
|
|
777
|
-
// property/entry, and keep track of the number of items we add to it.
|
|
778
|
-
const normalized = (Array.isArray(value) ? [] : {}) ;
|
|
779
|
-
let numAdded = 0;
|
|
780
|
-
|
|
781
|
-
// Before we begin, convert`Error` and`Event` instances into plain objects, since some of each of their relevant
|
|
782
|
-
// properties are non-enumerable and otherwise would get missed.
|
|
783
|
-
const visitable = convertToPlainObject(value );
|
|
784
|
-
|
|
785
|
-
for (const visitKey in visitable) {
|
|
786
|
-
// Avoid iterating over fields in the prototype if they've somehow been exposed to enumeration.
|
|
787
|
-
if (!Object.prototype.hasOwnProperty.call(visitable, visitKey)) {
|
|
788
|
-
continue;
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
if (numAdded >= maxProperties) {
|
|
792
|
-
normalized[visitKey] = '[MaxProperties ~]';
|
|
793
|
-
break;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
// Recursively visit all the child nodes
|
|
797
|
-
const visitValue = visitable[visitKey];
|
|
798
|
-
normalized[visitKey] = visit(visitKey, visitValue, remainingDepth - 1, maxProperties, memo);
|
|
799
|
-
|
|
800
|
-
numAdded++;
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
// Once we've visited all the branches, remove the parent from memo storage
|
|
804
|
-
unmemoize(value);
|
|
805
|
-
|
|
806
|
-
// Return accumulated values
|
|
807
|
-
return normalized;
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
/* eslint-disable complexity */
|
|
811
|
-
/**
|
|
812
|
-
* Stringify the given value. Handles various known special values and types.
|
|
813
|
-
*
|
|
814
|
-
* Not meant to be used on simple primitives which already have a string representation, as it will, for example, turn
|
|
815
|
-
* the number 1231 into "[Object Number]", nor on `null`, as it will throw.
|
|
816
|
-
*
|
|
817
|
-
* @param value The value to stringify
|
|
818
|
-
* @returns A stringified representation of the given value
|
|
819
|
-
*/
|
|
820
|
-
function stringifyValue(
|
|
821
|
-
key,
|
|
822
|
-
// this type is a tiny bit of a cheat, since this function does handle NaN (which is technically a number), but for
|
|
823
|
-
// our internal use, it'll do
|
|
824
|
-
value,
|
|
825
|
-
) {
|
|
826
|
-
try {
|
|
827
|
-
if (key === 'domain' && value && typeof value === 'object' && (value )._events) {
|
|
828
|
-
return '[Domain]';
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
if (key === 'domainEmitter') {
|
|
832
|
-
return '[DomainEmitter]';
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
// It's safe to use `global`, `window`, and `document` here in this manner, as we are asserting using `typeof` first
|
|
836
|
-
// which won't throw if they are not present.
|
|
837
|
-
|
|
838
|
-
if (typeof global !== 'undefined' && value === global) {
|
|
839
|
-
return '[Global]';
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
// eslint-disable-next-line no-restricted-globals
|
|
843
|
-
if (typeof window !== 'undefined' && value === window) {
|
|
844
|
-
return '[Window]';
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
// eslint-disable-next-line no-restricted-globals
|
|
848
|
-
if (typeof document !== 'undefined' && value === document) {
|
|
849
|
-
return '[Document]';
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
if (isVueViewModel(value)) {
|
|
853
|
-
return '[VueViewModel]';
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
// React's SyntheticEvent thingy
|
|
857
|
-
if (isSyntheticEvent(value)) {
|
|
858
|
-
return '[SyntheticEvent]';
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
if (typeof value === 'number' && value !== value) {
|
|
862
|
-
return '[NaN]';
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
if (typeof value === 'function') {
|
|
866
|
-
return `[Function: ${getFunctionName(value)}]`;
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
if (typeof value === 'symbol') {
|
|
870
|
-
return `[${String(value)}]`;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
// stringified BigInts are indistinguishable from regular numbers, so we need to label them to avoid confusion
|
|
874
|
-
if (typeof value === 'bigint') {
|
|
875
|
-
return `[BigInt: ${String(value)}]`;
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
// Now that we've knocked out all the special cases and the primitives, all we have left are objects. Simply casting
|
|
879
|
-
// them to strings means that instances of classes which haven't defined their `toStringTag` will just come out as
|
|
880
|
-
// `"[object Object]"`. If we instead look at the constructor's name (which is the same as the name of the class),
|
|
881
|
-
// we can make sure that only plain objects come out that way.
|
|
882
|
-
const objName = getConstructorName(value);
|
|
883
|
-
|
|
884
|
-
// Handle HTML Elements
|
|
885
|
-
if (/^HTML(\w*)Element$/.test(objName)) {
|
|
886
|
-
return `[HTMLElement: ${objName}]`;
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
return `[object ${objName}]`;
|
|
890
|
-
} catch (err) {
|
|
891
|
-
return `**non-serializable** (${err})`;
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
/* eslint-enable complexity */
|
|
895
|
-
|
|
896
|
-
function getConstructorName(value) {
|
|
897
|
-
const prototype = Object.getPrototypeOf(value);
|
|
898
|
-
|
|
899
|
-
return prototype ? prototype.constructor.name : 'null prototype';
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
|
903
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
904
|
-
|
|
905
|
-
/** SyncPromise internal states */
|
|
906
|
-
var States; (function (States) {
|
|
907
|
-
/** Pending */
|
|
908
|
-
const PENDING = 0; States[States["PENDING"] = PENDING] = "PENDING";
|
|
909
|
-
/** Resolved / OK */
|
|
910
|
-
const RESOLVED = 1; States[States["RESOLVED"] = RESOLVED] = "RESOLVED";
|
|
911
|
-
/** Rejected / Error */
|
|
912
|
-
const REJECTED = 2; States[States["REJECTED"] = REJECTED] = "REJECTED";
|
|
913
|
-
})(States || (States = {}));
|
|
914
|
-
|
|
915
|
-
/**
|
|
916
|
-
* This code was originally copied from the 'cookie` module at v0.5.0 and was simplified for our use case.
|
|
917
|
-
* https://github.com/jshttp/cookie/blob/a0c84147aab6266bdb3996cf4062e93907c0b0fc/index.js
|
|
918
|
-
* It had the following license:
|
|
919
|
-
*
|
|
920
|
-
* (The MIT License)
|
|
921
|
-
*
|
|
922
|
-
* Copyright (c) 2012-2014 Roman Shtylman <shtylman@gmail.com>
|
|
923
|
-
* Copyright (c) 2015 Douglas Christopher Wilson <doug@somethingdoug.com>
|
|
924
|
-
*
|
|
925
|
-
* Permission is hereby granted, free of charge, to any person obtaining
|
|
926
|
-
* a copy of this software and associated documentation files (the
|
|
927
|
-
* 'Software'), to deal in the Software without restriction, including
|
|
928
|
-
* without limitation the rights to use, copy, modify, merge, publish,
|
|
929
|
-
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
930
|
-
* permit persons to whom the Software is furnished to do so, subject to
|
|
931
|
-
* the following conditions:
|
|
932
|
-
*
|
|
933
|
-
* The above copyright notice and this permission notice shall be
|
|
934
|
-
* included in all copies or substantial portions of the Software.
|
|
935
|
-
*
|
|
936
|
-
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
937
|
-
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
938
|
-
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
939
|
-
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
940
|
-
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
941
|
-
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
942
|
-
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
943
|
-
*/
|
|
944
|
-
|
|
945
|
-
/**
|
|
946
|
-
* Parses a cookie string
|
|
947
|
-
*/
|
|
948
|
-
function parseCookie(str) {
|
|
949
|
-
const obj = {};
|
|
950
|
-
let index = 0;
|
|
951
|
-
|
|
952
|
-
while (index < str.length) {
|
|
953
|
-
const eqIdx = str.indexOf('=', index);
|
|
954
|
-
|
|
955
|
-
// no more cookie pairs
|
|
956
|
-
if (eqIdx === -1) {
|
|
957
|
-
break;
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
let endIdx = str.indexOf(';', index);
|
|
961
|
-
|
|
962
|
-
if (endIdx === -1) {
|
|
963
|
-
endIdx = str.length;
|
|
964
|
-
} else if (endIdx < eqIdx) {
|
|
965
|
-
// backtrack on prior semicolon
|
|
966
|
-
index = str.lastIndexOf(';', eqIdx - 1) + 1;
|
|
967
|
-
continue;
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
const key = str.slice(index, eqIdx).trim();
|
|
971
|
-
|
|
972
|
-
// only assign once
|
|
973
|
-
if (undefined === obj[key]) {
|
|
974
|
-
let val = str.slice(eqIdx + 1, endIdx).trim();
|
|
975
|
-
|
|
976
|
-
// quoted values
|
|
977
|
-
if (val.charCodeAt(0) === 0x22) {
|
|
978
|
-
val = val.slice(1, -1);
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
try {
|
|
982
|
-
obj[key] = val.indexOf('%') !== -1 ? decodeURIComponent(val) : val;
|
|
983
|
-
} catch (e) {
|
|
984
|
-
obj[key] = val;
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
index = endIdx + 1;
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
return obj;
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
/**
|
|
995
|
-
* Strip the query string and fragment off of a given URL or path (if present)
|
|
996
|
-
*
|
|
997
|
-
* @param urlPath Full URL or path, including possible query string and/or fragment
|
|
998
|
-
* @returns URL or path without query string or fragment
|
|
999
|
-
*/
|
|
1000
|
-
function stripUrlQueryAndFragment(urlPath) {
|
|
1001
|
-
// eslint-disable-next-line no-useless-escape
|
|
1002
|
-
return urlPath.split(/[\?#]/, 1)[0];
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
const DEFAULT_INCLUDES = {
|
|
1006
|
-
ip: false,
|
|
1007
|
-
request: true,
|
|
1008
|
-
transaction: true,
|
|
1009
|
-
user: true,
|
|
1010
|
-
};
|
|
1011
|
-
const DEFAULT_REQUEST_INCLUDES = ['cookies', 'data', 'headers', 'method', 'query_string', 'url'];
|
|
1012
|
-
const DEFAULT_USER_INCLUDES = ['id', 'username', 'email'];
|
|
1013
|
-
|
|
1014
|
-
/**
|
|
1015
|
-
* Extracts a complete and parameterized path from the request object and uses it to construct transaction name.
|
|
1016
|
-
* If the parameterized transaction name cannot be extracted, we fall back to the raw URL.
|
|
1017
|
-
*
|
|
1018
|
-
* Additionally, this function determines and returns the transaction name source
|
|
1019
|
-
*
|
|
1020
|
-
* eg. GET /mountpoint/user/:id
|
|
1021
|
-
*
|
|
1022
|
-
* @param req A request object
|
|
1023
|
-
* @param options What to include in the transaction name (method, path, or a custom route name to be
|
|
1024
|
-
* used instead of the request's route)
|
|
1025
|
-
*
|
|
1026
|
-
* @returns A tuple of the fully constructed transaction name [0] and its source [1] (can be either 'route' or 'url')
|
|
1027
|
-
*/
|
|
1028
|
-
function extractPathForTransaction(
|
|
1029
|
-
req,
|
|
1030
|
-
options = {},
|
|
1031
|
-
) {
|
|
1032
|
-
const method = req.method && req.method.toUpperCase();
|
|
1033
|
-
|
|
1034
|
-
let path = '';
|
|
1035
|
-
let source = 'url';
|
|
1036
|
-
|
|
1037
|
-
// Check to see if there's a parameterized route we can use (as there is in Express)
|
|
1038
|
-
if (options.customRoute || req.route) {
|
|
1039
|
-
path = options.customRoute || `${req.baseUrl || ''}${req.route && req.route.path}`;
|
|
1040
|
-
source = 'route';
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
// Otherwise, just take the original URL
|
|
1044
|
-
else if (req.originalUrl || req.url) {
|
|
1045
|
-
path = stripUrlQueryAndFragment(req.originalUrl || req.url || '');
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
let name = '';
|
|
1049
|
-
if (options.method && method) {
|
|
1050
|
-
name += method;
|
|
1051
|
-
}
|
|
1052
|
-
if (options.method && options.path) {
|
|
1053
|
-
name += ' ';
|
|
1054
|
-
}
|
|
1055
|
-
if (options.path && path) {
|
|
1056
|
-
name += path;
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
return [name, source];
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
/** JSDoc */
|
|
1063
|
-
function extractTransaction(req, type) {
|
|
1064
|
-
switch (type) {
|
|
1065
|
-
case 'path': {
|
|
1066
|
-
return extractPathForTransaction(req, { path: true })[0];
|
|
1067
|
-
}
|
|
1068
|
-
case 'handler': {
|
|
1069
|
-
return (req.route && req.route.stack && req.route.stack[0] && req.route.stack[0].name) || '<anonymous>';
|
|
1070
|
-
}
|
|
1071
|
-
case 'methodPath':
|
|
1072
|
-
default: {
|
|
1073
|
-
// if exist _reconstructedRoute return that path instead of route.path
|
|
1074
|
-
const customRoute = req._reconstructedRoute ? req._reconstructedRoute : undefined;
|
|
1075
|
-
return extractPathForTransaction(req, { path: true, method: true, customRoute })[0];
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
/** JSDoc */
|
|
1081
|
-
function extractUserData(
|
|
1082
|
-
user
|
|
1083
|
-
|
|
1084
|
-
,
|
|
1085
|
-
keys,
|
|
1086
|
-
) {
|
|
1087
|
-
const extractedUser = {};
|
|
1088
|
-
const attributes = Array.isArray(keys) ? keys : DEFAULT_USER_INCLUDES;
|
|
1089
|
-
|
|
1090
|
-
attributes.forEach(key => {
|
|
1091
|
-
if (user && key in user) {
|
|
1092
|
-
extractedUser[key] = user[key];
|
|
1093
|
-
}
|
|
1094
|
-
});
|
|
1095
|
-
|
|
1096
|
-
return extractedUser;
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
/**
|
|
1100
|
-
* Normalize data from the request object, accounting for framework differences.
|
|
1101
|
-
*
|
|
1102
|
-
* @param req The request object from which to extract data
|
|
1103
|
-
* @param options.include An optional array of keys to include in the normalized data. Defaults to
|
|
1104
|
-
* DEFAULT_REQUEST_INCLUDES if not provided.
|
|
1105
|
-
* @param options.deps Injected, platform-specific dependencies
|
|
1106
|
-
* @returns An object containing normalized request data
|
|
1107
|
-
*/
|
|
1108
|
-
function extractRequestData(
|
|
1109
|
-
req,
|
|
1110
|
-
options
|
|
1111
|
-
|
|
1112
|
-
,
|
|
1113
|
-
) {
|
|
1114
|
-
const { include = DEFAULT_REQUEST_INCLUDES } = options || {};
|
|
1115
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1116
|
-
const requestData = {};
|
|
1117
|
-
|
|
1118
|
-
// headers:
|
|
1119
|
-
// node, express, koa, nextjs: req.headers
|
|
1120
|
-
const headers = (req.headers || {})
|
|
1121
|
-
|
|
1122
|
-
;
|
|
1123
|
-
// method:
|
|
1124
|
-
// node, express, koa, nextjs: req.method
|
|
1125
|
-
const method = req.method;
|
|
1126
|
-
// host:
|
|
1127
|
-
// express: req.hostname in > 4 and req.host in < 4
|
|
1128
|
-
// koa: req.host
|
|
1129
|
-
// node, nextjs: req.headers.host
|
|
1130
|
-
// Express 4 mistakenly strips off port number from req.host / req.hostname so we can't rely on them
|
|
1131
|
-
// See: https://github.com/expressjs/express/issues/3047#issuecomment-236653223
|
|
1132
|
-
// Also: https://github.com/getsentry/sentry-javascript/issues/1917
|
|
1133
|
-
const host = headers.host || req.hostname || req.host || '<no host>';
|
|
1134
|
-
// protocol:
|
|
1135
|
-
// node, nextjs: <n/a>
|
|
1136
|
-
// express, koa: req.protocol
|
|
1137
|
-
const protocol = req.protocol === 'https' || (req.socket && req.socket.encrypted) ? 'https' : 'http';
|
|
1138
|
-
// url (including path and query string):
|
|
1139
|
-
// node, express: req.originalUrl
|
|
1140
|
-
// koa, nextjs: req.url
|
|
1141
|
-
const originalUrl = req.originalUrl || req.url || '';
|
|
1142
|
-
// absolute url
|
|
1143
|
-
const absoluteUrl = originalUrl.startsWith(protocol) ? originalUrl : `${protocol}://${host}${originalUrl}`;
|
|
1144
|
-
include.forEach(key => {
|
|
1145
|
-
switch (key) {
|
|
1146
|
-
case 'headers': {
|
|
1147
|
-
requestData.headers = headers;
|
|
1148
|
-
|
|
1149
|
-
// Remove the Cookie header in case cookie data should not be included in the event
|
|
1150
|
-
if (!include.includes('cookies')) {
|
|
1151
|
-
delete (requestData.headers ).cookie;
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
break;
|
|
1155
|
-
}
|
|
1156
|
-
case 'method': {
|
|
1157
|
-
requestData.method = method;
|
|
1158
|
-
break;
|
|
1159
|
-
}
|
|
1160
|
-
case 'url': {
|
|
1161
|
-
requestData.url = absoluteUrl;
|
|
1162
|
-
break;
|
|
1163
|
-
}
|
|
1164
|
-
case 'cookies': {
|
|
1165
|
-
// cookies:
|
|
1166
|
-
// node, express, koa: req.headers.cookie
|
|
1167
|
-
// vercel, sails.js, express (w/ cookie middleware), nextjs: req.cookies
|
|
1168
|
-
requestData.cookies =
|
|
1169
|
-
// TODO (v8 / #5257): We're only sending the empty object for backwards compatibility, so the last bit can
|
|
1170
|
-
// come off in v8
|
|
1171
|
-
req.cookies || (headers.cookie && parseCookie(headers.cookie)) || {};
|
|
1172
|
-
break;
|
|
1173
|
-
}
|
|
1174
|
-
case 'query_string': {
|
|
1175
|
-
// query string:
|
|
1176
|
-
// node: req.url (raw)
|
|
1177
|
-
// express, koa, nextjs: req.query
|
|
1178
|
-
requestData.query_string = extractQueryParams(req);
|
|
1179
|
-
break;
|
|
1180
|
-
}
|
|
1181
|
-
case 'data': {
|
|
1182
|
-
if (method === 'GET' || method === 'HEAD') {
|
|
1183
|
-
break;
|
|
1184
|
-
}
|
|
1185
|
-
// body data:
|
|
1186
|
-
// express, koa, nextjs: req.body
|
|
1187
|
-
//
|
|
1188
|
-
// when using node by itself, you have to read the incoming stream(see
|
|
1189
|
-
// https://nodejs.dev/learn/get-http-request-body-data-using-nodejs); if a user is doing that, we can't know
|
|
1190
|
-
// where they're going to store the final result, so they'll have to capture this data themselves
|
|
1191
|
-
if (req.body !== undefined) {
|
|
1192
|
-
requestData.data = isString(req.body) ? req.body : JSON.stringify(normalize(req.body));
|
|
1193
|
-
}
|
|
1194
|
-
break;
|
|
1195
|
-
}
|
|
1196
|
-
default: {
|
|
1197
|
-
if ({}.hasOwnProperty.call(req, key)) {
|
|
1198
|
-
requestData[key] = (req )[key];
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
});
|
|
1203
|
-
|
|
1204
|
-
return requestData;
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
/**
|
|
1208
|
-
* Add data from the given request to the given event
|
|
1209
|
-
*
|
|
1210
|
-
* @param event The event to which the request data will be added
|
|
1211
|
-
* @param req Request object
|
|
1212
|
-
* @param options.include Flags to control what data is included
|
|
1213
|
-
* @param options.deps Injected platform-specific dependencies
|
|
1214
|
-
* @returns The mutated `Event` object
|
|
1215
|
-
*/
|
|
1216
|
-
function addRequestDataToEvent(
|
|
1217
|
-
event,
|
|
1218
|
-
req,
|
|
1219
|
-
options,
|
|
1220
|
-
) {
|
|
1221
|
-
const include = {
|
|
1222
|
-
...DEFAULT_INCLUDES,
|
|
1223
|
-
...(options && options.include),
|
|
1224
|
-
};
|
|
1225
|
-
|
|
1226
|
-
if (include.request) {
|
|
1227
|
-
const extractedRequestData = Array.isArray(include.request)
|
|
1228
|
-
? extractRequestData(req, { include: include.request })
|
|
1229
|
-
: extractRequestData(req);
|
|
1230
|
-
|
|
1231
|
-
event.request = {
|
|
1232
|
-
...event.request,
|
|
1233
|
-
...extractedRequestData,
|
|
1234
|
-
};
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
if (include.user) {
|
|
1238
|
-
const extractedUser = req.user && isPlainObject(req.user) ? extractUserData(req.user, include.user) : {};
|
|
1239
|
-
|
|
1240
|
-
if (Object.keys(extractedUser).length) {
|
|
1241
|
-
event.user = {
|
|
1242
|
-
...event.user,
|
|
1243
|
-
...extractedUser,
|
|
1244
|
-
};
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
// client ip:
|
|
1249
|
-
// node, nextjs: req.socket.remoteAddress
|
|
1250
|
-
// express, koa: req.ip
|
|
1251
|
-
if (include.ip) {
|
|
1252
|
-
const ip = req.ip || (req.socket && req.socket.remoteAddress);
|
|
1253
|
-
if (ip) {
|
|
1254
|
-
event.user = {
|
|
1255
|
-
...event.user,
|
|
1256
|
-
ip_address: ip,
|
|
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
|
-
*/
|
|
1316
|
-
function createUnixTimestampInSecondsFunc() {
|
|
1317
|
-
const { performance } = GLOBAL_OBJ ;
|
|
1318
|
-
if (!performance || !performance.now) {
|
|
1319
|
-
return dateTimestampInSeconds;
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
// Some browser and environments don't have a timeOrigin, so we fallback to
|
|
1323
|
-
// using Date.now() to compute the starting time.
|
|
1324
|
-
const approxStartingTimeOrigin = Date.now() - performance.now();
|
|
1325
|
-
const timeOrigin = performance.timeOrigin == undefined ? approxStartingTimeOrigin : performance.timeOrigin;
|
|
1326
|
-
|
|
1327
|
-
// performance.now() is a monotonic clock, which means it starts at 0 when the process begins. To get the current
|
|
1328
|
-
// wall clock time (actual UNIX timestamp), we need to add the starting time origin and the current time elapsed.
|
|
1329
|
-
//
|
|
1330
|
-
// TODO: This does not account for the case where the monotonic clock that powers performance.now() drifts from the
|
|
1331
|
-
// wall clock time, which causes the returned timestamp to be inaccurate. We should investigate how to detect and
|
|
1332
|
-
// correct for this.
|
|
1333
|
-
// See: https://github.com/getsentry/sentry-javascript/issues/2590
|
|
1334
|
-
// See: https://github.com/mdn/content/issues/4713
|
|
1335
|
-
// See: https://dev.to/noamr/when-a-millisecond-is-not-a-millisecond-3h6
|
|
1336
|
-
return () => {
|
|
1337
|
-
return (timeOrigin + performance.now()) / ONE_SECOND_IN_MS;
|
|
1338
|
-
};
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
/**
|
|
1342
|
-
* Returns a timestamp in seconds since the UNIX epoch using either the Performance or Date APIs, depending on the
|
|
1343
|
-
* availability of the Performance API.
|
|
1344
|
-
*
|
|
1345
|
-
* BUG: Note that because of how browsers implement the Performance API, the clock might stop when the computer is
|
|
1346
|
-
* asleep. This creates a skew between `dateTimestampInSeconds` and `timestampInSeconds`. The
|
|
1347
|
-
* skew can grow to arbitrary amounts like days, weeks or months.
|
|
1348
|
-
* See https://github.com/getsentry/sentry-javascript/issues/2590.
|
|
1349
|
-
*/
|
|
1350
|
-
const timestampInSeconds = createUnixTimestampInSecondsFunc();
|
|
1351
|
-
|
|
1352
|
-
/**
|
|
1353
|
-
* The number of milliseconds since the UNIX epoch. This value is only usable in a browser, and only when the
|
|
1354
|
-
* performance API is available.
|
|
1355
|
-
*/
|
|
1356
|
-
(() => {
|
|
1357
|
-
// Unfortunately browsers may report an inaccurate time origin data, through either performance.timeOrigin or
|
|
1358
|
-
// performance.timing.navigationStart, which results in poor results in performance data. We only treat time origin
|
|
1359
|
-
// data as reliable if they are within a reasonable threshold of the current time.
|
|
1360
|
-
|
|
1361
|
-
const { performance } = GLOBAL_OBJ ;
|
|
1362
|
-
if (!performance || !performance.now) {
|
|
1363
|
-
return undefined;
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
const threshold = 3600 * 1000;
|
|
1367
|
-
const performanceNow = performance.now();
|
|
1368
|
-
const dateNow = Date.now();
|
|
1369
|
-
|
|
1370
|
-
// if timeOrigin isn't available set delta to threshold so it isn't used
|
|
1371
|
-
const timeOriginDelta = performance.timeOrigin
|
|
1372
|
-
? Math.abs(performance.timeOrigin + performanceNow - dateNow)
|
|
1373
|
-
: threshold;
|
|
1374
|
-
const timeOriginIsReliable = timeOriginDelta < threshold;
|
|
1375
|
-
|
|
1376
|
-
// While performance.timing.navigationStart is deprecated in favor of performance.timeOrigin, performance.timeOrigin
|
|
1377
|
-
// is not as widely supported. Namely, performance.timeOrigin is undefined in Safari as of writing.
|
|
1378
|
-
// Also as of writing, performance.timing is not available in Web Workers in mainstream browsers, so it is not always
|
|
1379
|
-
// a valid fallback. In the absence of an initial time provided by the browser, fallback to the current time from the
|
|
1380
|
-
// Date API.
|
|
1381
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
1382
|
-
const navigationStart = performance.timing && performance.timing.navigationStart;
|
|
1383
|
-
const hasNavigationStart = typeof navigationStart === 'number';
|
|
1384
|
-
// if navigationStart isn't available set delta to threshold so it isn't used
|
|
1385
|
-
const navigationStartDelta = hasNavigationStart ? Math.abs(navigationStart + performanceNow - dateNow) : threshold;
|
|
1386
|
-
const navigationStartIsReliable = navigationStartDelta < threshold;
|
|
1387
|
-
|
|
1388
|
-
if (timeOriginIsReliable || navigationStartIsReliable) {
|
|
1389
|
-
// Use the more reliable time origin
|
|
1390
|
-
if (timeOriginDelta <= navigationStartDelta) {
|
|
1391
|
-
return performance.timeOrigin;
|
|
1392
|
-
} else {
|
|
1393
|
-
return navigationStart;
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
return dateNow;
|
|
1397
|
-
})();
|
|
1398
|
-
|
|
1399
|
-
/**
|
|
1400
|
-
* Convenience function to loop through the items and item types of an envelope.
|
|
1401
|
-
* (This function was mostly created because working with envelope types is painful at the moment)
|
|
1402
|
-
*
|
|
1403
|
-
* If the callback returns true, the rest of the items will be skipped.
|
|
1404
|
-
*/
|
|
1405
|
-
function forEachEnvelopeItem(
|
|
1406
|
-
envelope,
|
|
1407
|
-
callback,
|
|
1408
|
-
) {
|
|
1409
|
-
const envelopeItems = envelope[1];
|
|
1410
|
-
|
|
1411
|
-
for (const envelopeItem of envelopeItems) {
|
|
1412
|
-
const envelopeItemType = envelopeItem[0].type;
|
|
1413
|
-
const result = callback(envelopeItem, envelopeItemType);
|
|
1414
|
-
|
|
1415
|
-
if (result) {
|
|
1416
|
-
return true;
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
return false;
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
/**
|
|
1424
|
-
* This serves as a build time flag that will be true by default, but false in non-debug builds or if users replace `__SENTRY_DEBUG__` in their generated code.
|
|
1425
|
-
*
|
|
1426
|
-
* ATTENTION: This constant must never cross package boundaries (i.e. be exported) to guarantee that it can be used for tree shaking.
|
|
1427
|
-
*/
|
|
1428
|
-
const DEBUG_BUILD$1 = (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__);
|
|
1429
|
-
|
|
1430
|
-
/**
|
|
1431
|
-
* @private Private API with no semver guarantees!
|
|
1432
|
-
*
|
|
1433
|
-
* Strategy used to track async context.
|
|
1434
|
-
*/
|
|
1435
|
-
|
|
1436
|
-
/**
|
|
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;
|
|
1447
|
-
}
|
|
1448
|
-
|
|
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__;
|
|
1457
|
-
}
|
|
1458
|
-
|
|
1459
|
-
const DEFAULT_ENVIRONMENT = 'production';
|
|
1460
|
-
|
|
1461
|
-
/**
|
|
1462
|
-
* Creates a new `Session` object by setting certain default parameters. If optional @param context
|
|
1463
|
-
* is passed, the passed properties are applied to the session object.
|
|
1464
|
-
*
|
|
1465
|
-
* @param context (optional) additional properties to be applied to the returned session object
|
|
1466
|
-
*
|
|
1467
|
-
* @returns a new `Session` object
|
|
1468
|
-
*/
|
|
1469
|
-
function makeSession(context) {
|
|
1470
|
-
// Both timestamp and started are in seconds since the UNIX epoch.
|
|
1471
|
-
const startingTime = timestampInSeconds();
|
|
1472
|
-
|
|
1473
|
-
const session = {
|
|
1474
|
-
sid: uuid4(),
|
|
1475
|
-
init: true,
|
|
1476
|
-
timestamp: startingTime,
|
|
1477
|
-
started: startingTime,
|
|
1478
|
-
duration: 0,
|
|
1479
|
-
status: 'ok',
|
|
1480
|
-
errors: 0,
|
|
1481
|
-
ignoreDuration: false,
|
|
1482
|
-
toJSON: () => sessionToJSON(session),
|
|
1483
|
-
};
|
|
1484
|
-
|
|
1485
|
-
if (context) {
|
|
1486
|
-
updateSession(session, context);
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
|
-
return session;
|
|
1490
|
-
}
|
|
1491
|
-
|
|
1492
|
-
/**
|
|
1493
|
-
* Updates a session object with the properties passed in the context.
|
|
1494
|
-
*
|
|
1495
|
-
* Note that this function mutates the passed object and returns void.
|
|
1496
|
-
* (Had to do this instead of returning a new and updated session because closing and sending a session
|
|
1497
|
-
* makes an update to the session after it was passed to the sending logic.
|
|
1498
|
-
* @see BaseClient.captureSession )
|
|
1499
|
-
*
|
|
1500
|
-
* @param session the `Session` to update
|
|
1501
|
-
* @param context the `SessionContext` holding the properties that should be updated in @param session
|
|
1502
|
-
*/
|
|
1503
|
-
// eslint-disable-next-line complexity
|
|
1504
|
-
function updateSession(session, context = {}) {
|
|
1505
|
-
if (context.user) {
|
|
1506
|
-
if (!session.ipAddress && context.user.ip_address) {
|
|
1507
|
-
session.ipAddress = context.user.ip_address;
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
if (!session.did && !context.did) {
|
|
1511
|
-
session.did = context.user.id || context.user.email || context.user.username;
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
|
|
1515
|
-
session.timestamp = context.timestamp || timestampInSeconds();
|
|
1516
|
-
|
|
1517
|
-
if (context.abnormal_mechanism) {
|
|
1518
|
-
session.abnormal_mechanism = context.abnormal_mechanism;
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
if (context.ignoreDuration) {
|
|
1522
|
-
session.ignoreDuration = context.ignoreDuration;
|
|
1523
|
-
}
|
|
1524
|
-
if (context.sid) {
|
|
1525
|
-
// Good enough uuid validation. — Kamil
|
|
1526
|
-
session.sid = context.sid.length === 32 ? context.sid : uuid4();
|
|
1527
|
-
}
|
|
1528
|
-
if (context.init !== undefined) {
|
|
1529
|
-
session.init = context.init;
|
|
1530
|
-
}
|
|
1531
|
-
if (!session.did && context.did) {
|
|
1532
|
-
session.did = `${context.did}`;
|
|
1533
|
-
}
|
|
1534
|
-
if (typeof context.started === 'number') {
|
|
1535
|
-
session.started = context.started;
|
|
1536
|
-
}
|
|
1537
|
-
if (session.ignoreDuration) {
|
|
1538
|
-
session.duration = undefined;
|
|
1539
|
-
} else if (typeof context.duration === 'number') {
|
|
1540
|
-
session.duration = context.duration;
|
|
1541
|
-
} else {
|
|
1542
|
-
const duration = session.timestamp - session.started;
|
|
1543
|
-
session.duration = duration >= 0 ? duration : 0;
|
|
1544
|
-
}
|
|
1545
|
-
if (context.release) {
|
|
1546
|
-
session.release = context.release;
|
|
1547
|
-
}
|
|
1548
|
-
if (context.environment) {
|
|
1549
|
-
session.environment = context.environment;
|
|
1550
|
-
}
|
|
1551
|
-
if (!session.ipAddress && context.ipAddress) {
|
|
1552
|
-
session.ipAddress = context.ipAddress;
|
|
1553
|
-
}
|
|
1554
|
-
if (!session.userAgent && context.userAgent) {
|
|
1555
|
-
session.userAgent = context.userAgent;
|
|
1556
|
-
}
|
|
1557
|
-
if (typeof context.errors === 'number') {
|
|
1558
|
-
session.errors = context.errors;
|
|
1559
|
-
}
|
|
1560
|
-
if (context.status) {
|
|
1561
|
-
session.status = context.status;
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
/**
|
|
1566
|
-
* Closes a session by setting its status and updating the session object with it.
|
|
1567
|
-
* Internally calls `updateSession` to update the passed session object.
|
|
1568
|
-
*
|
|
1569
|
-
* Note that this function mutates the passed session (@see updateSession for explanation).
|
|
1570
|
-
*
|
|
1571
|
-
* @param session the `Session` object to be closed
|
|
1572
|
-
* @param status the `SessionStatus` with which the session was closed. If you don't pass a status,
|
|
1573
|
-
* this function will keep the previously set status, unless it was `'ok'` in which case
|
|
1574
|
-
* it is changed to `'exited'`.
|
|
1575
|
-
*/
|
|
1576
|
-
function closeSession(session, status) {
|
|
1577
|
-
let context = {};
|
|
1578
|
-
if (status) {
|
|
1579
|
-
context = { status };
|
|
1580
|
-
} else if (session.status === 'ok') {
|
|
1581
|
-
context = { status: 'exited' };
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
updateSession(session, context);
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
/**
|
|
1588
|
-
* Serializes a passed session object to a JSON object with a slightly different structure.
|
|
1589
|
-
* This is necessary because the Sentry backend requires a slightly different schema of a session
|
|
1590
|
-
* than the one the JS SDKs use internally.
|
|
1591
|
-
*
|
|
1592
|
-
* @param session the session to be converted
|
|
1593
|
-
*
|
|
1594
|
-
* @returns a JSON object of the passed session
|
|
1595
|
-
*/
|
|
1596
|
-
function sessionToJSON(session) {
|
|
1597
|
-
return dropUndefinedKeys({
|
|
1598
|
-
sid: `${session.sid}`,
|
|
1599
|
-
init: session.init,
|
|
1600
|
-
// Make sure that sec is converted to ms for date constructor
|
|
1601
|
-
started: new Date(session.started * 1000).toISOString(),
|
|
1602
|
-
timestamp: new Date(session.timestamp * 1000).toISOString(),
|
|
1603
|
-
status: session.status,
|
|
1604
|
-
errors: session.errors,
|
|
1605
|
-
did: typeof session.did === 'number' || typeof session.did === 'string' ? `${session.did}` : undefined,
|
|
1606
|
-
duration: session.duration,
|
|
1607
|
-
abnormal_mechanism: session.abnormal_mechanism,
|
|
1608
|
-
attrs: {
|
|
1609
|
-
release: session.release,
|
|
1610
|
-
environment: session.environment,
|
|
1611
|
-
ip_address: session.ipAddress,
|
|
1612
|
-
user_agent: session.userAgent,
|
|
1613
|
-
},
|
|
1614
|
-
});
|
|
1615
|
-
}
|
|
1616
|
-
|
|
1617
|
-
/**
|
|
1618
|
-
* Default value for maximum number of breadcrumbs added to an event.
|
|
1619
|
-
*/
|
|
1620
|
-
const DEFAULT_MAX_BREADCRUMBS = 100;
|
|
1621
|
-
|
|
1622
|
-
/**
|
|
1623
|
-
* Holds additional event information.
|
|
1624
|
-
*/
|
|
1625
|
-
class Scope {
|
|
1626
|
-
/** Flag if notifying is happening. */
|
|
1627
|
-
|
|
1628
|
-
/** Callback for client to receive scope changes. */
|
|
1629
|
-
|
|
1630
|
-
/** Callback list that will be called during event processing. */
|
|
1631
|
-
|
|
1632
|
-
/** Array of breadcrumbs. */
|
|
1633
|
-
|
|
1634
|
-
/** User */
|
|
1635
|
-
|
|
1636
|
-
/** Tags */
|
|
1637
|
-
|
|
1638
|
-
/** Extra */
|
|
1639
|
-
|
|
1640
|
-
/** Contexts */
|
|
1641
|
-
|
|
1642
|
-
/** Attachments */
|
|
1643
|
-
|
|
1644
|
-
/** Propagation Context for distributed tracing */
|
|
1645
|
-
|
|
1646
|
-
/**
|
|
1647
|
-
* A place to stash data which is needed at some point in the SDK's event processing pipeline but which shouldn't get
|
|
1648
|
-
* sent to Sentry
|
|
1649
|
-
*/
|
|
1650
|
-
|
|
1651
|
-
/** Fingerprint */
|
|
1652
|
-
|
|
1653
|
-
/** Severity */
|
|
1654
|
-
|
|
1655
|
-
/**
|
|
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.
|
|
1660
|
-
*/
|
|
1661
|
-
|
|
1662
|
-
/** Span */
|
|
1663
|
-
|
|
1664
|
-
/** Session */
|
|
1665
|
-
|
|
1666
|
-
/** Request Mode Session Status */
|
|
1667
|
-
|
|
1668
|
-
/** The client on this scope */
|
|
1669
|
-
|
|
1670
|
-
// NOTE: Any field which gets added here should get added not only to the constructor but also to the `clone` method.
|
|
1671
|
-
|
|
1672
|
-
constructor() {
|
|
1673
|
-
this._notifyingListeners = false;
|
|
1674
|
-
this._scopeListeners = [];
|
|
1675
|
-
this._eventProcessors = [];
|
|
1676
|
-
this._breadcrumbs = [];
|
|
1677
|
-
this._attachments = [];
|
|
1678
|
-
this._user = {};
|
|
1679
|
-
this._tags = {};
|
|
1680
|
-
this._extra = {};
|
|
1681
|
-
this._contexts = {};
|
|
1682
|
-
this._sdkProcessingMetadata = {};
|
|
1683
|
-
this._propagationContext = generatePropagationContext();
|
|
1684
|
-
}
|
|
1685
|
-
|
|
1686
|
-
/**
|
|
1687
|
-
* Inherit values from the parent scope.
|
|
1688
|
-
* @deprecated Use `scope.clone()` and `new Scope()` instead.
|
|
1689
|
-
*/
|
|
1690
|
-
static clone(scope) {
|
|
1691
|
-
return scope ? scope.clone() : new Scope();
|
|
1692
|
-
}
|
|
1693
|
-
|
|
1694
|
-
/**
|
|
1695
|
-
* @inheritDoc
|
|
1696
|
-
*/
|
|
1697
|
-
clone() {
|
|
1698
|
-
const newScope = new Scope();
|
|
1699
|
-
newScope._breadcrumbs = [...this._breadcrumbs];
|
|
1700
|
-
newScope._tags = { ...this._tags };
|
|
1701
|
-
newScope._extra = { ...this._extra };
|
|
1702
|
-
newScope._contexts = { ...this._contexts };
|
|
1703
|
-
newScope._user = this._user;
|
|
1704
|
-
newScope._level = this._level;
|
|
1705
|
-
newScope._span = this._span;
|
|
1706
|
-
newScope._session = this._session;
|
|
1707
|
-
newScope._transactionName = this._transactionName;
|
|
1708
|
-
newScope._fingerprint = this._fingerprint;
|
|
1709
|
-
newScope._eventProcessors = [...this._eventProcessors];
|
|
1710
|
-
newScope._requestSession = this._requestSession;
|
|
1711
|
-
newScope._attachments = [...this._attachments];
|
|
1712
|
-
newScope._sdkProcessingMetadata = { ...this._sdkProcessingMetadata };
|
|
1713
|
-
newScope._propagationContext = { ...this._propagationContext };
|
|
1714
|
-
newScope._client = this._client;
|
|
1715
|
-
|
|
1716
|
-
return newScope;
|
|
1717
|
-
}
|
|
1718
|
-
|
|
1719
|
-
/**
|
|
1720
|
-
* @inheritDoc
|
|
1721
|
-
*/
|
|
1722
|
-
setClient(client) {
|
|
1723
|
-
this._client = client;
|
|
1724
|
-
}
|
|
1725
|
-
|
|
1726
|
-
/**
|
|
1727
|
-
* @inheritDoc
|
|
1728
|
-
*/
|
|
1729
|
-
getClient() {
|
|
1730
|
-
return this._client ;
|
|
1731
|
-
}
|
|
1732
|
-
|
|
1733
|
-
/**
|
|
1734
|
-
* @inheritDoc
|
|
1735
|
-
*/
|
|
1736
|
-
addScopeListener(callback) {
|
|
1737
|
-
this._scopeListeners.push(callback);
|
|
1738
|
-
}
|
|
1739
|
-
|
|
1740
|
-
/**
|
|
1741
|
-
* @inheritDoc
|
|
1742
|
-
*/
|
|
1743
|
-
addEventProcessor(callback) {
|
|
1744
|
-
this._eventProcessors.push(callback);
|
|
1745
|
-
return this;
|
|
1746
|
-
}
|
|
1747
|
-
|
|
1748
|
-
/**
|
|
1749
|
-
* @inheritDoc
|
|
1750
|
-
*/
|
|
1751
|
-
setUser(user) {
|
|
1752
|
-
// If null is passed we want to unset everything, but still define keys,
|
|
1753
|
-
// so that later down in the pipeline any existing values are cleared.
|
|
1754
|
-
this._user = user || {
|
|
1755
|
-
email: undefined,
|
|
1756
|
-
id: undefined,
|
|
1757
|
-
ip_address: undefined,
|
|
1758
|
-
username: undefined,
|
|
1759
|
-
};
|
|
1760
|
-
|
|
1761
|
-
if (this._session) {
|
|
1762
|
-
updateSession(this._session, { user });
|
|
1763
|
-
}
|
|
1764
|
-
|
|
1765
|
-
this._notifyScopeListeners();
|
|
1766
|
-
return this;
|
|
1767
|
-
}
|
|
1768
|
-
|
|
1769
|
-
/**
|
|
1770
|
-
* @inheritDoc
|
|
1771
|
-
*/
|
|
1772
|
-
getUser() {
|
|
1773
|
-
return this._user;
|
|
1774
|
-
}
|
|
1775
|
-
|
|
1776
|
-
/**
|
|
1777
|
-
* @inheritDoc
|
|
1778
|
-
*/
|
|
1779
|
-
getRequestSession() {
|
|
1780
|
-
return this._requestSession;
|
|
1781
|
-
}
|
|
1782
|
-
|
|
1783
|
-
/**
|
|
1784
|
-
* @inheritDoc
|
|
1785
|
-
*/
|
|
1786
|
-
setRequestSession(requestSession) {
|
|
1787
|
-
this._requestSession = requestSession;
|
|
1788
|
-
return this;
|
|
1789
|
-
}
|
|
1790
|
-
|
|
1791
|
-
/**
|
|
1792
|
-
* @inheritDoc
|
|
1793
|
-
*/
|
|
1794
|
-
setTags(tags) {
|
|
1795
|
-
this._tags = {
|
|
1796
|
-
...this._tags,
|
|
1797
|
-
...tags,
|
|
1798
|
-
};
|
|
1799
|
-
this._notifyScopeListeners();
|
|
1800
|
-
return this;
|
|
1801
|
-
}
|
|
1802
|
-
|
|
1803
|
-
/**
|
|
1804
|
-
* @inheritDoc
|
|
1805
|
-
*/
|
|
1806
|
-
setTag(key, value) {
|
|
1807
|
-
this._tags = { ...this._tags, [key]: value };
|
|
1808
|
-
this._notifyScopeListeners();
|
|
1809
|
-
return this;
|
|
1810
|
-
}
|
|
1811
|
-
|
|
1812
|
-
/**
|
|
1813
|
-
* @inheritDoc
|
|
1814
|
-
*/
|
|
1815
|
-
setExtras(extras) {
|
|
1816
|
-
this._extra = {
|
|
1817
|
-
...this._extra,
|
|
1818
|
-
...extras,
|
|
1819
|
-
};
|
|
1820
|
-
this._notifyScopeListeners();
|
|
1821
|
-
return this;
|
|
1822
|
-
}
|
|
1823
|
-
|
|
1824
|
-
/**
|
|
1825
|
-
* @inheritDoc
|
|
1826
|
-
*/
|
|
1827
|
-
setExtra(key, extra) {
|
|
1828
|
-
this._extra = { ...this._extra, [key]: extra };
|
|
1829
|
-
this._notifyScopeListeners();
|
|
1830
|
-
return this;
|
|
1831
|
-
}
|
|
1832
|
-
|
|
1833
|
-
/**
|
|
1834
|
-
* @inheritDoc
|
|
1835
|
-
*/
|
|
1836
|
-
setFingerprint(fingerprint) {
|
|
1837
|
-
this._fingerprint = fingerprint;
|
|
1838
|
-
this._notifyScopeListeners();
|
|
1839
|
-
return this;
|
|
1840
|
-
}
|
|
1841
|
-
|
|
1842
|
-
/**
|
|
1843
|
-
* @inheritDoc
|
|
1844
|
-
*/
|
|
1845
|
-
setLevel(level) {
|
|
1846
|
-
this._level = level;
|
|
1847
|
-
this._notifyScopeListeners();
|
|
1848
|
-
return this;
|
|
1849
|
-
}
|
|
1850
|
-
|
|
1851
|
-
/**
|
|
1852
|
-
* @inheritDoc
|
|
1853
|
-
*/
|
|
1854
|
-
setTransactionName(name) {
|
|
1855
|
-
this._transactionName = name;
|
|
1856
|
-
this._notifyScopeListeners();
|
|
1857
|
-
return this;
|
|
1858
|
-
}
|
|
1859
|
-
|
|
1860
|
-
/**
|
|
1861
|
-
* @inheritDoc
|
|
1862
|
-
*/
|
|
1863
|
-
setContext(key, context) {
|
|
1864
|
-
if (context === null) {
|
|
1865
|
-
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
1866
|
-
delete this._contexts[key];
|
|
1867
|
-
} else {
|
|
1868
|
-
this._contexts[key] = context;
|
|
1869
|
-
}
|
|
1870
|
-
|
|
1871
|
-
this._notifyScopeListeners();
|
|
1872
|
-
return this;
|
|
1873
|
-
}
|
|
1874
|
-
|
|
1875
|
-
/**
|
|
1876
|
-
* Sets the Span on the scope.
|
|
1877
|
-
* @param span Span
|
|
1878
|
-
* @deprecated Instead of setting a span on a scope, use `startSpan()`/`startSpanManual()` instead.
|
|
1879
|
-
*/
|
|
1880
|
-
setSpan(span) {
|
|
1881
|
-
this._span = span;
|
|
1882
|
-
this._notifyScopeListeners();
|
|
1883
|
-
return this;
|
|
1884
|
-
}
|
|
1885
|
-
|
|
1886
|
-
/**
|
|
1887
|
-
* Returns the `Span` if there is one.
|
|
1888
|
-
* @deprecated Use `getActiveSpan()` instead.
|
|
1889
|
-
*/
|
|
1890
|
-
getSpan() {
|
|
1891
|
-
return this._span;
|
|
1892
|
-
}
|
|
1893
|
-
|
|
1894
|
-
/**
|
|
1895
|
-
* Returns the `Transaction` attached to the scope (if there is one).
|
|
1896
|
-
* @deprecated You should not rely on the transaction, but just use `startSpan()` APIs instead.
|
|
1897
|
-
*/
|
|
1898
|
-
getTransaction() {
|
|
1899
|
-
// Often, this span (if it exists at all) will be a transaction, but it's not guaranteed to be. Regardless, it will
|
|
1900
|
-
// have a pointer to the currently-active transaction.
|
|
1901
|
-
const span = this._span;
|
|
1902
|
-
|
|
1903
|
-
// Cannot replace with getRootSpan because getRootSpan returns a span, not a transaction
|
|
1904
|
-
// Also, this method will be removed anyway.
|
|
1905
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
1906
|
-
if (span && (span ).transaction) {
|
|
1907
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
1908
|
-
return (span ).transaction;
|
|
1909
|
-
}
|
|
1910
|
-
return undefined;
|
|
1911
|
-
}
|
|
1912
|
-
|
|
1913
|
-
/**
|
|
1914
|
-
* @inheritDoc
|
|
1915
|
-
*/
|
|
1916
|
-
setSession(session) {
|
|
1917
|
-
if (!session) {
|
|
1918
|
-
delete this._session;
|
|
1919
|
-
} else {
|
|
1920
|
-
this._session = session;
|
|
1921
|
-
}
|
|
1922
|
-
this._notifyScopeListeners();
|
|
1923
|
-
return this;
|
|
1924
|
-
}
|
|
1925
|
-
|
|
1926
|
-
/**
|
|
1927
|
-
* @inheritDoc
|
|
1928
|
-
*/
|
|
1929
|
-
getSession() {
|
|
1930
|
-
return this._session;
|
|
1931
|
-
}
|
|
1932
|
-
|
|
1933
|
-
/**
|
|
1934
|
-
* @inheritDoc
|
|
1935
|
-
*/
|
|
1936
|
-
update(captureContext) {
|
|
1937
|
-
if (!captureContext) {
|
|
1938
|
-
return this;
|
|
1939
|
-
}
|
|
1940
|
-
|
|
1941
|
-
const scopeToMerge = typeof captureContext === 'function' ? captureContext(this) : captureContext;
|
|
1942
|
-
|
|
1943
|
-
if (scopeToMerge instanceof Scope) {
|
|
1944
|
-
const scopeData = scopeToMerge.getScopeData();
|
|
1945
|
-
|
|
1946
|
-
this._tags = { ...this._tags, ...scopeData.tags };
|
|
1947
|
-
this._extra = { ...this._extra, ...scopeData.extra };
|
|
1948
|
-
this._contexts = { ...this._contexts, ...scopeData.contexts };
|
|
1949
|
-
if (scopeData.user && Object.keys(scopeData.user).length) {
|
|
1950
|
-
this._user = scopeData.user;
|
|
1951
|
-
}
|
|
1952
|
-
if (scopeData.level) {
|
|
1953
|
-
this._level = scopeData.level;
|
|
1954
|
-
}
|
|
1955
|
-
if (scopeData.fingerprint.length) {
|
|
1956
|
-
this._fingerprint = scopeData.fingerprint;
|
|
1957
|
-
}
|
|
1958
|
-
if (scopeToMerge.getRequestSession()) {
|
|
1959
|
-
this._requestSession = scopeToMerge.getRequestSession();
|
|
1960
|
-
}
|
|
1961
|
-
if (scopeData.propagationContext) {
|
|
1962
|
-
this._propagationContext = scopeData.propagationContext;
|
|
1963
|
-
}
|
|
1964
|
-
} else if (isPlainObject(scopeToMerge)) {
|
|
1965
|
-
const scopeContext = captureContext ;
|
|
1966
|
-
this._tags = { ...this._tags, ...scopeContext.tags };
|
|
1967
|
-
this._extra = { ...this._extra, ...scopeContext.extra };
|
|
1968
|
-
this._contexts = { ...this._contexts, ...scopeContext.contexts };
|
|
1969
|
-
if (scopeContext.user) {
|
|
1970
|
-
this._user = scopeContext.user;
|
|
1971
|
-
}
|
|
1972
|
-
if (scopeContext.level) {
|
|
1973
|
-
this._level = scopeContext.level;
|
|
1974
|
-
}
|
|
1975
|
-
if (scopeContext.fingerprint) {
|
|
1976
|
-
this._fingerprint = scopeContext.fingerprint;
|
|
1977
|
-
}
|
|
1978
|
-
if (scopeContext.requestSession) {
|
|
1979
|
-
this._requestSession = scopeContext.requestSession;
|
|
1980
|
-
}
|
|
1981
|
-
if (scopeContext.propagationContext) {
|
|
1982
|
-
this._propagationContext = scopeContext.propagationContext;
|
|
1983
|
-
}
|
|
1984
|
-
}
|
|
1985
|
-
|
|
1986
|
-
return this;
|
|
1987
|
-
}
|
|
1988
|
-
|
|
1989
|
-
/**
|
|
1990
|
-
* @inheritDoc
|
|
1991
|
-
*/
|
|
1992
|
-
clear() {
|
|
1993
|
-
// client is not cleared here on purpose!
|
|
1994
|
-
this._breadcrumbs = [];
|
|
1995
|
-
this._tags = {};
|
|
1996
|
-
this._extra = {};
|
|
1997
|
-
this._user = {};
|
|
1998
|
-
this._contexts = {};
|
|
1999
|
-
this._level = undefined;
|
|
2000
|
-
this._transactionName = undefined;
|
|
2001
|
-
this._fingerprint = undefined;
|
|
2002
|
-
this._requestSession = undefined;
|
|
2003
|
-
this._span = undefined;
|
|
2004
|
-
this._session = undefined;
|
|
2005
|
-
this._notifyScopeListeners();
|
|
2006
|
-
this._attachments = [];
|
|
2007
|
-
this._propagationContext = generatePropagationContext();
|
|
2008
|
-
return this;
|
|
2009
|
-
}
|
|
2010
|
-
|
|
2011
|
-
/**
|
|
2012
|
-
* @inheritDoc
|
|
2013
|
-
*/
|
|
2014
|
-
addBreadcrumb(breadcrumb, maxBreadcrumbs) {
|
|
2015
|
-
const maxCrumbs = typeof maxBreadcrumbs === 'number' ? maxBreadcrumbs : DEFAULT_MAX_BREADCRUMBS;
|
|
2016
|
-
|
|
2017
|
-
// No data has been changed, so don't notify scope listeners
|
|
2018
|
-
if (maxCrumbs <= 0) {
|
|
2019
|
-
return this;
|
|
2020
|
-
}
|
|
2021
|
-
|
|
2022
|
-
const mergedBreadcrumb = {
|
|
2023
|
-
timestamp: dateTimestampInSeconds(),
|
|
2024
|
-
...breadcrumb,
|
|
2025
|
-
};
|
|
2026
|
-
|
|
2027
|
-
const breadcrumbs = this._breadcrumbs;
|
|
2028
|
-
breadcrumbs.push(mergedBreadcrumb);
|
|
2029
|
-
this._breadcrumbs = breadcrumbs.length > maxCrumbs ? breadcrumbs.slice(-maxCrumbs) : breadcrumbs;
|
|
2030
|
-
|
|
2031
|
-
this._notifyScopeListeners();
|
|
2032
|
-
|
|
2033
|
-
return this;
|
|
2034
|
-
}
|
|
2035
|
-
|
|
2036
|
-
/**
|
|
2037
|
-
* @inheritDoc
|
|
2038
|
-
*/
|
|
2039
|
-
getLastBreadcrumb() {
|
|
2040
|
-
return this._breadcrumbs[this._breadcrumbs.length - 1];
|
|
2041
|
-
}
|
|
2042
|
-
|
|
2043
|
-
/**
|
|
2044
|
-
* @inheritDoc
|
|
2045
|
-
*/
|
|
2046
|
-
clearBreadcrumbs() {
|
|
2047
|
-
this._breadcrumbs = [];
|
|
2048
|
-
this._notifyScopeListeners();
|
|
2049
|
-
return this;
|
|
2050
|
-
}
|
|
2051
|
-
|
|
2052
|
-
/**
|
|
2053
|
-
* @inheritDoc
|
|
2054
|
-
*/
|
|
2055
|
-
addAttachment(attachment) {
|
|
2056
|
-
this._attachments.push(attachment);
|
|
2057
|
-
return this;
|
|
2058
|
-
}
|
|
2059
|
-
|
|
2060
|
-
/**
|
|
2061
|
-
* @inheritDoc
|
|
2062
|
-
* @deprecated Use `getScopeData()` instead.
|
|
2063
|
-
*/
|
|
2064
|
-
getAttachments() {
|
|
2065
|
-
const data = this.getScopeData();
|
|
2066
|
-
|
|
2067
|
-
return data.attachments;
|
|
2068
|
-
}
|
|
2069
|
-
|
|
2070
|
-
/**
|
|
2071
|
-
* @inheritDoc
|
|
2072
|
-
*/
|
|
2073
|
-
clearAttachments() {
|
|
2074
|
-
this._attachments = [];
|
|
2075
|
-
return this;
|
|
2076
|
-
}
|
|
2077
|
-
|
|
2078
|
-
/** @inheritDoc */
|
|
2079
|
-
getScopeData() {
|
|
2080
|
-
const {
|
|
2081
|
-
_breadcrumbs,
|
|
2082
|
-
_attachments,
|
|
2083
|
-
_contexts,
|
|
2084
|
-
_tags,
|
|
2085
|
-
_extra,
|
|
2086
|
-
_user,
|
|
2087
|
-
_level,
|
|
2088
|
-
_fingerprint,
|
|
2089
|
-
_eventProcessors,
|
|
2090
|
-
_propagationContext,
|
|
2091
|
-
_sdkProcessingMetadata,
|
|
2092
|
-
_transactionName,
|
|
2093
|
-
_span,
|
|
2094
|
-
} = this;
|
|
2095
|
-
|
|
2096
|
-
return {
|
|
2097
|
-
breadcrumbs: _breadcrumbs,
|
|
2098
|
-
attachments: _attachments,
|
|
2099
|
-
contexts: _contexts,
|
|
2100
|
-
tags: _tags,
|
|
2101
|
-
extra: _extra,
|
|
2102
|
-
user: _user,
|
|
2103
|
-
level: _level,
|
|
2104
|
-
fingerprint: _fingerprint || [],
|
|
2105
|
-
eventProcessors: _eventProcessors,
|
|
2106
|
-
propagationContext: _propagationContext,
|
|
2107
|
-
sdkProcessingMetadata: _sdkProcessingMetadata,
|
|
2108
|
-
transactionName: _transactionName,
|
|
2109
|
-
span: _span,
|
|
2110
|
-
};
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
/**
|
|
2114
|
-
* @inheritDoc
|
|
2115
|
-
*/
|
|
2116
|
-
setSDKProcessingMetadata(newData) {
|
|
2117
|
-
this._sdkProcessingMetadata = { ...this._sdkProcessingMetadata, ...newData };
|
|
2118
|
-
|
|
2119
|
-
return this;
|
|
2120
|
-
}
|
|
2121
|
-
|
|
2122
|
-
/**
|
|
2123
|
-
* @inheritDoc
|
|
2124
|
-
*/
|
|
2125
|
-
setPropagationContext(context) {
|
|
2126
|
-
this._propagationContext = context;
|
|
2127
|
-
return this;
|
|
2128
|
-
}
|
|
2129
|
-
|
|
2130
|
-
/**
|
|
2131
|
-
* @inheritDoc
|
|
2132
|
-
*/
|
|
2133
|
-
getPropagationContext() {
|
|
2134
|
-
return this._propagationContext;
|
|
2135
|
-
}
|
|
2136
|
-
|
|
2137
|
-
/**
|
|
2138
|
-
* @inheritDoc
|
|
2139
|
-
*/
|
|
2140
|
-
captureException(exception, hint) {
|
|
2141
|
-
const eventId = hint && hint.event_id ? hint.event_id : uuid4();
|
|
2142
|
-
|
|
2143
|
-
if (!this._client) {
|
|
2144
|
-
logger.warn('No client configured on scope - will not capture exception!');
|
|
2145
|
-
return eventId;
|
|
2146
|
-
}
|
|
2147
|
-
|
|
2148
|
-
const syntheticException = new Error('Sentry syntheticException');
|
|
2149
|
-
|
|
2150
|
-
this._client.captureException(
|
|
2151
|
-
exception,
|
|
2152
|
-
{
|
|
2153
|
-
originalException: exception,
|
|
2154
|
-
syntheticException,
|
|
2155
|
-
...hint,
|
|
2156
|
-
event_id: eventId,
|
|
2157
|
-
},
|
|
2158
|
-
this,
|
|
2159
|
-
);
|
|
2160
|
-
|
|
2161
|
-
return eventId;
|
|
2162
|
-
}
|
|
2163
|
-
|
|
2164
|
-
/**
|
|
2165
|
-
* @inheritDoc
|
|
2166
|
-
*/
|
|
2167
|
-
captureMessage(message, level, hint) {
|
|
2168
|
-
const eventId = hint && hint.event_id ? hint.event_id : uuid4();
|
|
2169
|
-
|
|
2170
|
-
if (!this._client) {
|
|
2171
|
-
logger.warn('No client configured on scope - will not capture message!');
|
|
2172
|
-
return eventId;
|
|
2173
|
-
}
|
|
2174
|
-
|
|
2175
|
-
const syntheticException = new Error(message);
|
|
2176
|
-
|
|
2177
|
-
this._client.captureMessage(
|
|
2178
|
-
message,
|
|
2179
|
-
level,
|
|
2180
|
-
{
|
|
2181
|
-
originalException: message,
|
|
2182
|
-
syntheticException,
|
|
2183
|
-
...hint,
|
|
2184
|
-
event_id: eventId,
|
|
2185
|
-
},
|
|
2186
|
-
this,
|
|
2187
|
-
);
|
|
2188
|
-
|
|
2189
|
-
return eventId;
|
|
2190
|
-
}
|
|
2191
|
-
|
|
2192
|
-
/**
|
|
2193
|
-
* @inheritDoc
|
|
2194
|
-
*/
|
|
2195
|
-
captureEvent(event, hint) {
|
|
2196
|
-
const eventId = hint && hint.event_id ? hint.event_id : uuid4();
|
|
2197
|
-
|
|
2198
|
-
if (!this._client) {
|
|
2199
|
-
logger.warn('No client configured on scope - will not capture event!');
|
|
2200
|
-
return eventId;
|
|
2201
|
-
}
|
|
2202
|
-
|
|
2203
|
-
this._client.captureEvent(event, { ...hint, event_id: eventId }, this);
|
|
2204
|
-
|
|
2205
|
-
return eventId;
|
|
2206
|
-
}
|
|
2207
|
-
|
|
2208
|
-
/**
|
|
2209
|
-
* This will be called on every set call.
|
|
2210
|
-
*/
|
|
2211
|
-
_notifyScopeListeners() {
|
|
2212
|
-
// We need this check for this._notifyingListeners to be able to work on scope during updates
|
|
2213
|
-
// If this check is not here we'll produce endless recursion when something is done with the scope
|
|
2214
|
-
// during the callback.
|
|
2215
|
-
if (!this._notifyingListeners) {
|
|
2216
|
-
this._notifyingListeners = true;
|
|
2217
|
-
this._scopeListeners.forEach(callback => {
|
|
2218
|
-
callback(this);
|
|
2219
|
-
});
|
|
2220
|
-
this._notifyingListeners = false;
|
|
2221
|
-
}
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
|
|
2225
|
-
function generatePropagationContext() {
|
|
2226
|
-
return {
|
|
2227
|
-
traceId: uuid4(),
|
|
2228
|
-
spanId: uuid4().substring(16),
|
|
2229
|
-
};
|
|
2230
|
-
}
|
|
2231
|
-
|
|
2232
|
-
const SDK_VERSION = '8.0.0-alpha.3';
|
|
2233
|
-
|
|
2234
|
-
/**
|
|
2235
|
-
* API compatibility version of this hub.
|
|
2236
|
-
*
|
|
2237
|
-
* WARNING: This number should only be increased when the global interface
|
|
2238
|
-
* changes and new methods are introduced.
|
|
2239
|
-
*
|
|
2240
|
-
* @hidden
|
|
2241
|
-
*/
|
|
2242
|
-
const API_VERSION = parseFloat(SDK_VERSION);
|
|
2243
|
-
|
|
2244
|
-
/**
|
|
2245
|
-
* Default maximum number of breadcrumbs added to an event. Can be overwritten
|
|
2246
|
-
* with {@link Options.maxBreadcrumbs}.
|
|
2247
|
-
*/
|
|
2248
|
-
const DEFAULT_BREADCRUMBS$1 = 100;
|
|
2249
|
-
|
|
2250
|
-
/**
|
|
2251
|
-
* A layer in the process stack.
|
|
2252
|
-
* @hidden
|
|
2253
|
-
*/
|
|
2254
|
-
|
|
2255
|
-
/**
|
|
2256
|
-
* @inheritDoc
|
|
2257
|
-
*/
|
|
2258
|
-
class Hub {
|
|
2259
|
-
/** Is a {@link Layer}[] containing the client and scope */
|
|
2260
|
-
|
|
2261
|
-
/**
|
|
2262
|
-
* Creates a new instance of the hub, will push one {@link Layer} into the
|
|
2263
|
-
* internal stack on creation.
|
|
2264
|
-
*
|
|
2265
|
-
* @param client bound to the hub.
|
|
2266
|
-
* @param scope bound to the hub.
|
|
2267
|
-
* @param version number, higher number means higher priority.
|
|
2268
|
-
*
|
|
2269
|
-
* @deprecated Instantiation of Hub objects is deprecated and the constructor will be removed in version 8 of the SDK.
|
|
2270
|
-
*
|
|
2271
|
-
* If you are currently using the Hub for multi-client use like so:
|
|
2272
|
-
*
|
|
2273
|
-
* ```
|
|
2274
|
-
* // OLD
|
|
2275
|
-
* const hub = new Hub();
|
|
2276
|
-
* hub.bindClient(client);
|
|
2277
|
-
* makeMain(hub)
|
|
2278
|
-
* ```
|
|
2279
|
-
*
|
|
2280
|
-
* instead initialize the client as follows:
|
|
2281
|
-
*
|
|
2282
|
-
* ```
|
|
2283
|
-
* // NEW
|
|
2284
|
-
* Sentry.withIsolationScope(() => {
|
|
2285
|
-
* Sentry.setCurrentClient(client);
|
|
2286
|
-
* client.init();
|
|
2287
|
-
* });
|
|
2288
|
-
* ```
|
|
2289
|
-
*
|
|
2290
|
-
* If you are using the Hub to capture events like so:
|
|
2291
|
-
*
|
|
2292
|
-
* ```
|
|
2293
|
-
* // OLD
|
|
2294
|
-
* const client = new Client();
|
|
2295
|
-
* const hub = new Hub(client);
|
|
2296
|
-
* hub.captureException()
|
|
2297
|
-
* ```
|
|
2298
|
-
*
|
|
2299
|
-
* instead capture isolated events as follows:
|
|
2300
|
-
*
|
|
2301
|
-
* ```
|
|
2302
|
-
* // NEW
|
|
2303
|
-
* const client = new Client();
|
|
2304
|
-
* const scope = new Scope();
|
|
2305
|
-
* scope.setClient(client);
|
|
2306
|
-
* scope.captureException();
|
|
2307
|
-
* ```
|
|
2308
|
-
*/
|
|
2309
|
-
constructor(
|
|
2310
|
-
client,
|
|
2311
|
-
scope,
|
|
2312
|
-
isolationScope,
|
|
2313
|
-
_version = API_VERSION,
|
|
2314
|
-
) {this._version = _version;
|
|
2315
|
-
let assignedScope;
|
|
2316
|
-
if (!scope) {
|
|
2317
|
-
assignedScope = new Scope();
|
|
2318
|
-
assignedScope.setClient(client);
|
|
2319
|
-
} else {
|
|
2320
|
-
assignedScope = scope;
|
|
2321
|
-
}
|
|
2322
|
-
|
|
2323
|
-
let assignedIsolationScope;
|
|
2324
|
-
if (!isolationScope) {
|
|
2325
|
-
assignedIsolationScope = new Scope();
|
|
2326
|
-
assignedIsolationScope.setClient(client);
|
|
2327
|
-
} else {
|
|
2328
|
-
assignedIsolationScope = isolationScope;
|
|
2329
|
-
}
|
|
2330
|
-
|
|
2331
|
-
this._stack = [{ scope: assignedScope }];
|
|
2332
|
-
|
|
2333
|
-
if (client) {
|
|
2334
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2335
|
-
this.bindClient(client);
|
|
2336
|
-
}
|
|
2337
|
-
|
|
2338
|
-
this._isolationScope = assignedIsolationScope;
|
|
2339
|
-
}
|
|
2340
|
-
|
|
2341
|
-
/**
|
|
2342
|
-
* Checks if this hub's version is older than the given version.
|
|
2343
|
-
*
|
|
2344
|
-
* @param version A version number to compare to.
|
|
2345
|
-
* @return True if the given version is newer; otherwise false.
|
|
2346
|
-
*
|
|
2347
|
-
* @deprecated This will be removed in v8.
|
|
2348
|
-
*/
|
|
2349
|
-
isOlderThan(version) {
|
|
2350
|
-
return this._version < version;
|
|
2351
|
-
}
|
|
2352
|
-
|
|
2353
|
-
/**
|
|
2354
|
-
* This binds the given client to the current scope.
|
|
2355
|
-
* @param client An SDK client (client) instance.
|
|
2356
|
-
*
|
|
2357
|
-
* @deprecated Use `initAndBind()` directly, or `setCurrentClient()` and/or `client.init()` instead.
|
|
2358
|
-
*/
|
|
2359
|
-
bindClient(client) {
|
|
2360
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2361
|
-
const top = this.getStackTop();
|
|
2362
|
-
top.client = client;
|
|
2363
|
-
top.scope.setClient(client);
|
|
2364
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2365
|
-
if (client && client.setupIntegrations) {
|
|
2366
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2367
|
-
client.setupIntegrations();
|
|
2368
|
-
}
|
|
2369
|
-
}
|
|
2370
|
-
|
|
2371
|
-
/**
|
|
2372
|
-
* @inheritDoc
|
|
2373
|
-
*
|
|
2374
|
-
* @deprecated Use `withScope` instead.
|
|
2375
|
-
*/
|
|
2376
|
-
pushScope() {
|
|
2377
|
-
// We want to clone the content of prev scope
|
|
2378
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2379
|
-
const scope = this.getScope().clone();
|
|
2380
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2381
|
-
this.getStack().push({
|
|
2382
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2383
|
-
client: this.getClient(),
|
|
2384
|
-
scope,
|
|
2385
|
-
});
|
|
2386
|
-
return scope;
|
|
2387
|
-
}
|
|
2388
|
-
|
|
2389
|
-
/**
|
|
2390
|
-
* @inheritDoc
|
|
2391
|
-
*
|
|
2392
|
-
* @deprecated Use `withScope` instead.
|
|
2393
|
-
*/
|
|
2394
|
-
popScope() {
|
|
2395
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2396
|
-
if (this.getStack().length <= 1) return false;
|
|
2397
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2398
|
-
return !!this.getStack().pop();
|
|
2399
|
-
}
|
|
2400
|
-
|
|
2401
|
-
/**
|
|
2402
|
-
* @inheritDoc
|
|
2403
|
-
*
|
|
2404
|
-
* @deprecated Use `Sentry.withScope()` instead.
|
|
2405
|
-
*/
|
|
2406
|
-
withScope(callback) {
|
|
2407
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2408
|
-
const scope = this.pushScope();
|
|
2409
|
-
|
|
2410
|
-
let maybePromiseResult;
|
|
2411
|
-
try {
|
|
2412
|
-
maybePromiseResult = callback(scope);
|
|
2413
|
-
} catch (e) {
|
|
2414
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2415
|
-
this.popScope();
|
|
2416
|
-
throw e;
|
|
2417
|
-
}
|
|
2418
|
-
|
|
2419
|
-
if (isThenable(maybePromiseResult)) {
|
|
2420
|
-
// @ts-expect-error - isThenable returns the wrong type
|
|
2421
|
-
return maybePromiseResult.then(
|
|
2422
|
-
res => {
|
|
2423
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2424
|
-
this.popScope();
|
|
2425
|
-
return res;
|
|
2426
|
-
},
|
|
2427
|
-
e => {
|
|
2428
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2429
|
-
this.popScope();
|
|
2430
|
-
throw e;
|
|
2431
|
-
},
|
|
2432
|
-
);
|
|
2433
|
-
}
|
|
2434
|
-
|
|
2435
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2436
|
-
this.popScope();
|
|
2437
|
-
return maybePromiseResult;
|
|
2438
|
-
}
|
|
2439
|
-
|
|
2440
|
-
/**
|
|
2441
|
-
* @inheritDoc
|
|
2442
|
-
*
|
|
2443
|
-
* @deprecated Use `Sentry.getClient()` instead.
|
|
2444
|
-
*/
|
|
2445
|
-
getClient() {
|
|
2446
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2447
|
-
return this.getStackTop().client ;
|
|
2448
|
-
}
|
|
2449
|
-
|
|
2450
|
-
/**
|
|
2451
|
-
* Returns the scope of the top stack.
|
|
2452
|
-
*
|
|
2453
|
-
* @deprecated Use `Sentry.getCurrentScope()` instead.
|
|
2454
|
-
*/
|
|
2455
|
-
getScope() {
|
|
2456
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2457
|
-
return this.getStackTop().scope;
|
|
2458
|
-
}
|
|
2459
|
-
|
|
2460
|
-
/**
|
|
2461
|
-
* @deprecated Use `Sentry.getIsolationScope()` instead.
|
|
2462
|
-
*/
|
|
2463
|
-
getIsolationScope() {
|
|
2464
|
-
return this._isolationScope;
|
|
2465
|
-
}
|
|
2466
|
-
|
|
2467
|
-
/**
|
|
2468
|
-
* Returns the scope stack for domains or the process.
|
|
2469
|
-
* @deprecated This will be removed in v8.
|
|
2470
|
-
*/
|
|
2471
|
-
getStack() {
|
|
2472
|
-
return this._stack;
|
|
2473
|
-
}
|
|
2474
|
-
|
|
2475
|
-
/**
|
|
2476
|
-
* Returns the topmost scope layer in the order domain > local > process.
|
|
2477
|
-
* @deprecated This will be removed in v8.
|
|
2478
|
-
*/
|
|
2479
|
-
getStackTop() {
|
|
2480
|
-
return this._stack[this._stack.length - 1];
|
|
2481
|
-
}
|
|
2482
|
-
|
|
2483
|
-
/**
|
|
2484
|
-
* @inheritDoc
|
|
2485
|
-
*
|
|
2486
|
-
* @deprecated Use `Sentry.captureException()` instead.
|
|
2487
|
-
*/
|
|
2488
|
-
captureException(exception, hint) {
|
|
2489
|
-
const eventId = hint && hint.event_id ? hint.event_id : uuid4();
|
|
2490
|
-
const syntheticException = new Error('Sentry syntheticException');
|
|
2491
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2492
|
-
this.getScope().captureException(exception, {
|
|
2493
|
-
originalException: exception,
|
|
2494
|
-
syntheticException,
|
|
2495
|
-
...hint,
|
|
2496
|
-
event_id: eventId,
|
|
2497
|
-
});
|
|
2498
|
-
|
|
2499
|
-
return eventId;
|
|
2500
|
-
}
|
|
2501
|
-
|
|
2502
|
-
/**
|
|
2503
|
-
* @inheritDoc
|
|
2504
|
-
*
|
|
2505
|
-
* @deprecated Use `Sentry.captureMessage()` instead.
|
|
2506
|
-
*/
|
|
2507
|
-
captureMessage(message, level, hint) {
|
|
2508
|
-
const eventId = hint && hint.event_id ? hint.event_id : uuid4();
|
|
2509
|
-
const syntheticException = new Error(message);
|
|
2510
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2511
|
-
this.getScope().captureMessage(message, level, {
|
|
2512
|
-
originalException: message,
|
|
2513
|
-
syntheticException,
|
|
2514
|
-
...hint,
|
|
2515
|
-
event_id: eventId,
|
|
2516
|
-
});
|
|
2517
|
-
|
|
2518
|
-
return eventId;
|
|
2519
|
-
}
|
|
2520
|
-
|
|
2521
|
-
/**
|
|
2522
|
-
* @inheritDoc
|
|
2523
|
-
*
|
|
2524
|
-
* @deprecated Use `Sentry.captureEvent()` instead.
|
|
2525
|
-
*/
|
|
2526
|
-
captureEvent(event, hint) {
|
|
2527
|
-
const eventId = hint && hint.event_id ? hint.event_id : uuid4();
|
|
2528
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2529
|
-
this.getScope().captureEvent(event, { ...hint, event_id: eventId });
|
|
2530
|
-
return eventId;
|
|
2531
|
-
}
|
|
2532
|
-
|
|
2533
|
-
/**
|
|
2534
|
-
* @inheritDoc
|
|
2535
|
-
*
|
|
2536
|
-
* @deprecated Use `Sentry.addBreadcrumb()` instead.
|
|
2537
|
-
*/
|
|
2538
|
-
addBreadcrumb(breadcrumb, hint) {
|
|
2539
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2540
|
-
const { client } = this.getStackTop();
|
|
2541
|
-
|
|
2542
|
-
if (!client) return;
|
|
2543
|
-
|
|
2544
|
-
const { beforeBreadcrumb = null, maxBreadcrumbs = DEFAULT_BREADCRUMBS$1 } =
|
|
2545
|
-
(client.getOptions && client.getOptions()) || {};
|
|
2546
|
-
|
|
2547
|
-
if (maxBreadcrumbs <= 0) return;
|
|
2548
|
-
|
|
2549
|
-
const timestamp = dateTimestampInSeconds();
|
|
2550
|
-
const mergedBreadcrumb = { timestamp, ...breadcrumb };
|
|
2551
|
-
const finalBreadcrumb = beforeBreadcrumb
|
|
2552
|
-
? (consoleSandbox(() => beforeBreadcrumb(mergedBreadcrumb, hint)) )
|
|
2553
|
-
: mergedBreadcrumb;
|
|
2554
|
-
|
|
2555
|
-
if (finalBreadcrumb === null) return;
|
|
2556
|
-
|
|
2557
|
-
client.emit('beforeAddBreadcrumb', finalBreadcrumb, hint);
|
|
2558
|
-
|
|
2559
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2560
|
-
this.getIsolationScope().addBreadcrumb(finalBreadcrumb, maxBreadcrumbs);
|
|
2561
|
-
}
|
|
2562
|
-
|
|
2563
|
-
/**
|
|
2564
|
-
* @inheritDoc
|
|
2565
|
-
* @deprecated Use `Sentry.setUser()` instead.
|
|
2566
|
-
*/
|
|
2567
|
-
setUser(user) {
|
|
2568
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2569
|
-
this.getIsolationScope().setUser(user);
|
|
2570
|
-
}
|
|
2571
|
-
|
|
2572
|
-
/**
|
|
2573
|
-
* @inheritDoc
|
|
2574
|
-
* @deprecated Use `Sentry.setTags()` instead.
|
|
2575
|
-
*/
|
|
2576
|
-
setTags(tags) {
|
|
2577
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2578
|
-
this.getIsolationScope().setTags(tags);
|
|
2579
|
-
}
|
|
2580
|
-
|
|
2581
|
-
/**
|
|
2582
|
-
* @inheritDoc
|
|
2583
|
-
* @deprecated Use `Sentry.setExtras()` instead.
|
|
2584
|
-
*/
|
|
2585
|
-
setExtras(extras) {
|
|
2586
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2587
|
-
this.getIsolationScope().setExtras(extras);
|
|
2588
|
-
}
|
|
2589
|
-
|
|
2590
|
-
/**
|
|
2591
|
-
* @inheritDoc
|
|
2592
|
-
* @deprecated Use `Sentry.setTag()` instead.
|
|
2593
|
-
*/
|
|
2594
|
-
setTag(key, value) {
|
|
2595
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2596
|
-
this.getIsolationScope().setTag(key, value);
|
|
2597
|
-
}
|
|
2598
|
-
|
|
2599
|
-
/**
|
|
2600
|
-
* @inheritDoc
|
|
2601
|
-
* @deprecated Use `Sentry.setExtra()` instead.
|
|
2602
|
-
*/
|
|
2603
|
-
setExtra(key, extra) {
|
|
2604
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2605
|
-
this.getIsolationScope().setExtra(key, extra);
|
|
2606
|
-
}
|
|
2607
|
-
|
|
2608
|
-
/**
|
|
2609
|
-
* @inheritDoc
|
|
2610
|
-
* @deprecated Use `Sentry.setContext()` instead.
|
|
2611
|
-
*/
|
|
2612
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2613
|
-
setContext(name, context) {
|
|
2614
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2615
|
-
this.getIsolationScope().setContext(name, context);
|
|
2616
|
-
}
|
|
2617
|
-
|
|
2618
|
-
/**
|
|
2619
|
-
* @inheritDoc
|
|
2620
|
-
* @deprecated Use `Sentry.getClient().getIntegrationByName()` instead.
|
|
2621
|
-
*/
|
|
2622
|
-
getIntegration(integration) {
|
|
2623
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2624
|
-
const client = this.getClient();
|
|
2625
|
-
if (!client) return null;
|
|
2626
|
-
try {
|
|
2627
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2628
|
-
return client.getIntegration(integration);
|
|
2629
|
-
} catch (_oO) {
|
|
2630
|
-
DEBUG_BUILD$1 && logger.warn(`Cannot retrieve integration ${integration.id} from the current Hub`);
|
|
2631
|
-
return null;
|
|
2632
|
-
}
|
|
2633
|
-
}
|
|
2634
|
-
|
|
2635
|
-
/**
|
|
2636
|
-
* @inheritDoc
|
|
2637
|
-
*
|
|
2638
|
-
* @deprecated Use top level `captureSession` instead.
|
|
2639
|
-
*/
|
|
2640
|
-
captureSession(endSession = false) {
|
|
2641
|
-
// both send the update and pull the session from the scope
|
|
2642
|
-
if (endSession) {
|
|
2643
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2644
|
-
return this.endSession();
|
|
2645
|
-
}
|
|
2646
|
-
|
|
2647
|
-
// only send the update
|
|
2648
|
-
this._sendSessionUpdate();
|
|
2649
|
-
}
|
|
2650
|
-
|
|
2651
|
-
/**
|
|
2652
|
-
* @inheritDoc
|
|
2653
|
-
* @deprecated Use top level `endSession` instead.
|
|
2654
|
-
*/
|
|
2655
|
-
endSession() {
|
|
2656
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2657
|
-
const layer = this.getStackTop();
|
|
2658
|
-
const scope = layer.scope;
|
|
2659
|
-
const session = scope.getSession();
|
|
2660
|
-
if (session) {
|
|
2661
|
-
closeSession(session);
|
|
2662
|
-
}
|
|
2663
|
-
this._sendSessionUpdate();
|
|
2664
|
-
|
|
2665
|
-
// the session is over; take it off of the scope
|
|
2666
|
-
scope.setSession();
|
|
2667
|
-
}
|
|
2668
|
-
|
|
2669
|
-
/**
|
|
2670
|
-
* @inheritDoc
|
|
2671
|
-
* @deprecated Use top level `startSession` instead.
|
|
2672
|
-
*/
|
|
2673
|
-
startSession(context) {
|
|
2674
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2675
|
-
const { scope, client } = this.getStackTop();
|
|
2676
|
-
const { release, environment = DEFAULT_ENVIRONMENT } = (client && client.getOptions()) || {};
|
|
2677
|
-
|
|
2678
|
-
// Will fetch userAgent if called from browser sdk
|
|
2679
|
-
const { userAgent } = GLOBAL_OBJ.navigator || {};
|
|
2680
|
-
|
|
2681
|
-
const session = makeSession({
|
|
2682
|
-
release,
|
|
2683
|
-
environment,
|
|
2684
|
-
user: scope.getUser(),
|
|
2685
|
-
...(userAgent && { userAgent }),
|
|
2686
|
-
...context,
|
|
2687
|
-
});
|
|
2688
|
-
|
|
2689
|
-
// End existing session if there's one
|
|
2690
|
-
const currentSession = scope.getSession && scope.getSession();
|
|
2691
|
-
if (currentSession && currentSession.status === 'ok') {
|
|
2692
|
-
updateSession(currentSession, { status: 'exited' });
|
|
2693
|
-
}
|
|
2694
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
2695
|
-
this.endSession();
|
|
2696
|
-
|
|
2697
|
-
// Afterwards we set the new session on the scope
|
|
2698
|
-
scope.setSession(session);
|
|
2699
|
-
|
|
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();
|
|
3038
|
-
|
|
3039
|
-
function ensureMetadataStacksAreParsed(parser) {
|
|
3040
|
-
if (!GLOBAL_OBJ._sentryModuleMetadata) {
|
|
3041
|
-
return;
|
|
3042
|
-
}
|
|
3043
|
-
|
|
3044
|
-
for (const stack of Object.keys(GLOBAL_OBJ._sentryModuleMetadata)) {
|
|
3045
|
-
const metadata = GLOBAL_OBJ._sentryModuleMetadata[stack];
|
|
3046
|
-
|
|
3047
|
-
if (parsedStacks.has(stack)) {
|
|
3048
|
-
continue;
|
|
3049
|
-
}
|
|
3050
|
-
|
|
3051
|
-
// Ensure this stack doesn't get parsed again
|
|
3052
|
-
parsedStacks.add(stack);
|
|
3053
|
-
|
|
3054
|
-
const frames = parser(stack);
|
|
3055
|
-
|
|
3056
|
-
// Go through the frames starting from the top of the stack and find the first one with a filename
|
|
3057
|
-
for (const frame of frames.reverse()) {
|
|
3058
|
-
if (frame.filename) {
|
|
3059
|
-
// Save the metadata for this filename
|
|
3060
|
-
filenameMetadataMap.set(frame.filename, metadata);
|
|
3061
|
-
break;
|
|
3062
|
-
}
|
|
3063
|
-
}
|
|
3064
|
-
}
|
|
3065
|
-
}
|
|
3066
|
-
|
|
3067
|
-
/**
|
|
3068
|
-
* Retrieve metadata for a specific JavaScript file URL.
|
|
3069
|
-
*
|
|
3070
|
-
* Metadata is injected by the Sentry bundler plugins using the `_experiments.moduleMetadata` config option.
|
|
3071
|
-
*/
|
|
3072
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3073
|
-
function getMetadataForUrl(parser, filename) {
|
|
3074
|
-
ensureMetadataStacksAreParsed(parser);
|
|
3075
|
-
return filenameMetadataMap.get(filename);
|
|
3076
|
-
}
|
|
3077
|
-
|
|
3078
|
-
/**
|
|
3079
|
-
* Adds metadata to stack frames.
|
|
3080
|
-
*
|
|
3081
|
-
* Metadata is injected by the Sentry bundler plugins using the `_experiments.moduleMetadata` config option.
|
|
3082
|
-
*/
|
|
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
|
-
}
|
|
3106
|
-
}
|
|
3107
|
-
|
|
3108
|
-
/**
|
|
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.
|
|
3161
|
-
*
|
|
3162
|
-
* Metadata can be injected by the Sentry bundler plugins using the `_experiments.moduleMetadata` config option.
|
|
3163
|
-
*
|
|
3164
|
-
* When this integration is added, the metadata passed to the bundler plugin is added to the stack frames of all events
|
|
3165
|
-
* under the `module_metadata` property. This can be used to help in tagging or routing of events from different teams
|
|
3166
|
-
* our sources
|
|
3167
|
-
*
|
|
3168
|
-
* @deprecated Use `moduleMetadataIntegration()` instead.
|
|
3169
|
-
*/
|
|
3170
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
3171
|
-
convertIntegrationFnToClass(
|
|
3172
|
-
INTEGRATION_NAME$a,
|
|
3173
|
-
moduleMetadataIntegration,
|
|
3174
|
-
)
|
|
3175
|
-
|
|
3176
|
-
;
|
|
3177
|
-
|
|
3178
|
-
const DEFAULT_OPTIONS = {
|
|
3179
|
-
include: {
|
|
3180
|
-
cookies: true,
|
|
3181
|
-
data: true,
|
|
3182
|
-
headers: true,
|
|
3183
|
-
ip: false,
|
|
3184
|
-
query_string: true,
|
|
3185
|
-
url: true,
|
|
3186
|
-
user: {
|
|
3187
|
-
id: true,
|
|
3188
|
-
username: true,
|
|
3189
|
-
email: true,
|
|
3190
|
-
},
|
|
3191
|
-
},
|
|
3192
|
-
transactionNamingScheme: 'methodPath' ,
|
|
3193
|
-
};
|
|
3194
|
-
|
|
3195
|
-
const INTEGRATION_NAME$9 = 'RequestData';
|
|
3196
|
-
|
|
3197
|
-
const _requestDataIntegration = ((options = {}) => {
|
|
3198
|
-
const _options = {
|
|
3199
|
-
...DEFAULT_OPTIONS,
|
|
3200
|
-
...options,
|
|
3201
|
-
include: {
|
|
3202
|
-
...DEFAULT_OPTIONS.include,
|
|
3203
|
-
...options.include,
|
|
3204
|
-
user:
|
|
3205
|
-
options.include && typeof options.include.user === 'boolean'
|
|
3206
|
-
? options.include.user
|
|
3207
|
-
: {
|
|
3208
|
-
...DEFAULT_OPTIONS.include.user,
|
|
3209
|
-
// Unclear why TS still thinks `options.include.user` could be a boolean at this point
|
|
3210
|
-
...((options.include || {}).user ),
|
|
3211
|
-
},
|
|
3212
|
-
},
|
|
3213
|
-
};
|
|
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.
|
|
3275
|
-
*/
|
|
3276
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
3277
|
-
convertIntegrationFnToClass(INTEGRATION_NAME$9, requestDataIntegration)
|
|
3278
|
-
|
|
3279
|
-
;
|
|
3280
|
-
|
|
3281
|
-
/** Convert this integration's options to match what `addRequestDataToEvent` expects */
|
|
3282
|
-
/** TODO: Can possibly be deleted once https://github.com/getsentry/sentry-javascript/issues/5718 is fixed */
|
|
3283
|
-
function convertReqDataIntegrationOptsToAddReqDataOpts(
|
|
3284
|
-
integrationOptions,
|
|
3285
|
-
) {
|
|
3286
|
-
const {
|
|
3287
|
-
transactionNamingScheme,
|
|
3288
|
-
include: { ip, user, ...requestOptions },
|
|
3289
|
-
} = integrationOptions;
|
|
3290
|
-
|
|
3291
|
-
const requestIncludeKeys = ['method'];
|
|
3292
|
-
for (const [key, value] of Object.entries(requestOptions)) {
|
|
3293
|
-
if (value) {
|
|
3294
|
-
requestIncludeKeys.push(key);
|
|
3295
|
-
}
|
|
3296
|
-
}
|
|
3297
|
-
|
|
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
|
-
}
|
|
3312
|
-
|
|
3313
|
-
return {
|
|
3314
|
-
include: {
|
|
3315
|
-
ip,
|
|
3316
|
-
user: addReqDataUserOpt,
|
|
3317
|
-
request: requestIncludeKeys.length !== 0 ? requestIncludeKeys : undefined,
|
|
3318
|
-
transaction: transactionNamingScheme,
|
|
3319
|
-
},
|
|
3320
|
-
};
|
|
3321
|
-
}
|
|
3322
|
-
|
|
3323
|
-
function getSDKName(client) {
|
|
3324
|
-
try {
|
|
3325
|
-
// For a long chain like this, it's fewer bytes to combine a try-catch with assuming everything is there than to
|
|
3326
|
-
// write out a long chain of `a && a.b && a.b.c && ...`
|
|
3327
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
3328
|
-
return client.getOptions()._metadata.sdk.name;
|
|
3329
|
-
} catch (err) {
|
|
3330
|
-
// In theory we should never get here
|
|
3331
|
-
return undefined;
|
|
3332
|
-
}
|
|
3333
|
-
}
|
|
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
|
-
|
|
3368
|
-
/**
|
|
3369
|
-
* Patch toString calls to return proper name for wrapped functions.
|
|
3370
|
-
*
|
|
3371
|
-
* ```js
|
|
3372
|
-
* Sentry.init({
|
|
3373
|
-
* integrations: [
|
|
3374
|
-
* functionToStringIntegration(),
|
|
3375
|
-
* ],
|
|
3376
|
-
* });
|
|
3377
|
-
* ```
|
|
3378
|
-
*/
|
|
3379
|
-
const functionToStringIntegration = defineIntegration(_functionToStringIntegration);
|
|
3380
|
-
|
|
3381
|
-
/**
|
|
3382
|
-
* Patch toString calls to return proper name for wrapped functions.
|
|
3383
|
-
*
|
|
3384
|
-
* @deprecated Use `functionToStringIntegration()` instead.
|
|
3385
|
-
*/
|
|
3386
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
3387
|
-
convertIntegrationFnToClass(
|
|
3388
|
-
INTEGRATION_NAME$8,
|
|
3389
|
-
functionToStringIntegration,
|
|
3390
|
-
) ;
|
|
3391
|
-
|
|
3392
|
-
/**
|
|
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.
|
|
3396
|
-
*/
|
|
3397
|
-
const DEBUG_BUILD = (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__);
|
|
3398
|
-
|
|
3399
|
-
const stdlib = detectLibc.familySync();
|
|
3400
|
-
const platform = process.env['BUILD_PLATFORM'] || os.platform();
|
|
3401
|
-
const arch = process.env['BUILD_ARCH'] || os.arch();
|
|
3402
|
-
const abi = nodeAbi.getAbi(process$1.versions.node, 'node');
|
|
3403
|
-
const identifier = [platform, arch, stdlib, abi].filter(c => c !== undefined && c !== null).join('-');
|
|
3404
|
-
const built_from_source_path = path.resolve(__dirname, `./sentry_cpu_profiler-${identifier}`);
|
|
3405
|
-
/**
|
|
3406
|
-
* Imports cpp bindings based on the current platform and architecture.
|
|
3407
|
-
*/
|
|
3408
|
-
// eslint-disable-next-line complexity
|
|
3409
|
-
function importCppBindingsModule() {
|
|
3410
|
-
// If a binary path is specified, use that.
|
|
3411
|
-
if (process$1.env['SENTRY_PROFILER_BINARY_PATH']) {
|
|
3412
|
-
const envPath = process$1.env['SENTRY_PROFILER_BINARY_PATH'];
|
|
3413
|
-
return require(envPath);
|
|
3414
|
-
}
|
|
3415
|
-
// If a user specifies a different binary dir, they are in control of the binaries being moved there
|
|
3416
|
-
if (process$1.env['SENTRY_PROFILER_BINARY_DIR']) {
|
|
3417
|
-
const binaryPath = path.join(path.resolve(process$1.env['SENTRY_PROFILER_BINARY_DIR']), `sentry_cpu_profiler-${identifier}`);
|
|
3418
|
-
return require(`${binaryPath}.node`);
|
|
3419
|
-
}
|
|
3420
|
-
// We need the fallthrough so that in the end, we can fallback to the require dynamice require.
|
|
3421
|
-
// This is for cases where precompiled binaries were not provided, but may have been compiled from source.
|
|
3422
|
-
if (platform === 'darwin') {
|
|
3423
|
-
if (arch === 'x64') {
|
|
3424
|
-
if (abi === '93') {
|
|
3425
|
-
return require('./sentry_cpu_profiler-darwin-x64-93.node');
|
|
3426
|
-
}
|
|
3427
|
-
if (abi === '108') {
|
|
3428
|
-
return require('./sentry_cpu_profiler-darwin-x64-108.node');
|
|
3429
|
-
}
|
|
3430
|
-
if (abi === '115') {
|
|
3431
|
-
return require('./sentry_cpu_profiler-darwin-x64-115.node');
|
|
3432
|
-
}
|
|
3433
|
-
}
|
|
3434
|
-
if (arch === 'arm64') {
|
|
3435
|
-
if (abi === '93') {
|
|
3436
|
-
return require('./sentry_cpu_profiler-darwin-arm64-93.node');
|
|
3437
|
-
}
|
|
3438
|
-
if (abi === '108') {
|
|
3439
|
-
return require('./sentry_cpu_profiler-darwin-arm64-108.node');
|
|
3440
|
-
}
|
|
3441
|
-
if (abi === '115') {
|
|
3442
|
-
return require('./sentry_cpu_profiler-darwin-arm64-115.node');
|
|
3443
|
-
}
|
|
3444
|
-
}
|
|
3445
|
-
}
|
|
3446
|
-
if (platform === 'win32') {
|
|
3447
|
-
if (arch === 'x64') {
|
|
3448
|
-
if (abi === '93') {
|
|
3449
|
-
return require('./sentry_cpu_profiler-win32-x64-93.node');
|
|
3450
|
-
}
|
|
3451
|
-
if (abi === '108') {
|
|
3452
|
-
return require('./sentry_cpu_profiler-win32-x64-108.node');
|
|
3453
|
-
}
|
|
3454
|
-
if (abi === '115') {
|
|
3455
|
-
return require('./sentry_cpu_profiler-win32-x64-115.node');
|
|
3456
|
-
}
|
|
3457
|
-
}
|
|
3458
|
-
}
|
|
3459
|
-
if (platform === 'linux') {
|
|
3460
|
-
if (arch === 'x64') {
|
|
3461
|
-
if (stdlib === 'musl') {
|
|
3462
|
-
if (abi === '93') {
|
|
3463
|
-
return require('./sentry_cpu_profiler-linux-x64-musl-93.node');
|
|
3464
|
-
}
|
|
3465
|
-
if (abi === '108') {
|
|
3466
|
-
return require('./sentry_cpu_profiler-linux-x64-musl-108.node');
|
|
3467
|
-
}
|
|
3468
|
-
if (abi === '115') {
|
|
3469
|
-
return require('./sentry_cpu_profiler-linux-x64-musl-115.node');
|
|
3470
|
-
}
|
|
3471
|
-
}
|
|
3472
|
-
if (stdlib === 'glibc') {
|
|
3473
|
-
if (abi === '93') {
|
|
3474
|
-
return require('./sentry_cpu_profiler-linux-x64-glibc-93.node');
|
|
3475
|
-
}
|
|
3476
|
-
if (abi === '108') {
|
|
3477
|
-
return require('./sentry_cpu_profiler-linux-x64-glibc-108.node');
|
|
3478
|
-
}
|
|
3479
|
-
if (abi === '115') {
|
|
3480
|
-
return require('./sentry_cpu_profiler-linux-x64-glibc-115.node');
|
|
3481
|
-
}
|
|
3482
|
-
}
|
|
3483
|
-
}
|
|
3484
|
-
if (arch === 'arm64') {
|
|
3485
|
-
if (stdlib === 'musl') {
|
|
3486
|
-
if (abi === '93') {
|
|
3487
|
-
return require('./sentry_cpu_profiler-linux-arm64-musl-93.node');
|
|
3488
|
-
}
|
|
3489
|
-
if (abi === '108') {
|
|
3490
|
-
return require('./sentry_cpu_profiler-linux-arm64-musl-108.node');
|
|
3491
|
-
}
|
|
3492
|
-
if (abi === '115') {
|
|
3493
|
-
return require('./sentry_cpu_profiler-linux-arm64-musl-115.node');
|
|
3494
|
-
}
|
|
3495
|
-
}
|
|
3496
|
-
if (stdlib === 'glibc') {
|
|
3497
|
-
if (abi === '93') {
|
|
3498
|
-
return require('./sentry_cpu_profiler-linux-arm64-glibc-93.node');
|
|
3499
|
-
}
|
|
3500
|
-
if (abi === '108') {
|
|
3501
|
-
return require('./sentry_cpu_profiler-linux-arm64-glibc-108.node');
|
|
3502
|
-
}
|
|
3503
|
-
if (abi === '115') {
|
|
3504
|
-
return require('./sentry_cpu_profiler-linux-arm64-glibc-115.node');
|
|
3505
|
-
}
|
|
3506
|
-
}
|
|
3507
|
-
}
|
|
3508
|
-
}
|
|
3509
|
-
return require(`${built_from_source_path}.node`);
|
|
3510
|
-
}
|
|
3511
|
-
const PrivateCpuProfilerBindings = importCppBindingsModule();
|
|
3512
|
-
const CpuProfilerBindings = {
|
|
3513
|
-
startProfiling(name) {
|
|
3514
|
-
if (!PrivateCpuProfilerBindings) {
|
|
3515
|
-
DEBUG_BUILD && logger.log('[Profiling] Bindings not loaded, ignoring call to startProfiling.');
|
|
3516
|
-
return;
|
|
3517
|
-
}
|
|
3518
|
-
return PrivateCpuProfilerBindings.startProfiling(name);
|
|
3519
|
-
},
|
|
3520
|
-
stopProfiling(name) {
|
|
3521
|
-
if (!PrivateCpuProfilerBindings) {
|
|
3522
|
-
DEBUG_BUILD &&
|
|
3523
|
-
logger.log('[Profiling] Bindings not loaded or profile was never started, ignoring call to stopProfiling.');
|
|
3524
|
-
return null;
|
|
3525
|
-
}
|
|
3526
|
-
return PrivateCpuProfilerBindings.stopProfiling(name, worker_threads.threadId, !!GLOBAL_OBJ._sentryDebugIds);
|
|
3527
|
-
},
|
|
3528
|
-
};
|
|
3529
|
-
|
|
3530
|
-
// We require the file because if we import it, it will be included in the bundle.
|
|
3531
|
-
// I guess tsc does not check file contents when it's imported.
|
|
3532
|
-
const THREAD_ID_STRING = String(worker_threads.threadId);
|
|
3533
|
-
const THREAD_NAME = worker_threads.isMainThread ? 'main' : 'worker';
|
|
3534
|
-
const FORMAT_VERSION = '1';
|
|
3535
|
-
// Os machine was backported to 16.18, but this was not reflected in the types
|
|
3536
|
-
// @ts-expect-error ignore missing
|
|
3537
|
-
const machine = typeof os.machine === 'function' ? os.machine() : os.arch();
|
|
3538
|
-
// Machine properties (eval only once)
|
|
3539
|
-
const PLATFORM = os.platform();
|
|
3540
|
-
const RELEASE = os.release();
|
|
3541
|
-
const VERSION = os.version();
|
|
3542
|
-
const TYPE = os.type();
|
|
3543
|
-
const MODEL = machine;
|
|
3544
|
-
const ARCH = os.arch();
|
|
3545
|
-
/**
|
|
3546
|
-
* Checks if the profile is a raw profile or a profile enriched with thread information.
|
|
3547
|
-
* @param {ThreadCpuProfile | RawThreadCpuProfile} profile
|
|
3548
|
-
* @returns {boolean}
|
|
3549
|
-
*/
|
|
3550
|
-
function isRawThreadCpuProfile(profile) {
|
|
3551
|
-
return !('thread_metadata' in profile);
|
|
3552
|
-
}
|
|
3553
|
-
/**
|
|
3554
|
-
* Enriches the profile with threadId of the current thread.
|
|
3555
|
-
* This is done in node as we seem to not be able to get the info from C native code.
|
|
3556
|
-
*
|
|
3557
|
-
* @param {ThreadCpuProfile | RawThreadCpuProfile} profile
|
|
3558
|
-
* @returns {ThreadCpuProfile}
|
|
3559
|
-
*/
|
|
3560
|
-
function enrichWithThreadInformation(profile) {
|
|
3561
|
-
if (!isRawThreadCpuProfile(profile)) {
|
|
3562
|
-
return profile;
|
|
3563
|
-
}
|
|
3564
|
-
return {
|
|
3565
|
-
samples: profile.samples,
|
|
3566
|
-
frames: profile.frames,
|
|
3567
|
-
stacks: profile.stacks,
|
|
3568
|
-
thread_metadata: {
|
|
3569
|
-
[THREAD_ID_STRING]: {
|
|
3570
|
-
name: THREAD_NAME,
|
|
3571
|
-
},
|
|
3572
|
-
},
|
|
3573
|
-
};
|
|
3574
|
-
}
|
|
3575
|
-
/**
|
|
3576
|
-
* Creates a profiling envelope item, if the profile does not pass validation, returns null.
|
|
3577
|
-
* @param {RawThreadCpuProfile}
|
|
3578
|
-
* @param {Event}
|
|
3579
|
-
* @returns {Profile | null}
|
|
3580
|
-
*/
|
|
3581
|
-
function createProfilingEvent(profile, event) {
|
|
3582
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
3583
|
-
if (!isValidProfile(profile)) {
|
|
3584
|
-
return null;
|
|
3585
|
-
}
|
|
3586
|
-
return createProfilePayload(profile, {
|
|
3587
|
-
release: (_a = event.release) !== null && _a !== void 0 ? _a : '',
|
|
3588
|
-
environment: (_b = event.environment) !== null && _b !== void 0 ? _b : '',
|
|
3589
|
-
event_id: (_c = event.event_id) !== null && _c !== void 0 ? _c : '',
|
|
3590
|
-
transaction: (_d = event.transaction) !== null && _d !== void 0 ? _d : '',
|
|
3591
|
-
start_timestamp: event.start_timestamp ? event.start_timestamp * 1000 : Date.now(),
|
|
3592
|
-
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 : '',
|
|
3593
|
-
profile_id: profile.profile_id,
|
|
3594
|
-
});
|
|
3595
|
-
}
|
|
3596
|
-
/**
|
|
3597
|
-
* Create a profile
|
|
3598
|
-
* @param {RawThreadCpuProfile} cpuProfile
|
|
3599
|
-
* @param {options}
|
|
3600
|
-
* @returns {Profile}
|
|
3601
|
-
*/
|
|
3602
|
-
function createProfilePayload(cpuProfile, { release, environment, event_id, transaction, start_timestamp, trace_id, profile_id, }) {
|
|
3603
|
-
// Log a warning if the profile has an invalid traceId (should be uuidv4).
|
|
3604
|
-
// All profiles and transactions are rejected if this is the case and we want to
|
|
3605
|
-
// warn users that this is happening if they enable debug flag
|
|
3606
|
-
if (trace_id && trace_id.length !== 32) {
|
|
3607
|
-
DEBUG_BUILD && logger.log(`[Profiling] Invalid traceId: ${trace_id} on profiled event`);
|
|
3608
|
-
}
|
|
3609
|
-
const enrichedThreadProfile = enrichWithThreadInformation(cpuProfile);
|
|
3610
|
-
const profile = {
|
|
3611
|
-
event_id: profile_id,
|
|
3612
|
-
timestamp: new Date(start_timestamp).toISOString(),
|
|
3613
|
-
platform: 'node',
|
|
3614
|
-
version: FORMAT_VERSION,
|
|
3615
|
-
release: release,
|
|
3616
|
-
environment: environment,
|
|
3617
|
-
measurements: cpuProfile.measurements,
|
|
3618
|
-
runtime: {
|
|
3619
|
-
name: 'node',
|
|
3620
|
-
version: process$1.versions.node || '',
|
|
3621
|
-
},
|
|
3622
|
-
os: {
|
|
3623
|
-
name: PLATFORM,
|
|
3624
|
-
version: RELEASE,
|
|
3625
|
-
build_number: VERSION,
|
|
3626
|
-
},
|
|
3627
|
-
device: {
|
|
3628
|
-
locale: process$1.env['LC_ALL'] || process$1.env['LC_MESSAGES'] || process$1.env['LANG'] || process$1.env['LANGUAGE'] || '',
|
|
3629
|
-
model: MODEL,
|
|
3630
|
-
manufacturer: TYPE,
|
|
3631
|
-
architecture: ARCH,
|
|
3632
|
-
is_emulator: false,
|
|
3633
|
-
},
|
|
3634
|
-
debug_meta: {
|
|
3635
|
-
images: applyDebugMetadata(cpuProfile.resources),
|
|
3636
|
-
},
|
|
3637
|
-
profile: enrichedThreadProfile,
|
|
3638
|
-
transaction: {
|
|
3639
|
-
name: transaction,
|
|
3640
|
-
id: event_id,
|
|
3641
|
-
trace_id: trace_id || '',
|
|
3642
|
-
active_thread_id: THREAD_ID_STRING,
|
|
3643
|
-
},
|
|
3644
|
-
};
|
|
3645
|
-
return profile;
|
|
3646
|
-
}
|
|
3647
|
-
/**
|
|
3648
|
-
* Checks the given sample rate to make sure it is valid type and value (a boolean, or a number between 0 and 1).
|
|
3649
|
-
* @param {unknown} rate
|
|
3650
|
-
* @returns {boolean}
|
|
3651
|
-
*/
|
|
3652
|
-
function isValidSampleRate(rate) {
|
|
3653
|
-
// we need to check NaN explicitly because it's of type 'number' and therefore wouldn't get caught by this typecheck
|
|
3654
|
-
if ((typeof rate !== 'number' && typeof rate !== 'boolean') || (typeof rate === 'number' && isNaN(rate))) {
|
|
3655
|
-
DEBUG_BUILD &&
|
|
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)}.`);
|
|
3657
|
-
return false;
|
|
3658
|
-
}
|
|
3659
|
-
// Boolean sample rates are always valid
|
|
3660
|
-
if (rate === true || rate === false) {
|
|
3661
|
-
return true;
|
|
3662
|
-
}
|
|
3663
|
-
// in case sampleRate is a boolean, it will get automatically cast to 1 if it's true and 0 if it's false
|
|
3664
|
-
if (rate < 0 || rate > 1) {
|
|
3665
|
-
DEBUG_BUILD && logger.warn(`[Profiling] Invalid sample rate. Sample rate must be between 0 and 1. Got ${rate}.`);
|
|
3666
|
-
return false;
|
|
3667
|
-
}
|
|
3668
|
-
return true;
|
|
3669
|
-
}
|
|
3670
|
-
/**
|
|
3671
|
-
* Checks if the profile is valid and can be sent to Sentry.
|
|
3672
|
-
* @param {RawThreadCpuProfile} profile
|
|
3673
|
-
* @returns {boolean}
|
|
3674
|
-
*/
|
|
3675
|
-
function isValidProfile(profile) {
|
|
3676
|
-
if (profile.samples.length <= 1) {
|
|
3677
|
-
DEBUG_BUILD &&
|
|
3678
|
-
// Log a warning if the profile has less than 2 samples so users can know why
|
|
3679
|
-
// they are not seeing any profiling data and we cant avoid the back and forth
|
|
3680
|
-
// of asking them to provide us with a dump of the profile data.
|
|
3681
|
-
logger.log('[Profiling] Discarding profile because it contains less than 2 samples');
|
|
3682
|
-
return false;
|
|
3683
|
-
}
|
|
3684
|
-
if (!profile.profile_id) {
|
|
3685
|
-
return false;
|
|
3686
|
-
}
|
|
3687
|
-
return true;
|
|
3688
|
-
}
|
|
3689
|
-
/**
|
|
3690
|
-
* Adds items to envelope if they are not already present - mutates the envelope.
|
|
3691
|
-
* @param {Envelope} envelope
|
|
3692
|
-
* @param {Profile[]} profiles
|
|
3693
|
-
* @returns {Envelope}
|
|
3694
|
-
*/
|
|
3695
|
-
function addProfilesToEnvelope(envelope, profiles) {
|
|
3696
|
-
if (!profiles.length) {
|
|
3697
|
-
return envelope;
|
|
3698
|
-
}
|
|
3699
|
-
for (const profile of profiles) {
|
|
3700
|
-
// @ts-expect-error untyped envelope
|
|
3701
|
-
envelope[1].push([{ type: 'profile' }, profile]);
|
|
3702
|
-
}
|
|
3703
|
-
return envelope;
|
|
3704
|
-
}
|
|
3705
|
-
/**
|
|
3706
|
-
* Finds transactions with profile_id context in the envelope
|
|
3707
|
-
* @param {Envelope} envelope
|
|
3708
|
-
* @returns {Event[]}
|
|
3709
|
-
*/
|
|
3710
|
-
function findProfiledTransactionsFromEnvelope(envelope) {
|
|
3711
|
-
const events = [];
|
|
3712
|
-
forEachEnvelopeItem(envelope, (item, type) => {
|
|
3713
|
-
var _a, _b;
|
|
3714
|
-
if (type !== 'transaction') {
|
|
3715
|
-
return;
|
|
3716
|
-
}
|
|
3717
|
-
// First item is the type, so we can skip it, everything else is an event
|
|
3718
|
-
for (let j = 1; j < item.length; j++) {
|
|
3719
|
-
const event = item[j];
|
|
3720
|
-
if (!event) {
|
|
3721
|
-
// Shouldnt happen, but lets be safe
|
|
3722
|
-
continue;
|
|
3723
|
-
}
|
|
3724
|
-
// @ts-expect-error profile_id is not part of the metadata type
|
|
3725
|
-
const profile_id = (_b = (_a = event.contexts) === null || _a === void 0 ? void 0 : _a['profile']) === null || _b === void 0 ? void 0 : _b['profile_id'];
|
|
3726
|
-
if (event && profile_id) {
|
|
3727
|
-
events.push(item[j]);
|
|
3728
|
-
}
|
|
3729
|
-
}
|
|
3730
|
-
});
|
|
3731
|
-
return events;
|
|
3732
|
-
}
|
|
3733
|
-
const debugIdStackParserCache = new WeakMap();
|
|
3734
|
-
/**
|
|
3735
|
-
* Cross reference profile collected resources with debug_ids and return a list of debug images.
|
|
3736
|
-
* @param {string[]} resource_paths
|
|
3737
|
-
* @returns {DebugImage[]}
|
|
3738
|
-
*/
|
|
3739
|
-
function applyDebugMetadata(resource_paths) {
|
|
3740
|
-
const debugIdMap = GLOBAL_OBJ._sentryDebugIds;
|
|
3741
|
-
if (!debugIdMap) {
|
|
3742
|
-
return [];
|
|
3743
|
-
}
|
|
3744
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
3745
|
-
const hub = getCurrentHub();
|
|
3746
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
3747
|
-
const client = hub.getClient();
|
|
3748
|
-
const options = client && client.getOptions();
|
|
3749
|
-
if (!options || !options.stackParser) {
|
|
3750
|
-
return [];
|
|
3751
|
-
}
|
|
3752
|
-
let debugIdStackFramesCache;
|
|
3753
|
-
const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(options.stackParser);
|
|
3754
|
-
if (cachedDebugIdStackFrameCache) {
|
|
3755
|
-
debugIdStackFramesCache = cachedDebugIdStackFrameCache;
|
|
3756
|
-
}
|
|
3757
|
-
else {
|
|
3758
|
-
debugIdStackFramesCache = new Map();
|
|
3759
|
-
debugIdStackParserCache.set(options.stackParser, debugIdStackFramesCache);
|
|
3760
|
-
}
|
|
3761
|
-
// Build a map of filename -> debug_id.
|
|
3762
|
-
const filenameDebugIdMap = Object.keys(debugIdMap).reduce((acc, debugIdStackTrace) => {
|
|
3763
|
-
let parsedStack;
|
|
3764
|
-
const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace);
|
|
3765
|
-
if (cachedParsedStack) {
|
|
3766
|
-
parsedStack = cachedParsedStack;
|
|
3767
|
-
}
|
|
3768
|
-
else {
|
|
3769
|
-
parsedStack = options.stackParser(debugIdStackTrace);
|
|
3770
|
-
debugIdStackFramesCache.set(debugIdStackTrace, parsedStack);
|
|
3771
|
-
}
|
|
3772
|
-
for (let i = parsedStack.length - 1; i >= 0; i--) {
|
|
3773
|
-
const stackFrame = parsedStack[i];
|
|
3774
|
-
const file = stackFrame && stackFrame.filename;
|
|
3775
|
-
if (stackFrame && file) {
|
|
3776
|
-
acc[file] = debugIdMap[debugIdStackTrace];
|
|
3777
|
-
break;
|
|
3778
|
-
}
|
|
3779
|
-
}
|
|
3780
|
-
return acc;
|
|
3781
|
-
}, {});
|
|
3782
|
-
const images = [];
|
|
3783
|
-
for (const resource of resource_paths) {
|
|
3784
|
-
if (resource && filenameDebugIdMap[resource]) {
|
|
3785
|
-
images.push({
|
|
3786
|
-
type: 'sourcemap',
|
|
3787
|
-
code_file: resource,
|
|
3788
|
-
debug_id: filenameDebugIdMap[resource],
|
|
3789
|
-
});
|
|
3790
|
-
}
|
|
3791
|
-
}
|
|
3792
|
-
return images;
|
|
3793
|
-
}
|
|
3794
|
-
|
|
3795
|
-
const MAX_PROFILE_DURATION_MS = 30 * 1000;
|
|
3796
|
-
/**
|
|
3797
|
-
* Takes a transaction and determines if it should be profiled or not. If it should be profiled, it returns the
|
|
3798
|
-
* profile_id, otherwise returns undefined. Takes care of setting profile context on transaction as well
|
|
3799
|
-
*/
|
|
3800
|
-
function maybeProfileSpan(client, span, customSamplingContext) {
|
|
3801
|
-
// profilesSampleRate is multiplied with tracesSampleRate to get the final sampling rate. We dont perform
|
|
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)) {
|
|
3805
|
-
return;
|
|
3806
|
-
}
|
|
3807
|
-
// Client and options are required for profiling
|
|
3808
|
-
if (!client) {
|
|
3809
|
-
DEBUG_BUILD && logger.log('[Profiling] Profiling disabled, no client found.');
|
|
3810
|
-
return;
|
|
3811
|
-
}
|
|
3812
|
-
const options = client.getOptions();
|
|
3813
|
-
if (!options) {
|
|
3814
|
-
DEBUG_BUILD && logger.log('[Profiling] Profiling disabled, no options found.');
|
|
3815
|
-
return;
|
|
3816
|
-
}
|
|
3817
|
-
const profilesSampler = options.profilesSampler;
|
|
3818
|
-
let profilesSampleRate = options.profilesSampleRate;
|
|
3819
|
-
// Prefer sampler to sample rate if both are provided.
|
|
3820
|
-
if (typeof profilesSampler === 'function') {
|
|
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));
|
|
3828
|
-
}
|
|
3829
|
-
// Since this is coming from the user (or from a function provided by the user), who knows what we might get. (The
|
|
3830
|
-
// only valid values are booleans or numbers between 0 and 1.)
|
|
3831
|
-
if (!isValidSampleRate(profilesSampleRate)) {
|
|
3832
|
-
DEBUG_BUILD && logger.warn('[Profiling] Discarding profile because of invalid sample rate.');
|
|
3833
|
-
return;
|
|
3834
|
-
}
|
|
3835
|
-
// if the function returned 0 (or false), or if `profileSampleRate` is 0, it's a sign the profile should be dropped
|
|
3836
|
-
if (!profilesSampleRate) {
|
|
3837
|
-
DEBUG_BUILD &&
|
|
3838
|
-
logger.log(`[Profiling] Discarding profile because ${typeof profilesSampler === 'function'
|
|
3839
|
-
? 'profileSampler returned 0 or false'
|
|
3840
|
-
: 'a negative sampling decision was inherited or profileSampleRate is set to 0'}`);
|
|
3841
|
-
return;
|
|
3842
|
-
}
|
|
3843
|
-
// Now we roll the dice. Math.random is inclusive of 0, but not of 1, so strict < is safe here. In case sampleRate is
|
|
3844
|
-
// a boolean, the < comparison will cause it to be automatically cast to 1 if it's true and 0 if it's false.
|
|
3845
|
-
const sampled = profilesSampleRate === true ? true : Math.random() < profilesSampleRate;
|
|
3846
|
-
// Check if we should sample this profile
|
|
3847
|
-
if (!sampled) {
|
|
3848
|
-
DEBUG_BUILD &&
|
|
3849
|
-
logger.log(`[Profiling] Discarding profile because it's not included in the random sample (sampling rate = ${Number(profilesSampleRate)})`);
|
|
3850
|
-
return;
|
|
3851
|
-
}
|
|
3852
|
-
const profile_id = uuid4();
|
|
3853
|
-
CpuProfilerBindings.startProfiling(profile_id);
|
|
3854
|
-
DEBUG_BUILD && logger.log(`[Profiling] started profiling transaction: ${spanToJSON(span).description}`);
|
|
3855
|
-
// set transaction context - do this regardless if profiling fails down the line
|
|
3856
|
-
// so that we can still see the profile_id in the transaction context
|
|
3857
|
-
return profile_id;
|
|
3858
|
-
}
|
|
3859
|
-
/**
|
|
3860
|
-
* Stops the profiler for profile_id and returns the profile
|
|
3861
|
-
* @param transaction
|
|
3862
|
-
* @param profile_id
|
|
3863
|
-
* @returns
|
|
3864
|
-
*/
|
|
3865
|
-
function stopSpanProfile(span, profile_id) {
|
|
3866
|
-
// Should not happen, but satisfy the type checker and be safe regardless.
|
|
3867
|
-
if (!profile_id) {
|
|
3868
|
-
return null;
|
|
3869
|
-
}
|
|
3870
|
-
const profile = CpuProfilerBindings.stopProfiling(profile_id);
|
|
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.
|
|
3873
|
-
if (!profile) {
|
|
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');
|
|
3876
|
-
return null;
|
|
3877
|
-
}
|
|
3878
|
-
// Assign profile_id to the profile
|
|
3879
|
-
profile.profile_id = profile_id;
|
|
3880
|
-
return profile;
|
|
3881
|
-
}
|
|
3882
|
-
|
|
3883
|
-
const MAX_PROFILE_QUEUE_LENGTH = 50;
|
|
3884
|
-
const PROFILE_QUEUE = [];
|
|
3885
|
-
const PROFILE_TIMEOUTS = {};
|
|
3886
|
-
function addToProfileQueue(profile) {
|
|
3887
|
-
PROFILE_QUEUE.push(profile);
|
|
3888
|
-
// We only want to keep the last n profiles in the queue.
|
|
3889
|
-
if (PROFILE_QUEUE.length > MAX_PROFILE_QUEUE_LENGTH) {
|
|
3890
|
-
PROFILE_QUEUE.shift();
|
|
3891
|
-
}
|
|
3892
|
-
}
|
|
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);
|
|
3904
|
-
if (profile_id) {
|
|
3905
|
-
const options = client.getOptions();
|
|
3906
|
-
// Not intended for external use, hence missing types, but we want to profile a couple of things at Sentry that
|
|
3907
|
-
// currently exceed the default timeout set by the SDKs.
|
|
3908
|
-
const maxProfileDurationMs = (options._experiments && options._experiments['maxProfileDurationMs']) || MAX_PROFILE_DURATION_MS;
|
|
3909
|
-
if (PROFILE_TIMEOUTS[profile_id]) {
|
|
3910
|
-
global.clearTimeout(PROFILE_TIMEOUTS[profile_id]);
|
|
3911
|
-
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
3912
|
-
delete PROFILE_TIMEOUTS[profile_id];
|
|
3913
|
-
}
|
|
3914
|
-
// Enqueue a timeout to prevent profiles from running over max duration.
|
|
3915
|
-
PROFILE_TIMEOUTS[profile_id] = global.setTimeout(() => {
|
|
3916
|
-
DEBUG_BUILD &&
|
|
3917
|
-
logger.log('[Profiling] max profile duration elapsed, stopping profiling for:', spanToJSON(span).description);
|
|
3918
|
-
const profile = stopSpanProfile(span, profile_id);
|
|
3919
|
-
if (profile) {
|
|
3920
|
-
addToProfileQueue(profile);
|
|
3921
|
-
}
|
|
3922
|
-
}, maxProfileDurationMs);
|
|
3923
|
-
getCurrentScope().setContext('profile', { profile_id });
|
|
3924
|
-
spanToProfileIdMap.set(span, profile_id);
|
|
3925
|
-
}
|
|
3926
|
-
});
|
|
3927
|
-
client.on('spanEnd', span => {
|
|
3928
|
-
const profile_id = spanToProfileIdMap.get(span);
|
|
3929
|
-
if (profile_id) {
|
|
3930
|
-
if (PROFILE_TIMEOUTS[profile_id]) {
|
|
3931
|
-
global.clearTimeout(PROFILE_TIMEOUTS[profile_id]);
|
|
3932
|
-
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
3933
|
-
delete PROFILE_TIMEOUTS[profile_id];
|
|
3934
|
-
}
|
|
3935
|
-
const profile = stopSpanProfile(span, profile_id);
|
|
3936
|
-
if (profile) {
|
|
3937
|
-
addToProfileQueue(profile);
|
|
3938
|
-
}
|
|
3939
|
-
}
|
|
3940
|
-
});
|
|
3941
|
-
client.on('beforeEnvelope', (envelope) => {
|
|
3942
|
-
var _a, _b;
|
|
3943
|
-
// if not profiles are in queue, there is nothing to add to the envelope.
|
|
3944
|
-
if (!PROFILE_QUEUE.length) {
|
|
3945
|
-
return;
|
|
3946
|
-
}
|
|
3947
|
-
const profiledTransactionEvents = findProfiledTransactionsFromEnvelope(envelope);
|
|
3948
|
-
if (!profiledTransactionEvents.length) {
|
|
3949
|
-
return;
|
|
3950
|
-
}
|
|
3951
|
-
const profilesToAddToEnvelope = [];
|
|
3952
|
-
for (const profiledTransaction of profiledTransactionEvents) {
|
|
3953
|
-
const profileContext = (_a = profiledTransaction.contexts) === null || _a === void 0 ? void 0 : _a['profile'];
|
|
3954
|
-
const profile_id = profileContext === null || profileContext === void 0 ? void 0 : profileContext['profile_id'];
|
|
3955
|
-
if (!profile_id) {
|
|
3956
|
-
throw new TypeError('[Profiling] cannot find profile for a transaction without a profile context');
|
|
3957
|
-
}
|
|
3958
|
-
// Remove the profile from the transaction context before sending, relay will take care of the rest.
|
|
3959
|
-
if (profileContext) {
|
|
3960
|
-
(_b = profiledTransaction.contexts) === null || _b === void 0 ? true : delete _b['profile'];
|
|
3961
|
-
}
|
|
3962
|
-
// We need to find both a profile and a transaction event for the same profile_id.
|
|
3963
|
-
const profileIndex = PROFILE_QUEUE.findIndex(p => p.profile_id === profile_id);
|
|
3964
|
-
if (profileIndex === -1) {
|
|
3965
|
-
DEBUG_BUILD && logger.log(`[Profiling] Could not retrieve profile for transaction: ${profile_id}`);
|
|
3966
|
-
continue;
|
|
3967
|
-
}
|
|
3968
|
-
const cpuProfile = PROFILE_QUEUE[profileIndex];
|
|
3969
|
-
if (!cpuProfile) {
|
|
3970
|
-
DEBUG_BUILD && logger.log(`[Profiling] Could not retrieve profile for transaction: ${profile_id}`);
|
|
3971
|
-
continue;
|
|
3972
|
-
}
|
|
3973
|
-
// Remove the profile from the queue.
|
|
3974
|
-
PROFILE_QUEUE.splice(profileIndex, 1);
|
|
3975
|
-
const profile = createProfilingEvent(cpuProfile, profiledTransaction);
|
|
3976
|
-
if (client.emit && profile) {
|
|
3977
|
-
const integrations = client['_integrations'] && client['_integrations'] !== null && !Array.isArray(client['_integrations'])
|
|
3978
|
-
? Object.keys(client['_integrations'])
|
|
3979
|
-
: undefined;
|
|
3980
|
-
// @ts-expect-error bad overload due to unknown event
|
|
3981
|
-
client.emit('preprocessEvent', profile, {
|
|
3982
|
-
event_id: profiledTransaction.event_id,
|
|
3983
|
-
integrations,
|
|
3984
|
-
});
|
|
3985
|
-
}
|
|
3986
|
-
if (profile) {
|
|
3987
|
-
profilesToAddToEnvelope.push(profile);
|
|
3988
|
-
}
|
|
3989
|
-
}
|
|
3990
|
-
addProfilesToEnvelope(envelope, profilesToAddToEnvelope);
|
|
3991
|
-
});
|
|
3992
|
-
},
|
|
3993
|
-
};
|
|
3994
|
-
});
|
|
3995
|
-
/**
|
|
3996
|
-
* We need this integration in order to send data to Sentry. We hook into the event processor
|
|
3997
|
-
* and inspect each event to see if it is a transaction event and if that transaction event
|
|
3998
|
-
* contains a profile on it's metadata. If that is the case, we create a profiling event envelope
|
|
3999
|
-
* and delete the profile from the transaction metadata.
|
|
4000
|
-
*/
|
|
4001
|
-
const nodeProfilingIntegration = defineIntegration(_nodeProfilingIntegration);
|
|
4002
|
-
|
|
4003
|
-
exports.nodeProfilingIntegration = nodeProfilingIntegration;
|
|
4004
|
-
//# sourceMappingURL=index.js.map
|