rollbar 2.26.3 → 3.0.0-alpha.1
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/.cursor/rules/guidelines.mdc +154 -0
- package/.github/workflows/ci.yml +32 -12
- package/.lgtm.yml +7 -7
- package/.prettierignore +18 -0
- package/.vscode/settings.json +39 -0
- package/CHANGELOG.md +121 -35
- package/CLAUDE.md +201 -0
- package/Gruntfile.js +101 -48
- package/Makefile +3 -3
- package/README.md +2 -4
- package/SECURITY.md +5 -0
- package/babel.config.json +9 -0
- package/bower.json +1 -3
- package/codex.md +148 -0
- package/defaults.js +17 -5
- package/dist/plugins/jquery.min.js +1 -1
- package/dist/rollbar.js +18748 -5375
- package/dist/rollbar.js.map +1 -1
- package/dist/rollbar.min.js +2 -1
- package/dist/rollbar.min.js.LICENSE.txt +1 -0
- package/dist/rollbar.min.js.map +1 -1
- package/dist/rollbar.named-amd.js +19368 -6000
- package/dist/rollbar.named-amd.js.map +1 -1
- package/dist/rollbar.named-amd.min.js +3 -1
- package/dist/rollbar.named-amd.min.js.LICENSE.txt +1 -0
- package/dist/rollbar.named-amd.min.js.map +1 -1
- package/dist/rollbar.noconflict.umd.js +18749 -5380
- package/dist/rollbar.noconflict.umd.js.map +1 -1
- package/dist/rollbar.noconflict.umd.min.js +3 -1
- package/dist/rollbar.noconflict.umd.min.js.LICENSE.txt +1 -0
- package/dist/rollbar.noconflict.umd.min.js.map +1 -1
- package/dist/rollbar.snippet.js +1 -1
- package/dist/rollbar.umd.js +19367 -6000
- package/dist/rollbar.umd.js.map +1 -1
- package/dist/rollbar.umd.min.js +3 -1
- package/dist/rollbar.umd.min.js.LICENSE.txt +1 -0
- package/dist/rollbar.umd.min.js.map +1 -1
- package/docs/extension-exceptions.md +35 -30
- package/docs/migration_v0_to_v1.md +41 -38
- package/eslint.config.mjs +33 -0
- package/get_versions.js +33 -0
- package/index.d.ts +270 -231
- package/karma.conf.js +18 -27
- package/package.json +21 -21
- package/prettier.config.js +7 -0
- package/src/api.js +78 -14
- package/src/apiUtility.js +14 -11
- package/src/browser/core.js +138 -72
- package/src/browser/defaults/scrubFields.js +3 -3
- package/src/browser/detection.js +7 -8
- package/src/browser/domUtility.js +18 -8
- package/src/browser/globalSetup.js +12 -6
- package/src/browser/logger.js +1 -1
- package/src/browser/plugins/jquery.js +35 -35
- package/src/browser/predicates.js +1 -1
- package/src/browser/replay/defaults.js +71 -0
- package/src/browser/replay/recorder.js +193 -0
- package/src/browser/replay/replayMap.js +195 -0
- package/src/browser/rollbar.js +12 -8
- package/src/browser/rollbarWrapper.js +8 -5
- package/src/browser/shim.js +43 -19
- package/src/browser/snippet_callback.js +6 -4
- package/src/browser/telemetry.js +573 -361
- package/src/browser/transforms.js +46 -27
- package/src/browser/transport/fetch.js +26 -14
- package/src/browser/transport/xhr.js +41 -14
- package/src/browser/transport.js +93 -33
- package/src/browser/url.js +16 -8
- package/src/browser/wrapGlobals.js +27 -8
- package/src/defaults.js +3 -3
- package/src/errorParser.js +14 -11
- package/src/merge.js +32 -23
- package/src/notifier.js +16 -13
- package/src/predicates.js +43 -23
- package/src/queue.js +133 -40
- package/src/rateLimiter.js +59 -18
- package/src/react-native/logger.js +1 -1
- package/src/react-native/rollbar.js +59 -55
- package/src/react-native/transforms.js +13 -9
- package/src/react-native/transport.js +44 -34
- package/src/rollbar.js +72 -21
- package/src/scrub.js +0 -1
- package/src/server/locals.js +69 -39
- package/src/server/logger.js +4 -4
- package/src/server/parser.js +72 -47
- package/src/server/rollbar.js +135 -56
- package/src/server/sourceMap/stackTrace.js +33 -18
- package/src/server/telemetry/urlHelpers.js +9 -11
- package/src/server/telemetry.js +68 -45
- package/src/server/transforms.js +37 -21
- package/src/server/transport.js +62 -32
- package/src/telemetry.js +162 -33
- package/src/tracing/context.js +24 -0
- package/src/tracing/contextManager.js +37 -0
- package/src/tracing/defaults.js +7 -0
- package/src/tracing/exporter.js +188 -0
- package/src/tracing/hrtime.js +98 -0
- package/src/tracing/id.js +24 -0
- package/src/tracing/session.js +55 -0
- package/src/tracing/span.js +92 -0
- package/src/tracing/spanProcessor.js +15 -0
- package/src/tracing/tracer.js +46 -0
- package/src/tracing/tracing.js +89 -0
- package/src/transforms.js +33 -21
- package/src/truncation.js +8 -5
- package/src/utility/headers.js +43 -43
- package/src/utility/replace.js +9 -0
- package/src/utility/traverse.js +1 -1
- package/src/utility.js +123 -52
- package/test/api.test.js +88 -41
- package/test/apiUtility.test.js +48 -50
- package/test/browser.core.test.js +142 -141
- package/test/browser.domUtility.test.js +53 -36
- package/test/browser.predicates.test.js +14 -14
- package/test/browser.replay.recorder.test.js +416 -0
- package/test/browser.rollbar.test.js +655 -515
- package/test/browser.telemetry.test.js +46 -39
- package/test/browser.transforms.test.js +164 -139
- package/test/browser.transport.test.js +59 -50
- package/test/browser.url.test.js +13 -12
- package/test/fixtures/locals.fixtures.js +245 -126
- package/test/fixtures/replay/index.js +20 -0
- package/test/fixtures/replay/payloads.fixtures.js +229 -0
- package/test/fixtures/replay/rrwebEvents.fixtures.js +251 -0
- package/test/fixtures/replay/rrwebSyntheticEvents.fixtures.js +328 -0
- package/test/notifier.test.js +91 -79
- package/test/predicates.test.js +261 -215
- package/test/queue.test.js +231 -215
- package/test/rateLimiter.test.js +51 -43
- package/test/react-native.rollbar.test.js +150 -116
- package/test/react-native.transforms.test.js +23 -25
- package/test/react-native.transport.test.js +26 -14
- package/test/replay/index.js +2 -0
- package/test/replay/integration/api.spans.test.js +136 -0
- package/test/replay/integration/e2e.test.js +228 -0
- package/test/replay/integration/index.js +9 -0
- package/test/replay/integration/queue.replayMap.test.js +332 -0
- package/test/replay/integration/replayMap.test.js +163 -0
- package/test/replay/integration/sessionRecording.test.js +390 -0
- package/test/replay/unit/api.postSpans.test.js +150 -0
- package/test/replay/unit/index.js +7 -0
- package/test/replay/unit/queue.replayMap.test.js +225 -0
- package/test/replay/unit/replayMap.test.js +348 -0
- package/test/replay/util/index.js +5 -0
- package/test/replay/util/mockRecordFn.js +80 -0
- package/test/server.lambda.mocha.test.mjs +172 -0
- package/test/server.locals.constructor.mocha.test.mjs +80 -0
- package/test/server.locals.error-handling.mocha.test.mjs +387 -0
- package/test/server.locals.merge.mocha.test.mjs +267 -0
- package/test/server.locals.test-utils.mjs +114 -0
- package/test/server.parser.mocha.test.mjs +87 -0
- package/test/server.predicates.mocha.test.mjs +63 -0
- package/test/server.rollbar.constructor.mocha.test.mjs +199 -0
- package/test/server.rollbar.handlers.mocha.test.mjs +253 -0
- package/test/server.rollbar.logging.mocha.test.mjs +326 -0
- package/test/server.rollbar.misc.mocha.test.mjs +44 -0
- package/test/server.rollbar.test-utils.mjs +57 -0
- package/test/server.telemetry.mocha.test.mjs +377 -0
- package/test/server.transforms.data.mocha.test.mjs +163 -0
- package/test/server.transforms.error.mocha.test.mjs +199 -0
- package/test/server.transforms.request.mocha.test.mjs +208 -0
- package/test/server.transforms.scrub.mocha.test.mjs +140 -0
- package/test/server.transforms.sourcemaps.mocha.test.mjs +122 -0
- package/test/server.transforms.test-utils.mjs +62 -0
- package/test/server.transport.mocha.test.mjs +269 -0
- package/test/telemetry.test.js +178 -38
- package/test/tracing/contextManager.test.js +28 -0
- package/test/tracing/exporter.toPayload.test.js +400 -0
- package/test/tracing/id.test.js +24 -0
- package/test/tracing/span.test.js +183 -0
- package/test/tracing/spanProcessor.test.js +73 -0
- package/test/tracing/tracing.test.js +105 -0
- package/test/transforms.test.js +70 -68
- package/test/truncation.test.js +57 -55
- package/test/utility.test.js +310 -228
- package/webpack.config.js +36 -70
- package/.eslintignore +0 -7
- package/.gitmodules +0 -3
- package/test/server.lambda.test.js +0 -177
- package/test/server.locals.test.js +0 -841
- package/test/server.parser.test.js +0 -72
- package/test/server.predicates.test.js +0 -89
- package/test/server.rollbar.test.js +0 -676
- package/test/server.telemetry.test.js +0 -318
- package/test/server.transforms.test.js +0 -1099
- package/test/server.transport.test.js +0 -201
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
/* globals jQuery */
|
|
2
2
|
/* globals __JQUERY_PLUGIN_VERSION__ */
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
(function(jQuery, window, document) {
|
|
6
|
-
|
|
4
|
+
(function (jQuery, window, document) {
|
|
7
5
|
var rb = window.Rollbar;
|
|
8
6
|
if (!rb) {
|
|
9
7
|
return;
|
|
@@ -16,14 +14,14 @@
|
|
|
16
14
|
notifier: {
|
|
17
15
|
plugins: {
|
|
18
16
|
jquery: {
|
|
19
|
-
version: JQUERY_PLUGIN_VERSION
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
17
|
+
version: JQUERY_PLUGIN_VERSION,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
24
22
|
});
|
|
25
23
|
|
|
26
|
-
var logError = function(e) {
|
|
24
|
+
var logError = function (e) {
|
|
27
25
|
rb.error(e);
|
|
28
26
|
if (window.console) {
|
|
29
27
|
var msg = '[reported to Rollbar]';
|
|
@@ -35,35 +33,37 @@
|
|
|
35
33
|
};
|
|
36
34
|
|
|
37
35
|
// Report any ajax errors to Rollbar
|
|
38
|
-
jQuery(document).ajaxError(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
jQuery(document).ajaxError(
|
|
37
|
+
function (event, jqXHR, ajaxSettings, thrownError) {
|
|
38
|
+
var status = jqXHR.status;
|
|
39
|
+
var url = ajaxSettings.url;
|
|
40
|
+
var type = ajaxSettings.type;
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
// If status === 0 it means the user left the page before the ajax event finished
|
|
43
|
+
// or other uninteresting events.
|
|
44
|
+
if (!status) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
48
|
+
var extra = {
|
|
49
|
+
status: status,
|
|
50
|
+
url: url,
|
|
51
|
+
type: type,
|
|
52
|
+
isAjax: true,
|
|
53
|
+
data: ajaxSettings.data,
|
|
54
|
+
jqXHR_responseText: jqXHR.responseText,
|
|
55
|
+
jqXHR_statusText: jqXHR.statusText,
|
|
56
|
+
};
|
|
57
|
+
var msg = thrownError ? thrownError : 'jQuery ajax error for ' + type;
|
|
58
|
+
rb.warning(msg, extra);
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
61
|
|
|
62
62
|
// Wraps functions passed into jQuery's ready() with try/catch to
|
|
63
63
|
// report errors to Rollbar
|
|
64
64
|
var origReady = jQuery.fn.ready;
|
|
65
|
-
jQuery.fn.ready = function(fn) {
|
|
66
|
-
return origReady.call(this, function($) {
|
|
65
|
+
jQuery.fn.ready = function (fn) {
|
|
66
|
+
return origReady.call(this, function ($) {
|
|
67
67
|
try {
|
|
68
68
|
fn($);
|
|
69
69
|
} catch (e) {
|
|
@@ -75,10 +75,10 @@
|
|
|
75
75
|
// Modified from the code removed from Tracekit in this commit
|
|
76
76
|
// https://github.com/occ/TraceKit/commit/0d39401
|
|
77
77
|
var _oldEventAdd = jQuery.event.add;
|
|
78
|
-
jQuery.event.add = function(elem, types, handler, data, selector) {
|
|
78
|
+
jQuery.event.add = function (elem, types, handler, data, selector) {
|
|
79
79
|
var _handler;
|
|
80
|
-
var wrap = function(fn) {
|
|
81
|
-
return function() {
|
|
80
|
+
var wrap = function (fn) {
|
|
81
|
+
return function () {
|
|
82
82
|
try {
|
|
83
83
|
return fn.apply(this, arguments);
|
|
84
84
|
} catch (e) {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default options for the rrweb recorder
|
|
3
|
+
* See https://github.com/rrweb-io/rrweb/blob/master/guide.md#options for details
|
|
4
|
+
*/
|
|
5
|
+
export default {
|
|
6
|
+
enabled: false, // Whether recording is enabled
|
|
7
|
+
autoStart: true, // Start recording automatically when Rollbar initializes
|
|
8
|
+
maxSeconds: 300, // Maximum recording duration in seconds
|
|
9
|
+
|
|
10
|
+
// trigger options
|
|
11
|
+
triggerOptions: {
|
|
12
|
+
// Trigger replay on specific items (occurrences)
|
|
13
|
+
item: {
|
|
14
|
+
levels: ['error', 'critical'], // Trigger on item level
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
debug: {
|
|
19
|
+
logErrors: true, // Whether to log errors emitted by rrweb.
|
|
20
|
+
logEmits: false, // Whether to log emitted events
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
// Recording options
|
|
24
|
+
inlineStylesheet: true, // Whether to inline stylesheets to improve replay accuracy
|
|
25
|
+
inlineImages: false, // Whether to record the image content
|
|
26
|
+
collectFonts: true, // Whether to collect fonts in the website
|
|
27
|
+
|
|
28
|
+
// Privacy options
|
|
29
|
+
// Fine-grained control over which input types to mask
|
|
30
|
+
// By default only password inputs are masked if maskInputs is true
|
|
31
|
+
maskInputOptions: {
|
|
32
|
+
password: true,
|
|
33
|
+
email: false,
|
|
34
|
+
tel: false,
|
|
35
|
+
text: false,
|
|
36
|
+
color: false,
|
|
37
|
+
date: false,
|
|
38
|
+
'datetime-local': false,
|
|
39
|
+
month: false,
|
|
40
|
+
number: false,
|
|
41
|
+
range: false,
|
|
42
|
+
search: false,
|
|
43
|
+
time: false,
|
|
44
|
+
url: false,
|
|
45
|
+
week: false,
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
// Remove unnecessary parts of the DOM
|
|
49
|
+
// By default all removable elements are removed
|
|
50
|
+
slimDOMOptions: {
|
|
51
|
+
script: true, // Remove script elements
|
|
52
|
+
comment: true, // Remove comments
|
|
53
|
+
headFavicon: true, // Remove favicons in the head
|
|
54
|
+
headWhitespace: true, // Remove whitespace in head
|
|
55
|
+
headMetaDescKeywords: true, // Remove meta description and keywords
|
|
56
|
+
headMetaSocial: true, // Remove social media meta tags
|
|
57
|
+
headMetaRobots: true, // Remove robots meta directives
|
|
58
|
+
headMetaHttpEquiv: true, // Remove http-equiv meta directives
|
|
59
|
+
headMetaAuthorship: true, // Remove authorship meta directives
|
|
60
|
+
headMetaVerification: true, // Remove verification meta directives
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
// Custom callbacks for advanced use cases
|
|
64
|
+
// These are undefined by default and can be set programmatically
|
|
65
|
+
// maskInputFn: undefined, // Custom function to mask input values
|
|
66
|
+
// maskTextFn: undefined, // Custom function to mask text content
|
|
67
|
+
// errorHandler: undefined, // Custom error handler for recording errors
|
|
68
|
+
|
|
69
|
+
// Plugin system
|
|
70
|
+
// plugins: [] // List of plugins to use (must be set programmatically)
|
|
71
|
+
};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { record as rrwebRecordFn } from '@rrweb/record';
|
|
2
|
+
import { EventType } from '@rrweb/types';
|
|
3
|
+
|
|
4
|
+
import hrtime from '../../tracing/hrtime.js';
|
|
5
|
+
import logger from '../logger.js';
|
|
6
|
+
|
|
7
|
+
export default class Recorder {
|
|
8
|
+
#options;
|
|
9
|
+
#rrwebOptions;
|
|
10
|
+
#stopFn = null;
|
|
11
|
+
#recordFn;
|
|
12
|
+
#events = {
|
|
13
|
+
previous: [],
|
|
14
|
+
current: [],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates a new Recorder instance for capturing DOM events
|
|
19
|
+
*
|
|
20
|
+
* @param {Object} options - Configuration options for the recorder
|
|
21
|
+
* @param {Function} [recordFn=rrwebRecordFn] - The recording function to use
|
|
22
|
+
*/
|
|
23
|
+
constructor(options, recordFn = rrwebRecordFn) {
|
|
24
|
+
if (!recordFn) {
|
|
25
|
+
throw new TypeError("Expected 'recordFn' to be provided");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
this.options = options;
|
|
29
|
+
this.#recordFn = recordFn;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get isRecording() {
|
|
33
|
+
return this.#stopFn !== null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get options() {
|
|
37
|
+
return this.#options;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
set options(newOptions) {
|
|
41
|
+
this.configure(newOptions);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
configure(newOptions) {
|
|
45
|
+
const {
|
|
46
|
+
// Rollbar options
|
|
47
|
+
enabled,
|
|
48
|
+
autoStart,
|
|
49
|
+
maxSeconds,
|
|
50
|
+
triggerOptions,
|
|
51
|
+
|
|
52
|
+
// disallowed rrweb options
|
|
53
|
+
emit,
|
|
54
|
+
checkoutEveryNms,
|
|
55
|
+
|
|
56
|
+
// rrweb options
|
|
57
|
+
...rrwebOptions
|
|
58
|
+
} = newOptions;
|
|
59
|
+
this.#options = { enabled, autoStart, maxSeconds, triggerOptions };
|
|
60
|
+
this.#rrwebOptions = rrwebOptions;
|
|
61
|
+
|
|
62
|
+
if (this.isRecording && newOptions.enabled === false) {
|
|
63
|
+
this.stop();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
checkoutEveryNms() {
|
|
68
|
+
// Recording may be up to two checkout intervals, therefore the checkout
|
|
69
|
+
// interval is set to half of the maxSeconds.
|
|
70
|
+
return (this.options.maxSeconds || 10) * 1000 / 2;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Converts recorded events into a formatted payload ready for transport.
|
|
75
|
+
*
|
|
76
|
+
* This method takes the recorder's stored events, creates a new span with the
|
|
77
|
+
* provided tracing context, attaches all events with their timestamps as span
|
|
78
|
+
* events, and then returns a payload ready for transport to the server.
|
|
79
|
+
*
|
|
80
|
+
* @param {Object} tracing - The tracing system instance to create spans
|
|
81
|
+
* @param {string} replayId - Unique identifier to associate with this replay recording
|
|
82
|
+
* @returns {Object|null} A formatted payload containing spans data in OTLP format, or null if no events exist
|
|
83
|
+
*/
|
|
84
|
+
dump(tracing, replayId, occurrenceUuid) {
|
|
85
|
+
const events = this.#events.previous.concat(this.#events.current);
|
|
86
|
+
|
|
87
|
+
if (events.length < 2) {
|
|
88
|
+
logger.error('Replay recording cannot have less than 2 events');
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const recordingSpan = tracing.startSpan('rrweb-replay-recording', {});
|
|
93
|
+
|
|
94
|
+
recordingSpan.setAttribute('rollbar.replay.id', replayId);
|
|
95
|
+
|
|
96
|
+
if (occurrenceUuid) {
|
|
97
|
+
recordingSpan.setAttribute('rollbar.occurrence.uuid', occurrenceUuid);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const earliestEvent = events.reduce((earliestEvent, event) =>
|
|
101
|
+
event.timestamp < earliestEvent.timestamp ? event : earliestEvent,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
recordingSpan.span.startTime = hrtime.fromMillis(earliestEvent.timestamp);
|
|
105
|
+
|
|
106
|
+
for (const event of events) {
|
|
107
|
+
recordingSpan.addEvent(
|
|
108
|
+
'rrweb-replay-events',
|
|
109
|
+
{
|
|
110
|
+
eventType: event.type,
|
|
111
|
+
json: JSON.stringify(event.data),
|
|
112
|
+
'rollbar.replay.id': replayId,
|
|
113
|
+
},
|
|
114
|
+
hrtime.fromMillis(event.timestamp),
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
recordingSpan.end();
|
|
119
|
+
|
|
120
|
+
return tracing.exporter.toPayload();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
start() {
|
|
124
|
+
if (this.isRecording || this.options.enabled === false) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.clear();
|
|
129
|
+
|
|
130
|
+
this.#stopFn = this.#recordFn({
|
|
131
|
+
emit: (event, isCheckout) => {
|
|
132
|
+
if (this.options.debug?.logEmits) {
|
|
133
|
+
this._logEvent(event, isCheckout);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (isCheckout && event.type === EventType.Meta) {
|
|
137
|
+
this.#events.previous = this.#events.current;
|
|
138
|
+
this.#events.current = [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this.#events.current.push(event);
|
|
142
|
+
},
|
|
143
|
+
checkoutEveryNms: this.checkoutEveryNms(),
|
|
144
|
+
errorHandler: (error) => {
|
|
145
|
+
if (this.options.debug?.logErrors) {
|
|
146
|
+
logger.error('Error during replay recording', error);
|
|
147
|
+
}
|
|
148
|
+
return true; // swallow the error instead of throwing it to the window
|
|
149
|
+
},
|
|
150
|
+
...this.#rrwebOptions,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return this;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
stop() {
|
|
157
|
+
if (!this.isRecording) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
this.#stopFn();
|
|
162
|
+
this.#stopFn = null;
|
|
163
|
+
|
|
164
|
+
return this;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
clear() {
|
|
168
|
+
this.#events = {
|
|
169
|
+
previous: [],
|
|
170
|
+
current: [],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
_logEvent(event, isCheckout) {
|
|
175
|
+
logger.log(
|
|
176
|
+
`Recorder: ${isCheckout ? 'checkout' : ''} event\n`,
|
|
177
|
+
((e) => {
|
|
178
|
+
const seen = new WeakSet();
|
|
179
|
+
return JSON.stringify(
|
|
180
|
+
e,
|
|
181
|
+
(_, v) => {
|
|
182
|
+
if (typeof v === 'object' && v !== null) {
|
|
183
|
+
if (seen.has(v)) return '[Circular]';
|
|
184
|
+
seen.add(v);
|
|
185
|
+
}
|
|
186
|
+
return v;
|
|
187
|
+
},
|
|
188
|
+
2,
|
|
189
|
+
);
|
|
190
|
+
})(event),
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import id from '../../tracing/id.js';
|
|
2
|
+
import logger from '../logger.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ReplayMap - Manages the mapping between error occurrences and their associated
|
|
6
|
+
* session recordings. This class handles the coordination between when recordings
|
|
7
|
+
* are dumped and when they are eventually sent to the backend.
|
|
8
|
+
*/
|
|
9
|
+
export default class ReplayMap {
|
|
10
|
+
#map;
|
|
11
|
+
#recorder;
|
|
12
|
+
#api;
|
|
13
|
+
#tracing;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new ReplayMap instance
|
|
17
|
+
*
|
|
18
|
+
* @param {Object} props - Configuration props
|
|
19
|
+
* @param {Object} props.recorder - The recorder instance that dumps replay data into spans
|
|
20
|
+
* @param {Object} props.api - The API instance used to send replay payloads to the backend
|
|
21
|
+
* @param {Object} props.tracing - The tracing instance used to create spans and manage context
|
|
22
|
+
*/
|
|
23
|
+
constructor({ recorder, api, tracing }) {
|
|
24
|
+
if (!recorder) {
|
|
25
|
+
throw new TypeError("Expected 'recorder' to be provided");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!api) {
|
|
29
|
+
throw new TypeError("Expected 'api' to be provided");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!tracing) {
|
|
33
|
+
throw new TypeError("Expected 'tracing' to be provided");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.#map = new Map();
|
|
37
|
+
this.#recorder = recorder;
|
|
38
|
+
this.#api = api;
|
|
39
|
+
this.#tracing = tracing;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Processes a replay by converting recorder events into a transport-ready payload.
|
|
44
|
+
*
|
|
45
|
+
* Calls recorder.dump() to capture events as spans, formats them into a proper payload,
|
|
46
|
+
* and stores the result in the map using replayId as the key.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} replayId - The unique ID for this replay
|
|
49
|
+
* @returns {Promise<string>} A promise resolving to the processed replayId
|
|
50
|
+
* @private
|
|
51
|
+
*/
|
|
52
|
+
async _processReplay(replayId, occurrenceUuid) {
|
|
53
|
+
try {
|
|
54
|
+
const payload = this.#recorder.dump(
|
|
55
|
+
this.#tracing,
|
|
56
|
+
replayId,
|
|
57
|
+
occurrenceUuid,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
this.#map.set(replayId, payload);
|
|
61
|
+
} catch (transformError) {
|
|
62
|
+
logger.error('Error transforming spans:', transformError);
|
|
63
|
+
|
|
64
|
+
this.#map.set(replayId, null); // TODO(matux): Error span?
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return replayId;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Adds a replay to the map and returns a uniquely generated replay ID.
|
|
72
|
+
*
|
|
73
|
+
* This method immediately returns the replayId and asynchronously processes
|
|
74
|
+
* the replay data in the background. The processing involves converting
|
|
75
|
+
* recorder events into a payload format and storing it in the map.
|
|
76
|
+
*
|
|
77
|
+
* @returns {string} A unique identifier for this replay
|
|
78
|
+
*/
|
|
79
|
+
add(replayId, occurrenceUuid) {
|
|
80
|
+
replayId = replayId || id.gen(8);
|
|
81
|
+
|
|
82
|
+
this._processReplay(replayId, occurrenceUuid).catch((error) => {
|
|
83
|
+
logger.error('Failed to process replay:', error);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return replayId;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Sends the replay payload associated with the given replayId to the backend
|
|
91
|
+
* and removes it from the map.
|
|
92
|
+
*
|
|
93
|
+
* Retrieves the payload from the map, checks if it's valid, then sends it
|
|
94
|
+
* to the API endpoint for processing. The payload can be either a spans array
|
|
95
|
+
* or a formatted OTLP payload object.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} replayId - The ID of the replay to send
|
|
98
|
+
* @returns {Promise<boolean>} A promise that resolves to true if the payload was found and sent, false otherwise
|
|
99
|
+
*/
|
|
100
|
+
async send(replayId) {
|
|
101
|
+
if (!replayId) {
|
|
102
|
+
logger.error('ReplayMap.send: No replayId provided');
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!this.#map.has(replayId)) {
|
|
107
|
+
logger.error(`ReplayMap.send: No replay found for replayId: ${replayId}`);
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const payload = this.#map.get(replayId);
|
|
112
|
+
this.#map.delete(replayId);
|
|
113
|
+
|
|
114
|
+
// Check if payload is empty (could be raw spans array or OTLP payload)
|
|
115
|
+
const isEmpty =
|
|
116
|
+
!payload ||
|
|
117
|
+
(Array.isArray(payload) && payload.length === 0) ||
|
|
118
|
+
(payload.resourceSpans && payload.resourceSpans.length === 0);
|
|
119
|
+
|
|
120
|
+
if (isEmpty) {
|
|
121
|
+
logger.error(
|
|
122
|
+
`ReplayMap.send: No payload found for replayId: ${replayId}`,
|
|
123
|
+
);
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
await this.#api.postSpans(payload);
|
|
129
|
+
return true;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
logger.error('Error sending replay:', error);
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Discards the replay associated with the given replay ID by removing
|
|
138
|
+
* it from the map without sending it.
|
|
139
|
+
*
|
|
140
|
+
* @param {string} replayId - The ID of the replay to discard
|
|
141
|
+
* @returns {boolean} True if a replay was found and discarded, false otherwise
|
|
142
|
+
*/
|
|
143
|
+
discard(replayId) {
|
|
144
|
+
if (!replayId) {
|
|
145
|
+
logger.error('ReplayMap.discard: No replayId provided');
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!this.#map.has(replayId)) {
|
|
150
|
+
logger.error(
|
|
151
|
+
`ReplayMap.discard: No replay found for replayId: ${replayId}`,
|
|
152
|
+
);
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
this.#map.delete(replayId);
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Gets spans for the given replay ID
|
|
162
|
+
*
|
|
163
|
+
* @param {string} replayId - The ID to retrieve spans for
|
|
164
|
+
* @returns {Array|null} The spans array or null if not found
|
|
165
|
+
*/
|
|
166
|
+
getSpans(replayId) {
|
|
167
|
+
return this.#map.get(replayId) ?? null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Sets spans for a given replay ID
|
|
172
|
+
*
|
|
173
|
+
* @param {string} replayId - The ID to set spans for
|
|
174
|
+
* @param {Array} spans - The spans to set
|
|
175
|
+
*/
|
|
176
|
+
setSpans(replayId, spans) {
|
|
177
|
+
this.#map.set(replayId, spans);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Returns the size of the map (number of stored replays)
|
|
182
|
+
*
|
|
183
|
+
* @returns {number} The number of replays currently stored
|
|
184
|
+
*/
|
|
185
|
+
get size() {
|
|
186
|
+
return this.#map.size;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Clears all stored replays without sending them
|
|
191
|
+
*/
|
|
192
|
+
clear() {
|
|
193
|
+
this.#map.clear();
|
|
194
|
+
}
|
|
195
|
+
}
|
package/src/browser/rollbar.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
const Rollbar = require('./core');
|
|
2
|
+
const telemeter = require('../telemetry');
|
|
3
|
+
const instrumenter = require('./telemetry');
|
|
4
|
+
const polyfillJSON = require('../utility/polyfillJSON');
|
|
5
|
+
const wrapGlobals = require('./wrapGlobals');
|
|
6
|
+
const scrub = require('../scrub');
|
|
7
|
+
const truncation = require('../truncation');
|
|
8
|
+
const Tracing = require('../tracing/tracing');
|
|
9
|
+
const Recorder = require('./replay/recorder');
|
|
8
10
|
|
|
9
11
|
Rollbar.setComponents({
|
|
10
12
|
telemeter: telemeter,
|
|
@@ -12,7 +14,9 @@ Rollbar.setComponents({
|
|
|
12
14
|
polyfillJSON: polyfillJSON,
|
|
13
15
|
wrapGlobals: wrapGlobals,
|
|
14
16
|
scrub: scrub,
|
|
15
|
-
truncation: truncation
|
|
17
|
+
truncation: truncation,
|
|
18
|
+
tracing: Tracing.default,
|
|
19
|
+
recorder: Recorder.default,
|
|
16
20
|
});
|
|
17
21
|
|
|
18
22
|
module.exports = Rollbar;
|
|
@@ -5,8 +5,8 @@ function RollbarWrap(impl, options) {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
function _setupForwarding(prototype) {
|
|
8
|
-
var _forward = function(method) {
|
|
9
|
-
return function() {
|
|
8
|
+
var _forward = function (method) {
|
|
9
|
+
return function () {
|
|
10
10
|
var args = Array.prototype.slice.call(arguments, 0);
|
|
11
11
|
if (this.impl[method]) {
|
|
12
12
|
return this.impl[method].apply(this.impl, args);
|
|
@@ -14,13 +14,16 @@ function _setupForwarding(prototype) {
|
|
|
14
14
|
};
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
var _methods =
|
|
18
|
-
|
|
17
|
+
var _methods =
|
|
18
|
+
'log,debug,info,warn,warning,error,critical,global,configure,handleUncaughtException,handleAnonymousErrors,handleUnhandledRejection,_createItem,wrap,loadFull,shimId,captureEvent,captureDomContentLoaded,captureLoad'.split(
|
|
19
|
+
',',
|
|
20
|
+
);
|
|
21
|
+
for (var i = 0; i < _methods.length; i++) {
|
|
19
22
|
prototype[_methods[i]] = _forward(_methods[i]);
|
|
20
23
|
}
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
RollbarWrap.prototype._swapAndProcessMessages = function(impl, messages) {
|
|
26
|
+
RollbarWrap.prototype._swapAndProcessMessages = function (impl, messages) {
|
|
24
27
|
this.impl = impl(this.options);
|
|
25
28
|
var msg, method, args;
|
|
26
29
|
while ((msg = messages.shift())) {
|