@newrelic/browser-agent 1.312.1-rc.12 → 1.312.1-rc.13
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/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/util/short-circuit.js +13 -0
- package/dist/cjs/features/jserrors/aggregate/index.js +204 -38
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/util/short-circuit.js +6 -0
- package/dist/esm/features/jserrors/aggregate/index.js +205 -39
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/common/util/short-circuit.d.ts +8 -0
- package/dist/types/common/util/short-circuit.d.ts.map +1 -0
- package/dist/types/features/jserrors/aggregate/index.d.ts +25 -16
- package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/common/util/short-circuit.js +6 -0
- package/src/features/jserrors/aggregate/index.js +200 -43
|
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.RRWEB_PACKAGE_NAME = exports.D
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the version of the agent
|
|
19
19
|
*/
|
|
20
|
-
const VERSION = exports.VERSION = "1.312.1-rc.
|
|
20
|
+
const VERSION = exports.VERSION = "1.312.1-rc.13";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Exposes the build type of the agent
|
|
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.RRWEB_PACKAGE_NAME = exports.D
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the version of the agent
|
|
19
19
|
*/
|
|
20
|
-
const VERSION = exports.VERSION = "1.312.1-rc.
|
|
20
|
+
const VERSION = exports.VERSION = "1.312.1-rc.13";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Exposes the build type of the agent
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.ShortCircuit = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
9
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
10
|
+
*/
|
|
11
|
+
/** A special error used to short-circuit the error processing pipeline */
|
|
12
|
+
class ShortCircuit extends Error {}
|
|
13
|
+
exports.ShortCircuit = ShortCircuit;
|
|
@@ -20,6 +20,7 @@ var _traverse = require("../../../common/util/traverse");
|
|
|
20
20
|
var _internalErrors = require("./internal-errors");
|
|
21
21
|
var _utils = require("../../../common/v2/utils");
|
|
22
22
|
var _causeString = require("./cause-string");
|
|
23
|
+
var _shortCircuit = require("../../../common/util/short-circuit");
|
|
23
24
|
/**
|
|
24
25
|
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
25
26
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -40,8 +41,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
40
41
|
this.observedAt = {};
|
|
41
42
|
this.pageviewReported = {};
|
|
42
43
|
this.errorOnPage = false;
|
|
43
|
-
(0, _registerHandler.registerHandler)('err',
|
|
44
|
-
(0, _registerHandler.registerHandler)('ierr',
|
|
44
|
+
(0, _registerHandler.registerHandler)('err', this.processError.bind(this), this.featureName, this.ee);
|
|
45
|
+
(0, _registerHandler.registerHandler)('ierr', this.processError.bind(this), this.featureName, this.ee);
|
|
45
46
|
(0, _registerHandler.registerHandler)('returnJserror', (jsErrorEvent, softNavAttrs) => this.#storeJserrorForHarvest(jsErrorEvent, softNavAttrs), this.featureName, this.ee);
|
|
46
47
|
|
|
47
48
|
// 0 == off, 1 == on
|
|
@@ -73,60 +74,162 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
/**
|
|
76
|
-
*
|
|
77
|
-
* by a newline character. Lines take the form `<functionName>@<url>:<lineNumber>`.
|
|
77
|
+
* Main entry point for processing JavaScript errors. This method orchestrates the complete error processing pipeline.
|
|
78
78
|
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
79
|
+
* Processing Flow:
|
|
80
|
+
* 1. Filter the error through the customer's onerror handler (if configured and not internal)
|
|
81
|
+
* 2. Compute the stack trace from the error object
|
|
82
|
+
* 3. Evaluate if the error should be swallowed (internal errors, known issues, etc.)
|
|
83
|
+
* 4. Derive target(s) for the error (MFE detection for v2 endpoints, or default target) - Note: "undefined" indicates the default target will be used
|
|
84
|
+
* 5. Store the error for each derived target. During storage (#storeJserrorForHarvest), duplication for MFE <-> container will be handled
|
|
85
|
+
*
|
|
86
|
+
* Important: "ShortCircuit" Pattern:
|
|
87
|
+
* Several steps in the pipeline can throw a ShortCircuit error to halt processing without
|
|
88
|
+
* treating it as a reportable error. This pattern is used when:
|
|
89
|
+
* - The customer's onerror handler returns a truthy value (excluding fingerprinting objects)
|
|
90
|
+
* - The error is identified as an internal error that shouldn't be reported
|
|
91
|
+
*
|
|
92
|
+
* When a ShortCircuit is thrown, processing stops immediately and the error is not stored.
|
|
93
|
+
* Any other thrown error is re-thrown as it represents an actual problem in the agent code.
|
|
94
|
+
*
|
|
95
|
+
* @param {Error|UncaughtError} err - The error instance to be processed
|
|
96
|
+
* @param {number} [time] - The relative ms (to origin) timestamp of occurrence. Defaults to now()
|
|
97
|
+
* @param {boolean} [internal=false] - If the error was "caught" and deemed "internal" before reporting to the jserrors feature
|
|
98
|
+
* @param {object} [customAttributes] - Any custom attributes to be included in the error payload
|
|
99
|
+
* @param {boolean} [hasReplay=false] - A flag indicating if the error occurred during a replay session
|
|
100
|
+
* @param {string} [swallowReason] - A string indicating pre-defined reason if swallowing the error. Mainly used by internal error supportability metrics
|
|
101
|
+
* @param {object} [target] - The target to buffer and harvest to. If undefined, the default configuration target is used
|
|
102
|
+
* @returns {void}
|
|
81
103
|
*/
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
104
|
+
processError(err, time, internal, customAttributes, hasReplay, swallowReason, target) {
|
|
105
|
+
if (!err) return;
|
|
106
|
+
time = time || (0, _now.now)();
|
|
107
|
+
try {
|
|
108
|
+
const filterOutput = this.#filterError(err, internal);
|
|
109
|
+
const stackInfo = (0, _computeStackTrace.computeStackTrace)(err);
|
|
110
|
+
this.#swallowError(stackInfo, internal, swallowReason);
|
|
111
|
+
this.#deriveTargets(stackInfo, target).forEach(target => {
|
|
112
|
+
this.#storeError(err, time, stackInfo, filterOutput, customAttributes, hasReplay, target);
|
|
113
|
+
});
|
|
114
|
+
} catch (e) {
|
|
115
|
+
if (!(e instanceof _shortCircuit.ShortCircuit)) {
|
|
116
|
+
throw e;
|
|
117
|
+
}
|
|
91
118
|
}
|
|
92
|
-
return canonicalStackString;
|
|
93
119
|
}
|
|
94
120
|
|
|
95
121
|
/**
|
|
122
|
+
* Filters an error through the customer's configured onerror handler.
|
|
96
123
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
124
|
+
* If the customer has configured a custom onerror handler and the error is not internal,
|
|
125
|
+
* this method invokes that handler. The handler's return value determines whether the error
|
|
126
|
+
* should be reported:
|
|
127
|
+
* - Falsey values (false, null, undefined, etc.) → Report the error normally
|
|
128
|
+
* - Truthy non-object values → Don't report (throws ShortCircuit)
|
|
129
|
+
* - Object with 'group' property (non-empty string) → Report with fingerprinting label
|
|
130
|
+
* - Any other truthy value → Don't report (throws ShortCircuit)
|
|
131
|
+
*
|
|
132
|
+
* @param {Error|UncaughtError} err - The error to filter
|
|
133
|
+
* @param {boolean} internal - Whether this is an internal error (internal errors skip filtering)
|
|
134
|
+
* @returns {undefined|object} The filter output. If an object with 'group' property, contains fingerprinting data
|
|
135
|
+
* @throws {ShortCircuit} When the error should not be reported based on the filter output
|
|
105
136
|
*/
|
|
106
|
-
|
|
107
|
-
if (!err) return;
|
|
108
|
-
// are we in an interaction
|
|
109
|
-
time = time || (0, _now.now)();
|
|
137
|
+
#filterError(err, internal) {
|
|
110
138
|
let filterOutput;
|
|
111
139
|
if (!internal && this.agentRef.runtime.onerror) {
|
|
112
140
|
filterOutput = this.agentRef.runtime.onerror(err);
|
|
113
141
|
if (filterOutput && !(typeof filterOutput.group === 'string' && filterOutput.group.length)) {
|
|
114
142
|
// All truthy values mean don't report (store) the error, per backwards-compatible usage,
|
|
115
143
|
// - EXCEPT if a fingerprinting label is returned, via an object with key of 'group' and value of non-empty string
|
|
116
|
-
|
|
144
|
+
throw new _shortCircuit.ShortCircuit();
|
|
117
145
|
}
|
|
118
146
|
// Again as with previous usage, all falsey values would include the error.
|
|
119
147
|
}
|
|
120
|
-
|
|
148
|
+
return filterOutput;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Evaluates whether an error should be swallowed (not reported) based on internal error criteria.
|
|
153
|
+
*
|
|
154
|
+
* This method uses the evaluateInternalError function to determine if the error matches known
|
|
155
|
+
* internal error patterns (e.g., errors from the agent itself, known browser issues, etc.).
|
|
156
|
+
* If the error should be swallowed, a supportability metric is recorded and processing is halted.
|
|
157
|
+
*
|
|
158
|
+
* @param {StackInfo} stackInfo - The computed stack trace information
|
|
159
|
+
* @param {boolean} internal - Whether the error was marked as internal
|
|
160
|
+
* @param {string} [swallowReason] - Optional pre-determined reason for swallowing
|
|
161
|
+
* @returns {void}
|
|
162
|
+
* @throws {ShortCircuit} When the error should be swallowed and not reported
|
|
163
|
+
*/
|
|
164
|
+
#swallowError(stackInfo, internal, swallowReason) {
|
|
121
165
|
const {
|
|
122
166
|
shouldSwallow,
|
|
123
167
|
reason
|
|
124
168
|
} = (0, _internalErrors.evaluateInternalError)(stackInfo, internal, swallowReason);
|
|
125
169
|
if (shouldSwallow) {
|
|
126
170
|
this.reportSupportabilityMetric('Internal/Error/' + reason);
|
|
127
|
-
|
|
171
|
+
throw new _shortCircuit.ShortCircuit();
|
|
128
172
|
}
|
|
129
|
-
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Derives the appropriate targets for reporting the given stack information.
|
|
177
|
+
*
|
|
178
|
+
* Targets represent the entities that should receive the error data. This is particularly
|
|
179
|
+
* important for Micro Frontend (MFE) scenarios where errors may need to be reported to
|
|
180
|
+
* different applications.
|
|
181
|
+
*
|
|
182
|
+
* Logic:
|
|
183
|
+
* - If a target is explicitly provided (e.g., from the register API), use it
|
|
184
|
+
* - For v2 endpoints without an explicit target, scan stack frames to detect MFE sources
|
|
185
|
+
* - If no MFE is detected or v2 is not enabled, use undefined (default target)
|
|
186
|
+
*
|
|
187
|
+
* @param {StackInfo} stackInfo - The computed stack trace information containing frames
|
|
188
|
+
* @param {object} [target] - Explicitly provided target, typically from the register API
|
|
189
|
+
* @returns {Array<object|undefined>} Array of targets to report the error to. Always contains at least one element.
|
|
190
|
+
*/
|
|
191
|
+
#deriveTargets(stackInfo, target) {
|
|
192
|
+
const targets = [];
|
|
193
|
+
if (target) {
|
|
194
|
+
// reported by the register API directly
|
|
195
|
+
targets.push(target);
|
|
196
|
+
} else {
|
|
197
|
+
// we dont know if this is MFE yet, we need to figure it out.
|
|
198
|
+
if (this.harvestEndpointVersion === 2) {
|
|
199
|
+
for (const frame of stackInfo.frames) {
|
|
200
|
+
targets.push(...(0, _utils.getRegisteredTargetsFromFilename)(frame.url, this.agentRef));
|
|
201
|
+
if (targets.length) break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (!targets.length) targets.push(undefined);
|
|
205
|
+
}
|
|
206
|
+
return targets;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Stores error data for eventual harvesting and transmission to the backend.
|
|
211
|
+
*
|
|
212
|
+
* This method processes the error through several stages:
|
|
213
|
+
* 1. Build canonical stack string for cross-browser error grouping
|
|
214
|
+
* 2. Build cause chain string if the error has a cause property
|
|
215
|
+
* 3. Create params object with error metadata (stack hash, class, message, etc.)
|
|
216
|
+
* 4. Create bucket hash for internal deduplication
|
|
217
|
+
* 5. Store stack trace on first occurrence of this error
|
|
218
|
+
* 6. Add custom attributes and send to other features (trace, replay)
|
|
219
|
+
* 7. Route through soft nav if enabled, or directly to harvest storage
|
|
220
|
+
* 8. Handle MFE duplication for v2 endpoints if needed
|
|
221
|
+
*
|
|
222
|
+
* @param {Error|UncaughtError} err - The error instance to be processed
|
|
223
|
+
* @param {number} time - The relative ms (to origin) timestamp of occurrence
|
|
224
|
+
* @param {StackInfo} stackInfo - The computed stack trace information
|
|
225
|
+
* @param {object} [filterOutput] - Output from the customer's onerror handler, may contain fingerprinting group
|
|
226
|
+
* @param {object} [customAttributes] - Any custom attributes to be included in the error payload
|
|
227
|
+
* @param {boolean} [hasReplay=false] - A flag indicating if the error occurred during a replay session
|
|
228
|
+
* @param {object} [target] - The target to buffer and harvest to. If undefined, the default configuration target is used
|
|
229
|
+
* @returns {void}
|
|
230
|
+
*/
|
|
231
|
+
#storeError(err, time, stackInfo, filterOutput, customAttributes, hasReplay, target) {
|
|
232
|
+
var canonicalStackString = this.#buildCanonicalStackString(stackInfo);
|
|
130
233
|
const causeStackString = (0, _causeString.buildCauseString)(err);
|
|
131
234
|
const params = {
|
|
132
235
|
stackHash: (0, _stringHashCode.stringHashCode)(canonicalStackString),
|
|
@@ -149,7 +252,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
149
252
|
* the canonical stack trace excludes items like the column number increasing the hit-rate of different errors potentially
|
|
150
253
|
* bucketing and ultimately resulting in the loss of data in NR1.
|
|
151
254
|
*/
|
|
152
|
-
var bucketHash = (0, _stringHashCode.stringHashCode)("".concat(stackInfo.name, "_").concat(stackInfo.message, "_").concat(stackInfo.stackString, "_").concat(params.hasReplay ? 1 : 0, "_").concat(target?.id || 'container'));
|
|
255
|
+
var bucketHash = (0, _stringHashCode.stringHashCode)("".concat(stackInfo.name, "_").concat(stackInfo.message, "_").concat(stackInfo.stackString, "_").concat(params.hasReplay ? 1 : 0, "_").concat(target?.id || 'container', "_").concat(target?.instance || ''));
|
|
153
256
|
if (!this.stackReported[bucketHash]) {
|
|
154
257
|
this.stackReported[bucketHash] = true;
|
|
155
258
|
params.stack_trace = (0, _formatStackTrace.truncateSize)(stackInfo.stackString);
|
|
@@ -189,27 +292,90 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
189
292
|
// pass the error to soft nav for evaluation - it will return it via 'returnJserror' when interaction is resolved
|
|
190
293
|
(0, _handle.handle)('jserror', [jsErrorEvent], undefined, _features.FEATURE_NAMES.softNav, this.ee);
|
|
191
294
|
} else {
|
|
192
|
-
this.#storeJserrorForHarvest(jsErrorEvent
|
|
295
|
+
this.#storeJserrorForHarvest(jsErrorEvent);
|
|
193
296
|
}
|
|
194
297
|
}
|
|
195
298
|
|
|
196
299
|
// always add directly if scoped to a sub-entity, the other pathways above will be deterministic if the main agent should procede
|
|
197
|
-
if (target) this.#storeJserrorForHarvest(
|
|
300
|
+
if (target) this.#storeJserrorForHarvest(jsErrorEvent, {}, target);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Builds a standardized (canonical) stack trace string from the frames in the given `stackInfo` object.
|
|
305
|
+
*
|
|
306
|
+
* The canonical format is used for cross-browser error grouping in NR1, as different browsers
|
|
307
|
+
* format stack traces differently. Each frame is separated by a newline character and takes
|
|
308
|
+
* the form: `<functionName>@<url>:<lineNumber>`
|
|
309
|
+
*
|
|
310
|
+
* Note: Column numbers are intentionally excluded from the canonical format to improve
|
|
311
|
+
* grouping accuracy, as the same error across different minified builds might have different
|
|
312
|
+
* column numbers but should still be grouped together.
|
|
313
|
+
*
|
|
314
|
+
* Example output:
|
|
315
|
+
* ```
|
|
316
|
+
* handleClick@https://example.com/app.js:42
|
|
317
|
+
* EventEmitter.emit@https://example.com/vendor.js:1337
|
|
318
|
+
* ```
|
|
319
|
+
*
|
|
320
|
+
* @param {StackInfo} stackInfo - An object containing parsed stack frames from computeStackTrace
|
|
321
|
+
* @returns {string} A canonical stack string built from the URLs and function names in the given `stackInfo` object
|
|
322
|
+
*/
|
|
323
|
+
#buildCanonicalStackString(stackInfo) {
|
|
324
|
+
var canonicalStackString = '';
|
|
325
|
+
for (var i = 0; i < stackInfo.frames.length; i++) {
|
|
326
|
+
var frame = stackInfo.frames[i];
|
|
327
|
+
var func = (0, _canonicalFunctionName.canonicalFunctionName)(frame.func);
|
|
328
|
+
if (canonicalStackString) canonicalStackString += '\n';
|
|
329
|
+
if (func) canonicalStackString += func + '@';
|
|
330
|
+
if (typeof frame.url === 'string') canonicalStackString += frame.url;
|
|
331
|
+
if (frame.line) canonicalStackString += ':' + frame.line;
|
|
332
|
+
}
|
|
333
|
+
return canonicalStackString;
|
|
198
334
|
}
|
|
199
|
-
|
|
200
|
-
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Adds a processed error to the harvest buffer with all custom attributes merged.
|
|
338
|
+
*
|
|
339
|
+
* This is the final step before an error is stored to be sent to the backend. It handles:
|
|
340
|
+
* - Merging all custom attributes (global, soft nav, MFE, and local)
|
|
341
|
+
* - Creating a unique aggregate hash for deduplication
|
|
342
|
+
* - Adding the error to the events buffer for harvest
|
|
343
|
+
* - Duplicating the error for MFE scenarios when needed (v2 endpoints)
|
|
344
|
+
*
|
|
345
|
+
* Custom Attribute Precedence (lowest to highest):
|
|
346
|
+
* 1. Global jsAttributes from agent config
|
|
347
|
+
* 2. Soft navigation attributes (if from a soft nav interaction)
|
|
348
|
+
* 3. MFE v2 attributes (source/parent metadata)
|
|
349
|
+
* 4. Local custom attributes passed with the specific error
|
|
350
|
+
*
|
|
351
|
+
* @param {Array} errorInfoArr - Array containing [type, bucketHash, params, metrics, customAttributes, target]
|
|
352
|
+
* @param {object} [attrs={}] - Additional attributes to merge (e.g., from soft nav interactions)
|
|
353
|
+
* @returns {void}
|
|
354
|
+
*/
|
|
355
|
+
#storeJserrorForHarvest(errorInfoArr, attrs = {}, target) {
|
|
356
|
+
let [type, bucketHash, params, newMetrics, localAttrs] = errorInfoArr;
|
|
201
357
|
const allCustomAttrs = {
|
|
202
358
|
/** MFE specific attributes if in "multiple" mode (ie consumer version 2) */
|
|
203
359
|
...(0, _utils.getVersion2Attributes)(target, this)
|
|
204
360
|
};
|
|
205
361
|
Object.entries(this.agentRef.info.jsAttributes).forEach(([k, v]) => setCustom(k, v));
|
|
206
|
-
Object.entries(
|
|
362
|
+
Object.entries(attrs).forEach(([k, v]) => setCustom(k, v)); // when an ixn finishes, it'll pass attrs specific to the ixn; if no associated ixn, this defaults to empty
|
|
207
363
|
if (params.browserInteractionId) bucketHash += params.browserInteractionId;
|
|
208
364
|
if (localAttrs) Object.entries(localAttrs).forEach(([k, v]) => setCustom(k, v)); // local custom attrs are applied in either case with the highest precedence
|
|
209
365
|
|
|
210
366
|
const jsAttributesHash = (0, _stringHashCode.stringHashCode)((0, _stringify.stringify)(allCustomAttrs));
|
|
211
367
|
const aggregateHash = bucketHash + ':' + jsAttributesHash;
|
|
212
368
|
this.events.add([type, aggregateHash, params, newMetrics, allCustomAttrs]);
|
|
369
|
+
if ((0, _utils.shouldDuplicate)(target, this)) {
|
|
370
|
+
// Clone the array with new object references to prevent deduplication in the shared aggregator
|
|
371
|
+
this.#storeJserrorForHarvest([type, bucketHash, {
|
|
372
|
+
...params
|
|
373
|
+
}, {
|
|
374
|
+
...newMetrics
|
|
375
|
+
}, localAttrs && {
|
|
376
|
+
...localAttrs
|
|
377
|
+
}], (0, _utils.getVersion2DuplicationAttributes)(target, this));
|
|
378
|
+
}
|
|
213
379
|
function setCustom(key, val) {
|
|
214
380
|
allCustomAttrs[key] = val && typeof val === 'object' ? (0, _stringify.stringify)(val) : val;
|
|
215
381
|
}
|
|
@@ -17,8 +17,9 @@ import { AggregateBase } from '../../utils/aggregate-base';
|
|
|
17
17
|
import { now } from '../../../common/timing/now';
|
|
18
18
|
import { applyFnToProps } from '../../../common/util/traverse';
|
|
19
19
|
import { evaluateInternalError } from './internal-errors';
|
|
20
|
-
import { getVersion2Attributes } from '../../../common/v2/utils';
|
|
20
|
+
import { getRegisteredTargetsFromFilename, getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/v2/utils';
|
|
21
21
|
import { buildCauseString } from './cause-string';
|
|
22
|
+
import { ShortCircuit } from '../../../common/util/short-circuit';
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* @typedef {import('./compute-stack-trace.js').StackInfo} StackInfo
|
|
@@ -35,8 +36,8 @@ export class Aggregate extends AggregateBase {
|
|
|
35
36
|
this.observedAt = {};
|
|
36
37
|
this.pageviewReported = {};
|
|
37
38
|
this.errorOnPage = false;
|
|
38
|
-
register('err',
|
|
39
|
-
register('ierr',
|
|
39
|
+
register('err', this.processError.bind(this), this.featureName, this.ee);
|
|
40
|
+
register('ierr', this.processError.bind(this), this.featureName, this.ee);
|
|
40
41
|
register('returnJserror', (jsErrorEvent, softNavAttrs) => this.#storeJserrorForHarvest(jsErrorEvent, softNavAttrs), this.featureName, this.ee);
|
|
41
42
|
|
|
42
43
|
// 0 == off, 1 == on
|
|
@@ -68,60 +69,162 @@ export class Aggregate extends AggregateBase {
|
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
/**
|
|
71
|
-
*
|
|
72
|
-
* by a newline character. Lines take the form `<functionName>@<url>:<lineNumber>`.
|
|
72
|
+
* Main entry point for processing JavaScript errors. This method orchestrates the complete error processing pipeline.
|
|
73
73
|
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
74
|
+
* Processing Flow:
|
|
75
|
+
* 1. Filter the error through the customer's onerror handler (if configured and not internal)
|
|
76
|
+
* 2. Compute the stack trace from the error object
|
|
77
|
+
* 3. Evaluate if the error should be swallowed (internal errors, known issues, etc.)
|
|
78
|
+
* 4. Derive target(s) for the error (MFE detection for v2 endpoints, or default target) - Note: "undefined" indicates the default target will be used
|
|
79
|
+
* 5. Store the error for each derived target. During storage (#storeJserrorForHarvest), duplication for MFE <-> container will be handled
|
|
80
|
+
*
|
|
81
|
+
* Important: "ShortCircuit" Pattern:
|
|
82
|
+
* Several steps in the pipeline can throw a ShortCircuit error to halt processing without
|
|
83
|
+
* treating it as a reportable error. This pattern is used when:
|
|
84
|
+
* - The customer's onerror handler returns a truthy value (excluding fingerprinting objects)
|
|
85
|
+
* - The error is identified as an internal error that shouldn't be reported
|
|
86
|
+
*
|
|
87
|
+
* When a ShortCircuit is thrown, processing stops immediately and the error is not stored.
|
|
88
|
+
* Any other thrown error is re-thrown as it represents an actual problem in the agent code.
|
|
89
|
+
*
|
|
90
|
+
* @param {Error|UncaughtError} err - The error instance to be processed
|
|
91
|
+
* @param {number} [time] - The relative ms (to origin) timestamp of occurrence. Defaults to now()
|
|
92
|
+
* @param {boolean} [internal=false] - If the error was "caught" and deemed "internal" before reporting to the jserrors feature
|
|
93
|
+
* @param {object} [customAttributes] - Any custom attributes to be included in the error payload
|
|
94
|
+
* @param {boolean} [hasReplay=false] - A flag indicating if the error occurred during a replay session
|
|
95
|
+
* @param {string} [swallowReason] - A string indicating pre-defined reason if swallowing the error. Mainly used by internal error supportability metrics
|
|
96
|
+
* @param {object} [target] - The target to buffer and harvest to. If undefined, the default configuration target is used
|
|
97
|
+
* @returns {void}
|
|
76
98
|
*/
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
99
|
+
processError(err, time, internal, customAttributes, hasReplay, swallowReason, target) {
|
|
100
|
+
if (!err) return;
|
|
101
|
+
time = time || now();
|
|
102
|
+
try {
|
|
103
|
+
const filterOutput = this.#filterError(err, internal);
|
|
104
|
+
const stackInfo = computeStackTrace(err);
|
|
105
|
+
this.#swallowError(stackInfo, internal, swallowReason);
|
|
106
|
+
this.#deriveTargets(stackInfo, target).forEach(target => {
|
|
107
|
+
this.#storeError(err, time, stackInfo, filterOutput, customAttributes, hasReplay, target);
|
|
108
|
+
});
|
|
109
|
+
} catch (e) {
|
|
110
|
+
if (!(e instanceof ShortCircuit)) {
|
|
111
|
+
throw e;
|
|
112
|
+
}
|
|
86
113
|
}
|
|
87
|
-
return canonicalStackString;
|
|
88
114
|
}
|
|
89
115
|
|
|
90
116
|
/**
|
|
117
|
+
* Filters an error through the customer's configured onerror handler.
|
|
91
118
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
119
|
+
* If the customer has configured a custom onerror handler and the error is not internal,
|
|
120
|
+
* this method invokes that handler. The handler's return value determines whether the error
|
|
121
|
+
* should be reported:
|
|
122
|
+
* - Falsey values (false, null, undefined, etc.) → Report the error normally
|
|
123
|
+
* - Truthy non-object values → Don't report (throws ShortCircuit)
|
|
124
|
+
* - Object with 'group' property (non-empty string) → Report with fingerprinting label
|
|
125
|
+
* - Any other truthy value → Don't report (throws ShortCircuit)
|
|
126
|
+
*
|
|
127
|
+
* @param {Error|UncaughtError} err - The error to filter
|
|
128
|
+
* @param {boolean} internal - Whether this is an internal error (internal errors skip filtering)
|
|
129
|
+
* @returns {undefined|object} The filter output. If an object with 'group' property, contains fingerprinting data
|
|
130
|
+
* @throws {ShortCircuit} When the error should not be reported based on the filter output
|
|
100
131
|
*/
|
|
101
|
-
|
|
102
|
-
if (!err) return;
|
|
103
|
-
// are we in an interaction
|
|
104
|
-
time = time || now();
|
|
132
|
+
#filterError(err, internal) {
|
|
105
133
|
let filterOutput;
|
|
106
134
|
if (!internal && this.agentRef.runtime.onerror) {
|
|
107
135
|
filterOutput = this.agentRef.runtime.onerror(err);
|
|
108
136
|
if (filterOutput && !(typeof filterOutput.group === 'string' && filterOutput.group.length)) {
|
|
109
137
|
// All truthy values mean don't report (store) the error, per backwards-compatible usage,
|
|
110
138
|
// - EXCEPT if a fingerprinting label is returned, via an object with key of 'group' and value of non-empty string
|
|
111
|
-
|
|
139
|
+
throw new ShortCircuit();
|
|
112
140
|
}
|
|
113
141
|
// Again as with previous usage, all falsey values would include the error.
|
|
114
142
|
}
|
|
115
|
-
|
|
143
|
+
return filterOutput;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Evaluates whether an error should be swallowed (not reported) based on internal error criteria.
|
|
148
|
+
*
|
|
149
|
+
* This method uses the evaluateInternalError function to determine if the error matches known
|
|
150
|
+
* internal error patterns (e.g., errors from the agent itself, known browser issues, etc.).
|
|
151
|
+
* If the error should be swallowed, a supportability metric is recorded and processing is halted.
|
|
152
|
+
*
|
|
153
|
+
* @param {StackInfo} stackInfo - The computed stack trace information
|
|
154
|
+
* @param {boolean} internal - Whether the error was marked as internal
|
|
155
|
+
* @param {string} [swallowReason] - Optional pre-determined reason for swallowing
|
|
156
|
+
* @returns {void}
|
|
157
|
+
* @throws {ShortCircuit} When the error should be swallowed and not reported
|
|
158
|
+
*/
|
|
159
|
+
#swallowError(stackInfo, internal, swallowReason) {
|
|
116
160
|
const {
|
|
117
161
|
shouldSwallow,
|
|
118
162
|
reason
|
|
119
163
|
} = evaluateInternalError(stackInfo, internal, swallowReason);
|
|
120
164
|
if (shouldSwallow) {
|
|
121
165
|
this.reportSupportabilityMetric('Internal/Error/' + reason);
|
|
122
|
-
|
|
166
|
+
throw new ShortCircuit();
|
|
123
167
|
}
|
|
124
|
-
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Derives the appropriate targets for reporting the given stack information.
|
|
172
|
+
*
|
|
173
|
+
* Targets represent the entities that should receive the error data. This is particularly
|
|
174
|
+
* important for Micro Frontend (MFE) scenarios where errors may need to be reported to
|
|
175
|
+
* different applications.
|
|
176
|
+
*
|
|
177
|
+
* Logic:
|
|
178
|
+
* - If a target is explicitly provided (e.g., from the register API), use it
|
|
179
|
+
* - For v2 endpoints without an explicit target, scan stack frames to detect MFE sources
|
|
180
|
+
* - If no MFE is detected or v2 is not enabled, use undefined (default target)
|
|
181
|
+
*
|
|
182
|
+
* @param {StackInfo} stackInfo - The computed stack trace information containing frames
|
|
183
|
+
* @param {object} [target] - Explicitly provided target, typically from the register API
|
|
184
|
+
* @returns {Array<object|undefined>} Array of targets to report the error to. Always contains at least one element.
|
|
185
|
+
*/
|
|
186
|
+
#deriveTargets(stackInfo, target) {
|
|
187
|
+
const targets = [];
|
|
188
|
+
if (target) {
|
|
189
|
+
// reported by the register API directly
|
|
190
|
+
targets.push(target);
|
|
191
|
+
} else {
|
|
192
|
+
// we dont know if this is MFE yet, we need to figure it out.
|
|
193
|
+
if (this.harvestEndpointVersion === 2) {
|
|
194
|
+
for (const frame of stackInfo.frames) {
|
|
195
|
+
targets.push(...getRegisteredTargetsFromFilename(frame.url, this.agentRef));
|
|
196
|
+
if (targets.length) break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (!targets.length) targets.push(undefined);
|
|
200
|
+
}
|
|
201
|
+
return targets;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Stores error data for eventual harvesting and transmission to the backend.
|
|
206
|
+
*
|
|
207
|
+
* This method processes the error through several stages:
|
|
208
|
+
* 1. Build canonical stack string for cross-browser error grouping
|
|
209
|
+
* 2. Build cause chain string if the error has a cause property
|
|
210
|
+
* 3. Create params object with error metadata (stack hash, class, message, etc.)
|
|
211
|
+
* 4. Create bucket hash for internal deduplication
|
|
212
|
+
* 5. Store stack trace on first occurrence of this error
|
|
213
|
+
* 6. Add custom attributes and send to other features (trace, replay)
|
|
214
|
+
* 7. Route through soft nav if enabled, or directly to harvest storage
|
|
215
|
+
* 8. Handle MFE duplication for v2 endpoints if needed
|
|
216
|
+
*
|
|
217
|
+
* @param {Error|UncaughtError} err - The error instance to be processed
|
|
218
|
+
* @param {number} time - The relative ms (to origin) timestamp of occurrence
|
|
219
|
+
* @param {StackInfo} stackInfo - The computed stack trace information
|
|
220
|
+
* @param {object} [filterOutput] - Output from the customer's onerror handler, may contain fingerprinting group
|
|
221
|
+
* @param {object} [customAttributes] - Any custom attributes to be included in the error payload
|
|
222
|
+
* @param {boolean} [hasReplay=false] - A flag indicating if the error occurred during a replay session
|
|
223
|
+
* @param {object} [target] - The target to buffer and harvest to. If undefined, the default configuration target is used
|
|
224
|
+
* @returns {void}
|
|
225
|
+
*/
|
|
226
|
+
#storeError(err, time, stackInfo, filterOutput, customAttributes, hasReplay, target) {
|
|
227
|
+
var canonicalStackString = this.#buildCanonicalStackString(stackInfo);
|
|
125
228
|
const causeStackString = buildCauseString(err);
|
|
126
229
|
const params = {
|
|
127
230
|
stackHash: stringHashCode(canonicalStackString),
|
|
@@ -144,7 +247,7 @@ export class Aggregate extends AggregateBase {
|
|
|
144
247
|
* the canonical stack trace excludes items like the column number increasing the hit-rate of different errors potentially
|
|
145
248
|
* bucketing and ultimately resulting in the loss of data in NR1.
|
|
146
249
|
*/
|
|
147
|
-
var bucketHash = stringHashCode("".concat(stackInfo.name, "_").concat(stackInfo.message, "_").concat(stackInfo.stackString, "_").concat(params.hasReplay ? 1 : 0, "_").concat(target?.id || 'container'));
|
|
250
|
+
var bucketHash = stringHashCode("".concat(stackInfo.name, "_").concat(stackInfo.message, "_").concat(stackInfo.stackString, "_").concat(params.hasReplay ? 1 : 0, "_").concat(target?.id || 'container', "_").concat(target?.instance || ''));
|
|
148
251
|
if (!this.stackReported[bucketHash]) {
|
|
149
252
|
this.stackReported[bucketHash] = true;
|
|
150
253
|
params.stack_trace = truncateSize(stackInfo.stackString);
|
|
@@ -184,27 +287,90 @@ export class Aggregate extends AggregateBase {
|
|
|
184
287
|
// pass the error to soft nav for evaluation - it will return it via 'returnJserror' when interaction is resolved
|
|
185
288
|
handle('jserror', [jsErrorEvent], undefined, FEATURE_NAMES.softNav, this.ee);
|
|
186
289
|
} else {
|
|
187
|
-
this.#storeJserrorForHarvest(jsErrorEvent
|
|
290
|
+
this.#storeJserrorForHarvest(jsErrorEvent);
|
|
188
291
|
}
|
|
189
292
|
}
|
|
190
293
|
|
|
191
294
|
// always add directly if scoped to a sub-entity, the other pathways above will be deterministic if the main agent should procede
|
|
192
|
-
if (target) this.#storeJserrorForHarvest(
|
|
295
|
+
if (target) this.#storeJserrorForHarvest(jsErrorEvent, {}, target);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Builds a standardized (canonical) stack trace string from the frames in the given `stackInfo` object.
|
|
300
|
+
*
|
|
301
|
+
* The canonical format is used for cross-browser error grouping in NR1, as different browsers
|
|
302
|
+
* format stack traces differently. Each frame is separated by a newline character and takes
|
|
303
|
+
* the form: `<functionName>@<url>:<lineNumber>`
|
|
304
|
+
*
|
|
305
|
+
* Note: Column numbers are intentionally excluded from the canonical format to improve
|
|
306
|
+
* grouping accuracy, as the same error across different minified builds might have different
|
|
307
|
+
* column numbers but should still be grouped together.
|
|
308
|
+
*
|
|
309
|
+
* Example output:
|
|
310
|
+
* ```
|
|
311
|
+
* handleClick@https://example.com/app.js:42
|
|
312
|
+
* EventEmitter.emit@https://example.com/vendor.js:1337
|
|
313
|
+
* ```
|
|
314
|
+
*
|
|
315
|
+
* @param {StackInfo} stackInfo - An object containing parsed stack frames from computeStackTrace
|
|
316
|
+
* @returns {string} A canonical stack string built from the URLs and function names in the given `stackInfo` object
|
|
317
|
+
*/
|
|
318
|
+
#buildCanonicalStackString(stackInfo) {
|
|
319
|
+
var canonicalStackString = '';
|
|
320
|
+
for (var i = 0; i < stackInfo.frames.length; i++) {
|
|
321
|
+
var frame = stackInfo.frames[i];
|
|
322
|
+
var func = canonicalFunctionName(frame.func);
|
|
323
|
+
if (canonicalStackString) canonicalStackString += '\n';
|
|
324
|
+
if (func) canonicalStackString += func + '@';
|
|
325
|
+
if (typeof frame.url === 'string') canonicalStackString += frame.url;
|
|
326
|
+
if (frame.line) canonicalStackString += ':' + frame.line;
|
|
327
|
+
}
|
|
328
|
+
return canonicalStackString;
|
|
193
329
|
}
|
|
194
|
-
|
|
195
|
-
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Adds a processed error to the harvest buffer with all custom attributes merged.
|
|
333
|
+
*
|
|
334
|
+
* This is the final step before an error is stored to be sent to the backend. It handles:
|
|
335
|
+
* - Merging all custom attributes (global, soft nav, MFE, and local)
|
|
336
|
+
* - Creating a unique aggregate hash for deduplication
|
|
337
|
+
* - Adding the error to the events buffer for harvest
|
|
338
|
+
* - Duplicating the error for MFE scenarios when needed (v2 endpoints)
|
|
339
|
+
*
|
|
340
|
+
* Custom Attribute Precedence (lowest to highest):
|
|
341
|
+
* 1. Global jsAttributes from agent config
|
|
342
|
+
* 2. Soft navigation attributes (if from a soft nav interaction)
|
|
343
|
+
* 3. MFE v2 attributes (source/parent metadata)
|
|
344
|
+
* 4. Local custom attributes passed with the specific error
|
|
345
|
+
*
|
|
346
|
+
* @param {Array} errorInfoArr - Array containing [type, bucketHash, params, metrics, customAttributes, target]
|
|
347
|
+
* @param {object} [attrs={}] - Additional attributes to merge (e.g., from soft nav interactions)
|
|
348
|
+
* @returns {void}
|
|
349
|
+
*/
|
|
350
|
+
#storeJserrorForHarvest(errorInfoArr, attrs = {}, target) {
|
|
351
|
+
let [type, bucketHash, params, newMetrics, localAttrs] = errorInfoArr;
|
|
196
352
|
const allCustomAttrs = {
|
|
197
353
|
/** MFE specific attributes if in "multiple" mode (ie consumer version 2) */
|
|
198
354
|
...getVersion2Attributes(target, this)
|
|
199
355
|
};
|
|
200
356
|
Object.entries(this.agentRef.info.jsAttributes).forEach(([k, v]) => setCustom(k, v));
|
|
201
|
-
Object.entries(
|
|
357
|
+
Object.entries(attrs).forEach(([k, v]) => setCustom(k, v)); // when an ixn finishes, it'll pass attrs specific to the ixn; if no associated ixn, this defaults to empty
|
|
202
358
|
if (params.browserInteractionId) bucketHash += params.browserInteractionId;
|
|
203
359
|
if (localAttrs) Object.entries(localAttrs).forEach(([k, v]) => setCustom(k, v)); // local custom attrs are applied in either case with the highest precedence
|
|
204
360
|
|
|
205
361
|
const jsAttributesHash = stringHashCode(stringify(allCustomAttrs));
|
|
206
362
|
const aggregateHash = bucketHash + ':' + jsAttributesHash;
|
|
207
363
|
this.events.add([type, aggregateHash, params, newMetrics, allCustomAttrs]);
|
|
364
|
+
if (shouldDuplicate(target, this)) {
|
|
365
|
+
// Clone the array with new object references to prevent deduplication in the shared aggregator
|
|
366
|
+
this.#storeJserrorForHarvest([type, bucketHash, {
|
|
367
|
+
...params
|
|
368
|
+
}, {
|
|
369
|
+
...newMetrics
|
|
370
|
+
}, localAttrs && {
|
|
371
|
+
...localAttrs
|
|
372
|
+
}], getVersion2DuplicationAttributes(target, this));
|
|
373
|
+
}
|
|
208
374
|
function setCustom(key, val) {
|
|
209
375
|
allCustomAttrs[key] = val && typeof val === 'object' ? stringify(val) : val;
|
|
210
376
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/index.js","../src/cdn/experimental.js","../src/cdn/lite.js","../src/cdn/pro.js","../src/cdn/spa.js","../src/common/aggregate/aggregator.js","../src/common/aggregate/event-aggregator.js","../src/common/config/configurable.js","../src/common/config/info.js","../src/common/config/init-types.js","../src/common/config/init.js","../src/common/config/loader-config.js","../src/common/config/runtime.js","../src/common/constants/agent-constants.js","../src/common/constants/env.cdn.js","../src/common/constants/env.js","../src/common/constants/env.npm.js","../src/common/constants/runtime.js","../src/common/constants/shared-channel.js","../src/common/deny-list/deny-list.js","../src/common/dispatch/global-event.js","../src/common/dom/iframe.js","../src/common/dom/query-selector.js","../src/common/dom/selector-path.js","../src/common/drain/drain.js","../src/common/event-emitter/contextual-ee.js","../src/common/event-emitter/event-context.js","../src/common/event-emitter/handle.js","../src/common/event-emitter/register-handler.js","../src/common/event-listener/event-listener-opts.js","../src/common/harvest/harvester.js","../src/common/harvest/types.js","../src/common/ids/bundle-id.js","../src/common/ids/id.js","../src/common/ids/unique-id.js","../src/common/serialize/bel-serializer.js","../src/common/session/constants.js","../src/common/session/session-entity.js","../src/common/storage/local-storage.js","../src/common/timer/interaction-timer.js","../src/common/timer/timer.js","../src/common/timing/nav-timing.js","../src/common/timing/now.js","../src/common/timing/time-keeper.js","../src/common/unload/eol.js","../src/common/url/canonicalize-url.js","../src/common/url/clean-url.js","../src/common/url/encode.js","../src/common/url/extract-url.js","../src/common/url/location.js","../src/common/url/parse-url.js","../src/common/url/protocol.js","../src/common/util/attribute-size.js","../src/common/util/browser-stack-matchers.js","../src/common/util/console.js","../src/common/util/data-size.js","../src/common/util/event-origin.js","../src/common/util/feature-flags.js","../src/common/util/get-or-set.js","../src/common/util/invoke.js","../src/common/util/monkey-patched.js","../src/common/util/obfuscate.js","../src/common/util/stringify.js","../src/common/util/submit-data.js","../src/common/util/text.js","../src/common/util/traverse.js","../src/common/util/type-check.js","../src/common/util/webdriver-detection.js","../src/common/v2/script-correlation.js","../src/common/v2/script-tracker.js","../src/common/v2/utils.js","../src/common/vitals/constants.js","../src/common/vitals/cumulative-layout-shift.js","../src/common/vitals/first-contentful-paint.js","../src/common/vitals/first-paint.js","../src/common/vitals/interaction-to-next-paint.js","../src/common/vitals/largest-contentful-paint.js","../src/common/vitals/load-time.js","../src/common/vitals/time-to-first-byte.js","../src/common/vitals/vital-metric.js","../src/common/window/load.js","../src/common/window/nreum.js","../src/common/window/page-visibility.js","../src/common/wrap/wrap-events.js","../src/common/wrap/wrap-fetch.js","../src/common/wrap/wrap-function.js","../src/common/wrap/wrap-history.js","../src/common/wrap/wrap-logger.js","../src/common/wrap/wrap-websocket.js","../src/common/wrap/wrap-xhr.js","../src/features/ajax/constants.js","../src/features/ajax/index.js","../src/features/ajax/aggregate/gql.js","../src/features/ajax/aggregate/index.js","../src/features/ajax/instrument/distributed-tracing.js","../src/features/ajax/instrument/index.js","../src/features/ajax/instrument/response-size.js","../src/features/generic_events/constants.js","../src/features/generic_events/index.js","../src/features/generic_events/aggregate/index.js","../src/features/generic_events/aggregate/user-actions/aggregated-user-action.js","../src/features/generic_events/aggregate/user-actions/user-actions-aggregator.js","../src/features/generic_events/instrument/index.js","../src/features/jserrors/constants.js","../src/features/jserrors/index.js","../src/features/jserrors/aggregate/canonical-function-name.js","../src/features/jserrors/aggregate/cause-string.js","../src/features/jserrors/aggregate/compute-stack-trace.js","../src/features/jserrors/aggregate/format-stack-trace.js","../src/features/jserrors/aggregate/index.js","../src/features/jserrors/aggregate/internal-errors.js","../src/features/jserrors/aggregate/string-hash-code.js","../src/features/jserrors/instrument/index.js","../src/features/jserrors/shared/cast-error.js","../src/features/jserrors/shared/uncaught-error.js","../src/features/logging/constants.js","../src/features/logging/index.js","../src/features/logging/aggregate/index.js","../src/features/logging/instrument/index.js","../src/features/logging/shared/log.js","../src/features/logging/shared/utils.js","../src/features/metrics/constants.js","../src/features/metrics/index.js","../src/features/metrics/aggregate/framework-detection.js","../src/features/metrics/aggregate/harvest-metadata.js","../src/features/metrics/aggregate/index.js","../src/features/metrics/instrument/index.js","../src/features/page_action/constants.js","../src/features/page_action/index.js","../src/features/page_action/instrument/index.js","../src/features/page_view_event/constants.js","../src/features/page_view_event/index.js","../src/features/page_view_event/aggregate/index.js","../src/features/page_view_event/aggregate/initialized-features.js","../src/features/page_view_event/instrument/index.js","../src/features/page_view_timing/constants.js","../src/features/page_view_timing/index.js","../src/features/page_view_timing/aggregate/index.js","../src/features/page_view_timing/instrument/index.js","../src/features/session_replay/constants.js","../src/features/session_replay/index.js","../src/features/session_replay/aggregate/index.js","../src/features/session_replay/instrument/index.js","../src/features/session_replay/shared/recorder-events.js","../src/features/session_replay/shared/recorder.js","../src/features/session_replay/shared/stylesheet-evaluator.js","../src/features/session_replay/shared/utils.js","../src/features/session_trace/constants.js","../src/features/session_trace/index.js","../src/features/session_trace/aggregate/index.js","../src/features/session_trace/aggregate/trace/node.js","../src/features/session_trace/aggregate/trace/storage.js","../src/features/session_trace/aggregate/trace/utils.js","../src/features/session_trace/instrument/index.js","../src/features/soft_navigations/constants.js","../src/features/soft_navigations/index.js","../src/features/soft_navigations/aggregate/ajax-node.js","../src/features/soft_navigations/aggregate/bel-node.js","../src/features/soft_navigations/aggregate/index.js","../src/features/soft_navigations/aggregate/initial-page-load-interaction.js","../src/features/soft_navigations/aggregate/interaction.js","../src/features/soft_navigations/instrument/index.js","../src/features/utils/agent-session.js","../src/features/utils/aggregate-base.js","../src/features/utils/event-buffer.js","../src/features/utils/feature-base.js","../src/features/utils/feature-gates.js","../src/features/utils/instrument-base.js","../src/interfaces/registered-entity.js","../src/loaders/agent-base.js","../src/loaders/agent.js","../src/loaders/api-base.js","../src/loaders/browser-agent.js","../src/loaders/micro-agent-base.js","../src/loaders/micro-agent.js","../src/loaders/api/addPageAction.js","../src/loaders/api/addRelease.js","../src/loaders/api/addToTrace.js","../src/loaders/api/consent.js","../src/loaders/api/constants.js","../src/loaders/api/finished.js","../src/loaders/api/interaction-types.js","../src/loaders/api/interaction.js","../src/loaders/api/log.js","../src/loaders/api/measure.js","../src/loaders/api/noticeError.js","../src/loaders/api/pauseReplay.js","../src/loaders/api/recordCustomEvent.js","../src/loaders/api/recordReplay.js","../src/loaders/api/register-api-types.js","../src/loaders/api/register.js","../src/loaders/api/setApplicationVersion.js","../src/loaders/api/setCustomAttribute.js","../src/loaders/api/setErrorHandler.js","../src/loaders/api/setPageViewName.js","../src/loaders/api/setUserId.js","../src/loaders/api/sharedHandlers.js","../src/loaders/api/start.js","../src/loaders/api/topLevelCallers.js","../src/loaders/api/wrapLogger.js","../src/loaders/configure/configure.js","../src/loaders/configure/nonce.js","../src/loaders/configure/public-path.js","../src/loaders/features/enabled-features.js","../src/loaders/features/featureDependencies.js","../src/loaders/features/features.js"],"version":"5.9.3"}
|
|
1
|
+
{"root":["../src/index.js","../src/cdn/experimental.js","../src/cdn/lite.js","../src/cdn/pro.js","../src/cdn/spa.js","../src/common/aggregate/aggregator.js","../src/common/aggregate/event-aggregator.js","../src/common/config/configurable.js","../src/common/config/info.js","../src/common/config/init-types.js","../src/common/config/init.js","../src/common/config/loader-config.js","../src/common/config/runtime.js","../src/common/constants/agent-constants.js","../src/common/constants/env.cdn.js","../src/common/constants/env.js","../src/common/constants/env.npm.js","../src/common/constants/runtime.js","../src/common/constants/shared-channel.js","../src/common/deny-list/deny-list.js","../src/common/dispatch/global-event.js","../src/common/dom/iframe.js","../src/common/dom/query-selector.js","../src/common/dom/selector-path.js","../src/common/drain/drain.js","../src/common/event-emitter/contextual-ee.js","../src/common/event-emitter/event-context.js","../src/common/event-emitter/handle.js","../src/common/event-emitter/register-handler.js","../src/common/event-listener/event-listener-opts.js","../src/common/harvest/harvester.js","../src/common/harvest/types.js","../src/common/ids/bundle-id.js","../src/common/ids/id.js","../src/common/ids/unique-id.js","../src/common/serialize/bel-serializer.js","../src/common/session/constants.js","../src/common/session/session-entity.js","../src/common/storage/local-storage.js","../src/common/timer/interaction-timer.js","../src/common/timer/timer.js","../src/common/timing/nav-timing.js","../src/common/timing/now.js","../src/common/timing/time-keeper.js","../src/common/unload/eol.js","../src/common/url/canonicalize-url.js","../src/common/url/clean-url.js","../src/common/url/encode.js","../src/common/url/extract-url.js","../src/common/url/location.js","../src/common/url/parse-url.js","../src/common/url/protocol.js","../src/common/util/attribute-size.js","../src/common/util/browser-stack-matchers.js","../src/common/util/console.js","../src/common/util/data-size.js","../src/common/util/event-origin.js","../src/common/util/feature-flags.js","../src/common/util/get-or-set.js","../src/common/util/invoke.js","../src/common/util/monkey-patched.js","../src/common/util/obfuscate.js","../src/common/util/short-circuit.js","../src/common/util/stringify.js","../src/common/util/submit-data.js","../src/common/util/text.js","../src/common/util/traverse.js","../src/common/util/type-check.js","../src/common/util/webdriver-detection.js","../src/common/v2/script-correlation.js","../src/common/v2/script-tracker.js","../src/common/v2/utils.js","../src/common/vitals/constants.js","../src/common/vitals/cumulative-layout-shift.js","../src/common/vitals/first-contentful-paint.js","../src/common/vitals/first-paint.js","../src/common/vitals/interaction-to-next-paint.js","../src/common/vitals/largest-contentful-paint.js","../src/common/vitals/load-time.js","../src/common/vitals/time-to-first-byte.js","../src/common/vitals/vital-metric.js","../src/common/window/load.js","../src/common/window/nreum.js","../src/common/window/page-visibility.js","../src/common/wrap/wrap-events.js","../src/common/wrap/wrap-fetch.js","../src/common/wrap/wrap-function.js","../src/common/wrap/wrap-history.js","../src/common/wrap/wrap-logger.js","../src/common/wrap/wrap-websocket.js","../src/common/wrap/wrap-xhr.js","../src/features/ajax/constants.js","../src/features/ajax/index.js","../src/features/ajax/aggregate/gql.js","../src/features/ajax/aggregate/index.js","../src/features/ajax/instrument/distributed-tracing.js","../src/features/ajax/instrument/index.js","../src/features/ajax/instrument/response-size.js","../src/features/generic_events/constants.js","../src/features/generic_events/index.js","../src/features/generic_events/aggregate/index.js","../src/features/generic_events/aggregate/user-actions/aggregated-user-action.js","../src/features/generic_events/aggregate/user-actions/user-actions-aggregator.js","../src/features/generic_events/instrument/index.js","../src/features/jserrors/constants.js","../src/features/jserrors/index.js","../src/features/jserrors/aggregate/canonical-function-name.js","../src/features/jserrors/aggregate/cause-string.js","../src/features/jserrors/aggregate/compute-stack-trace.js","../src/features/jserrors/aggregate/format-stack-trace.js","../src/features/jserrors/aggregate/index.js","../src/features/jserrors/aggregate/internal-errors.js","../src/features/jserrors/aggregate/string-hash-code.js","../src/features/jserrors/instrument/index.js","../src/features/jserrors/shared/cast-error.js","../src/features/jserrors/shared/uncaught-error.js","../src/features/logging/constants.js","../src/features/logging/index.js","../src/features/logging/aggregate/index.js","../src/features/logging/instrument/index.js","../src/features/logging/shared/log.js","../src/features/logging/shared/utils.js","../src/features/metrics/constants.js","../src/features/metrics/index.js","../src/features/metrics/aggregate/framework-detection.js","../src/features/metrics/aggregate/harvest-metadata.js","../src/features/metrics/aggregate/index.js","../src/features/metrics/instrument/index.js","../src/features/page_action/constants.js","../src/features/page_action/index.js","../src/features/page_action/instrument/index.js","../src/features/page_view_event/constants.js","../src/features/page_view_event/index.js","../src/features/page_view_event/aggregate/index.js","../src/features/page_view_event/aggregate/initialized-features.js","../src/features/page_view_event/instrument/index.js","../src/features/page_view_timing/constants.js","../src/features/page_view_timing/index.js","../src/features/page_view_timing/aggregate/index.js","../src/features/page_view_timing/instrument/index.js","../src/features/session_replay/constants.js","../src/features/session_replay/index.js","../src/features/session_replay/aggregate/index.js","../src/features/session_replay/instrument/index.js","../src/features/session_replay/shared/recorder-events.js","../src/features/session_replay/shared/recorder.js","../src/features/session_replay/shared/stylesheet-evaluator.js","../src/features/session_replay/shared/utils.js","../src/features/session_trace/constants.js","../src/features/session_trace/index.js","../src/features/session_trace/aggregate/index.js","../src/features/session_trace/aggregate/trace/node.js","../src/features/session_trace/aggregate/trace/storage.js","../src/features/session_trace/aggregate/trace/utils.js","../src/features/session_trace/instrument/index.js","../src/features/soft_navigations/constants.js","../src/features/soft_navigations/index.js","../src/features/soft_navigations/aggregate/ajax-node.js","../src/features/soft_navigations/aggregate/bel-node.js","../src/features/soft_navigations/aggregate/index.js","../src/features/soft_navigations/aggregate/initial-page-load-interaction.js","../src/features/soft_navigations/aggregate/interaction.js","../src/features/soft_navigations/instrument/index.js","../src/features/utils/agent-session.js","../src/features/utils/aggregate-base.js","../src/features/utils/event-buffer.js","../src/features/utils/feature-base.js","../src/features/utils/feature-gates.js","../src/features/utils/instrument-base.js","../src/interfaces/registered-entity.js","../src/loaders/agent-base.js","../src/loaders/agent.js","../src/loaders/api-base.js","../src/loaders/browser-agent.js","../src/loaders/micro-agent-base.js","../src/loaders/micro-agent.js","../src/loaders/api/addPageAction.js","../src/loaders/api/addRelease.js","../src/loaders/api/addToTrace.js","../src/loaders/api/consent.js","../src/loaders/api/constants.js","../src/loaders/api/finished.js","../src/loaders/api/interaction-types.js","../src/loaders/api/interaction.js","../src/loaders/api/log.js","../src/loaders/api/measure.js","../src/loaders/api/noticeError.js","../src/loaders/api/pauseReplay.js","../src/loaders/api/recordCustomEvent.js","../src/loaders/api/recordReplay.js","../src/loaders/api/register-api-types.js","../src/loaders/api/register.js","../src/loaders/api/setApplicationVersion.js","../src/loaders/api/setCustomAttribute.js","../src/loaders/api/setErrorHandler.js","../src/loaders/api/setPageViewName.js","../src/loaders/api/setUserId.js","../src/loaders/api/sharedHandlers.js","../src/loaders/api/start.js","../src/loaders/api/topLevelCallers.js","../src/loaders/api/wrapLogger.js","../src/loaders/configure/configure.js","../src/loaders/configure/nonce.js","../src/loaders/configure/public-path.js","../src/loaders/features/enabled-features.js","../src/loaders/features/featureDependencies.js","../src/loaders/features/features.js"],"version":"5.9.3"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
/** A special error used to short-circuit the error processing pipeline */
|
|
6
|
+
export class ShortCircuit extends Error {
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=short-circuit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"short-circuit.d.ts","sourceRoot":"","sources":["../../../../src/common/util/short-circuit.js"],"names":[],"mappings":"AAAA;;;GAGG;AACH,0EAA0E;AAC1E;CAA0C"}
|
|
@@ -14,25 +14,34 @@ export class Aggregate extends AggregateBase {
|
|
|
14
14
|
pve: string;
|
|
15
15
|
};
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
* by a newline character. Lines take the form `<functionName>@<url>:<lineNumber>`.
|
|
17
|
+
* Main entry point for processing JavaScript errors. This method orchestrates the complete error processing pipeline.
|
|
19
18
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
* Processing Flow:
|
|
20
|
+
* 1. Filter the error through the customer's onerror handler (if configured and not internal)
|
|
21
|
+
* 2. Compute the stack trace from the error object
|
|
22
|
+
* 3. Evaluate if the error should be swallowed (internal errors, known issues, etc.)
|
|
23
|
+
* 4. Derive target(s) for the error (MFE detection for v2 endpoints, or default target) - Note: "undefined" indicates the default target will be used
|
|
24
|
+
* 5. Store the error for each derived target. During storage (#storeJserrorForHarvest), duplication for MFE <-> container will be handled
|
|
25
|
+
*
|
|
26
|
+
* Important: "ShortCircuit" Pattern:
|
|
27
|
+
* Several steps in the pipeline can throw a ShortCircuit error to halt processing without
|
|
28
|
+
* treating it as a reportable error. This pattern is used when:
|
|
29
|
+
* - The customer's onerror handler returns a truthy value (excluding fingerprinting objects)
|
|
30
|
+
* - The error is identified as an internal error that shouldn't be reported
|
|
31
|
+
*
|
|
32
|
+
* When a ShortCircuit is thrown, processing stops immediately and the error is not stored.
|
|
33
|
+
* Any other thrown error is re-thrown as it represents an actual problem in the agent code.
|
|
25
34
|
*
|
|
26
|
-
* @param {Error|UncaughtError} err The error instance to be processed
|
|
27
|
-
* @param {number} time
|
|
28
|
-
* @param {boolean
|
|
29
|
-
* @param {object
|
|
30
|
-
* @param {boolean
|
|
31
|
-
* @param {string
|
|
32
|
-
* @param {object
|
|
33
|
-
* @returns
|
|
35
|
+
* @param {Error|UncaughtError} err - The error instance to be processed
|
|
36
|
+
* @param {number} [time] - The relative ms (to origin) timestamp of occurrence. Defaults to now()
|
|
37
|
+
* @param {boolean} [internal=false] - If the error was "caught" and deemed "internal" before reporting to the jserrors feature
|
|
38
|
+
* @param {object} [customAttributes] - Any custom attributes to be included in the error payload
|
|
39
|
+
* @param {boolean} [hasReplay=false] - A flag indicating if the error occurred during a replay session
|
|
40
|
+
* @param {string} [swallowReason] - A string indicating pre-defined reason if swallowing the error. Mainly used by internal error supportability metrics
|
|
41
|
+
* @param {object} [target] - The target to buffer and harvest to. If undefined, the default configuration target is used
|
|
42
|
+
* @returns {void}
|
|
34
43
|
*/
|
|
35
|
-
|
|
44
|
+
processError(err: Error | UncaughtError, time?: number, internal?: boolean, customAttributes?: object, hasReplay?: boolean, swallowReason?: string, target?: object): void;
|
|
36
45
|
#private;
|
|
37
46
|
}
|
|
38
47
|
export type StackInfo = import("./compute-stack-trace.js").StackInfo;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/aggregate/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/aggregate/index.js"],"names":[],"mappings":"AAyBA;;GAEG;AAEH;IACE,2BAAiC;IACjC,2BAuBC;IAlBC,kBAAuB;IACvB,eAAoB;IACpB,qBAA0B;IAC1B,qBAAwB;IAiB1B,oDAEC;IAED;;;MAcC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,kBATW,KAAK,GAAC,aAAa,SACnB,MAAM,aACN,OAAO,qBACP,MAAM,cACN,OAAO,kBACP,MAAM,WACN,MAAM,GACJ,IAAI,CAiBhB;;CAyQF;wBAtWY,OAAO,0BAA0B,EAAE,SAAS;8BAT3B,4BAA4B"}
|
package/package.json
CHANGED
|
@@ -19,8 +19,9 @@ import { AggregateBase } from '../../utils/aggregate-base'
|
|
|
19
19
|
import { now } from '../../../common/timing/now'
|
|
20
20
|
import { applyFnToProps } from '../../../common/util/traverse'
|
|
21
21
|
import { evaluateInternalError } from './internal-errors'
|
|
22
|
-
import { getVersion2Attributes } from '../../../common/v2/utils'
|
|
22
|
+
import { getRegisteredTargetsFromFilename, getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/v2/utils'
|
|
23
23
|
import { buildCauseString } from './cause-string'
|
|
24
|
+
import { ShortCircuit } from '../../../common/util/short-circuit'
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* @typedef {import('./compute-stack-trace.js').StackInfo} StackInfo
|
|
@@ -38,8 +39,8 @@ export class Aggregate extends AggregateBase {
|
|
|
38
39
|
this.pageviewReported = {}
|
|
39
40
|
this.errorOnPage = false
|
|
40
41
|
|
|
41
|
-
register('err',
|
|
42
|
-
register('ierr',
|
|
42
|
+
register('err', this.processError.bind(this), this.featureName, this.ee)
|
|
43
|
+
register('ierr', this.processError.bind(this), this.featureName, this.ee)
|
|
43
44
|
register('returnJserror', (jsErrorEvent, softNavAttrs) => this.#storeJserrorForHarvest(jsErrorEvent, softNavAttrs), this.featureName, this.ee)
|
|
44
45
|
|
|
45
46
|
// 0 == off, 1 == on
|
|
@@ -74,43 +75,67 @@ export class Aggregate extends AggregateBase {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
/**
|
|
77
|
-
*
|
|
78
|
-
* by a newline character. Lines take the form `<functionName>@<url>:<lineNumber>`.
|
|
78
|
+
* Main entry point for processing JavaScript errors. This method orchestrates the complete error processing pipeline.
|
|
79
79
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
80
|
+
* Processing Flow:
|
|
81
|
+
* 1. Filter the error through the customer's onerror handler (if configured and not internal)
|
|
82
|
+
* 2. Compute the stack trace from the error object
|
|
83
|
+
* 3. Evaluate if the error should be swallowed (internal errors, known issues, etc.)
|
|
84
|
+
* 4. Derive target(s) for the error (MFE detection for v2 endpoints, or default target) - Note: "undefined" indicates the default target will be used
|
|
85
|
+
* 5. Store the error for each derived target. During storage (#storeJserrorForHarvest), duplication for MFE <-> container will be handled
|
|
86
|
+
*
|
|
87
|
+
* Important: "ShortCircuit" Pattern:
|
|
88
|
+
* Several steps in the pipeline can throw a ShortCircuit error to halt processing without
|
|
89
|
+
* treating it as a reportable error. This pattern is used when:
|
|
90
|
+
* - The customer's onerror handler returns a truthy value (excluding fingerprinting objects)
|
|
91
|
+
* - The error is identified as an internal error that shouldn't be reported
|
|
92
|
+
*
|
|
93
|
+
* When a ShortCircuit is thrown, processing stops immediately and the error is not stored.
|
|
94
|
+
* Any other thrown error is re-thrown as it represents an actual problem in the agent code.
|
|
95
|
+
*
|
|
96
|
+
* @param {Error|UncaughtError} err - The error instance to be processed
|
|
97
|
+
* @param {number} [time] - The relative ms (to origin) timestamp of occurrence. Defaults to now()
|
|
98
|
+
* @param {boolean} [internal=false] - If the error was "caught" and deemed "internal" before reporting to the jserrors feature
|
|
99
|
+
* @param {object} [customAttributes] - Any custom attributes to be included in the error payload
|
|
100
|
+
* @param {boolean} [hasReplay=false] - A flag indicating if the error occurred during a replay session
|
|
101
|
+
* @param {string} [swallowReason] - A string indicating pre-defined reason if swallowing the error. Mainly used by internal error supportability metrics
|
|
102
|
+
* @param {object} [target] - The target to buffer and harvest to. If undefined, the default configuration target is used
|
|
103
|
+
* @returns {void}
|
|
82
104
|
*/
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
105
|
+
processError (err, time, internal, customAttributes, hasReplay, swallowReason, target) {
|
|
106
|
+
if (!err) return
|
|
107
|
+
time = time || now()
|
|
108
|
+
try {
|
|
109
|
+
const filterOutput = this.#filterError(err, internal)
|
|
110
|
+
const stackInfo = computeStackTrace(err)
|
|
111
|
+
this.#swallowError(stackInfo, internal, swallowReason)
|
|
112
|
+
this.#deriveTargets(stackInfo, target).forEach(target => {
|
|
113
|
+
this.#storeError(err, time, stackInfo, filterOutput, customAttributes, hasReplay, target)
|
|
114
|
+
})
|
|
115
|
+
} catch (e) {
|
|
116
|
+
if (!(e instanceof ShortCircuit)) {
|
|
117
|
+
throw e
|
|
118
|
+
}
|
|
94
119
|
}
|
|
95
|
-
|
|
96
|
-
return canonicalStackString
|
|
97
120
|
}
|
|
98
121
|
|
|
99
122
|
/**
|
|
123
|
+
* Filters an error through the customer's configured onerror handler.
|
|
100
124
|
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
125
|
+
* If the customer has configured a custom onerror handler and the error is not internal,
|
|
126
|
+
* this method invokes that handler. The handler's return value determines whether the error
|
|
127
|
+
* should be reported:
|
|
128
|
+
* - Falsey values (false, null, undefined, etc.) → Report the error normally
|
|
129
|
+
* - Truthy non-object values → Don't report (throws ShortCircuit)
|
|
130
|
+
* - Object with 'group' property (non-empty string) → Report with fingerprinting label
|
|
131
|
+
* - Any other truthy value → Don't report (throws ShortCircuit)
|
|
132
|
+
*
|
|
133
|
+
* @param {Error|UncaughtError} err - The error to filter
|
|
134
|
+
* @param {boolean} internal - Whether this is an internal error (internal errors skip filtering)
|
|
135
|
+
* @returns {undefined|object} The filter output. If an object with 'group' property, contains fingerprinting data
|
|
136
|
+
* @throws {ShortCircuit} When the error should not be reported based on the filter output
|
|
109
137
|
*/
|
|
110
|
-
|
|
111
|
-
if (!err) return
|
|
112
|
-
// are we in an interaction
|
|
113
|
-
time = time || now()
|
|
138
|
+
#filterError (err, internal) {
|
|
114
139
|
let filterOutput
|
|
115
140
|
|
|
116
141
|
if (!internal && this.agentRef.runtime.onerror) {
|
|
@@ -118,20 +143,92 @@ export class Aggregate extends AggregateBase {
|
|
|
118
143
|
if (filterOutput && !(typeof filterOutput.group === 'string' && filterOutput.group.length)) {
|
|
119
144
|
// All truthy values mean don't report (store) the error, per backwards-compatible usage,
|
|
120
145
|
// - EXCEPT if a fingerprinting label is returned, via an object with key of 'group' and value of non-empty string
|
|
121
|
-
|
|
146
|
+
throw new ShortCircuit()
|
|
122
147
|
}
|
|
123
148
|
// Again as with previous usage, all falsey values would include the error.
|
|
124
149
|
}
|
|
150
|
+
return filterOutput
|
|
151
|
+
}
|
|
125
152
|
|
|
126
|
-
|
|
127
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Evaluates whether an error should be swallowed (not reported) based on internal error criteria.
|
|
155
|
+
*
|
|
156
|
+
* This method uses the evaluateInternalError function to determine if the error matches known
|
|
157
|
+
* internal error patterns (e.g., errors from the agent itself, known browser issues, etc.).
|
|
158
|
+
* If the error should be swallowed, a supportability metric is recorded and processing is halted.
|
|
159
|
+
*
|
|
160
|
+
* @param {StackInfo} stackInfo - The computed stack trace information
|
|
161
|
+
* @param {boolean} internal - Whether the error was marked as internal
|
|
162
|
+
* @param {string} [swallowReason] - Optional pre-determined reason for swallowing
|
|
163
|
+
* @returns {void}
|
|
164
|
+
* @throws {ShortCircuit} When the error should be swallowed and not reported
|
|
165
|
+
*/
|
|
166
|
+
#swallowError (stackInfo, internal, swallowReason) {
|
|
128
167
|
const { shouldSwallow, reason } = evaluateInternalError(stackInfo, internal, swallowReason)
|
|
129
168
|
if (shouldSwallow) {
|
|
130
169
|
this.reportSupportabilityMetric('Internal/Error/' + reason)
|
|
131
|
-
|
|
170
|
+
throw new ShortCircuit()
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Derives the appropriate targets for reporting the given stack information.
|
|
176
|
+
*
|
|
177
|
+
* Targets represent the entities that should receive the error data. This is particularly
|
|
178
|
+
* important for Micro Frontend (MFE) scenarios where errors may need to be reported to
|
|
179
|
+
* different applications.
|
|
180
|
+
*
|
|
181
|
+
* Logic:
|
|
182
|
+
* - If a target is explicitly provided (e.g., from the register API), use it
|
|
183
|
+
* - For v2 endpoints without an explicit target, scan stack frames to detect MFE sources
|
|
184
|
+
* - If no MFE is detected or v2 is not enabled, use undefined (default target)
|
|
185
|
+
*
|
|
186
|
+
* @param {StackInfo} stackInfo - The computed stack trace information containing frames
|
|
187
|
+
* @param {object} [target] - Explicitly provided target, typically from the register API
|
|
188
|
+
* @returns {Array<object|undefined>} Array of targets to report the error to. Always contains at least one element.
|
|
189
|
+
*/
|
|
190
|
+
#deriveTargets (stackInfo, target) {
|
|
191
|
+
const targets = []
|
|
192
|
+
if (target) {
|
|
193
|
+
// reported by the register API directly
|
|
194
|
+
targets.push(target)
|
|
195
|
+
} else {
|
|
196
|
+
// we dont know if this is MFE yet, we need to figure it out.
|
|
197
|
+
if (this.harvestEndpointVersion === 2) {
|
|
198
|
+
for (const frame of stackInfo.frames) {
|
|
199
|
+
targets.push(...getRegisteredTargetsFromFilename(frame.url, this.agentRef))
|
|
200
|
+
if (targets.length) break
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (!targets.length) targets.push(undefined)
|
|
132
204
|
}
|
|
205
|
+
return targets
|
|
206
|
+
}
|
|
133
207
|
|
|
134
|
-
|
|
208
|
+
/**
|
|
209
|
+
* Stores error data for eventual harvesting and transmission to the backend.
|
|
210
|
+
*
|
|
211
|
+
* This method processes the error through several stages:
|
|
212
|
+
* 1. Build canonical stack string for cross-browser error grouping
|
|
213
|
+
* 2. Build cause chain string if the error has a cause property
|
|
214
|
+
* 3. Create params object with error metadata (stack hash, class, message, etc.)
|
|
215
|
+
* 4. Create bucket hash for internal deduplication
|
|
216
|
+
* 5. Store stack trace on first occurrence of this error
|
|
217
|
+
* 6. Add custom attributes and send to other features (trace, replay)
|
|
218
|
+
* 7. Route through soft nav if enabled, or directly to harvest storage
|
|
219
|
+
* 8. Handle MFE duplication for v2 endpoints if needed
|
|
220
|
+
*
|
|
221
|
+
* @param {Error|UncaughtError} err - The error instance to be processed
|
|
222
|
+
* @param {number} time - The relative ms (to origin) timestamp of occurrence
|
|
223
|
+
* @param {StackInfo} stackInfo - The computed stack trace information
|
|
224
|
+
* @param {object} [filterOutput] - Output from the customer's onerror handler, may contain fingerprinting group
|
|
225
|
+
* @param {object} [customAttributes] - Any custom attributes to be included in the error payload
|
|
226
|
+
* @param {boolean} [hasReplay=false] - A flag indicating if the error occurred during a replay session
|
|
227
|
+
* @param {object} [target] - The target to buffer and harvest to. If undefined, the default configuration target is used
|
|
228
|
+
* @returns {void}
|
|
229
|
+
*/
|
|
230
|
+
#storeError (err, time, stackInfo, filterOutput, customAttributes, hasReplay, target) {
|
|
231
|
+
var canonicalStackString = this.#buildCanonicalStackString(stackInfo)
|
|
135
232
|
|
|
136
233
|
const causeStackString = buildCauseString(err)
|
|
137
234
|
|
|
@@ -154,7 +251,7 @@ export class Aggregate extends AggregateBase {
|
|
|
154
251
|
* the canonical stack trace excludes items like the column number increasing the hit-rate of different errors potentially
|
|
155
252
|
* bucketing and ultimately resulting in the loss of data in NR1.
|
|
156
253
|
*/
|
|
157
|
-
var bucketHash = stringHashCode(`${stackInfo.name}_${stackInfo.message}_${stackInfo.stackString}_${params.hasReplay ? 1 : 0}_${target?.id || 'container'}`)
|
|
254
|
+
var bucketHash = stringHashCode(`${stackInfo.name}_${stackInfo.message}_${stackInfo.stackString}_${params.hasReplay ? 1 : 0}_${target?.id || 'container'}_${target?.instance || ''}`)
|
|
158
255
|
|
|
159
256
|
if (!this.stackReported[bucketHash]) {
|
|
160
257
|
this.stackReported[bucketHash] = true
|
|
@@ -196,23 +293,78 @@ export class Aggregate extends AggregateBase {
|
|
|
196
293
|
if (softNavInUse) { // pass the error to soft nav for evaluation - it will return it via 'returnJserror' when interaction is resolved
|
|
197
294
|
handle('jserror', [jsErrorEvent], undefined, FEATURE_NAMES.softNav, this.ee)
|
|
198
295
|
} else {
|
|
199
|
-
this.#storeJserrorForHarvest(jsErrorEvent
|
|
296
|
+
this.#storeJserrorForHarvest(jsErrorEvent)
|
|
200
297
|
}
|
|
201
298
|
}
|
|
202
299
|
|
|
203
300
|
// always add directly if scoped to a sub-entity, the other pathways above will be deterministic if the main agent should procede
|
|
204
|
-
if (target) this.#storeJserrorForHarvest(
|
|
301
|
+
if (target) this.#storeJserrorForHarvest(jsErrorEvent, {}, target)
|
|
205
302
|
}
|
|
206
303
|
|
|
207
|
-
|
|
208
|
-
|
|
304
|
+
/**
|
|
305
|
+
* Builds a standardized (canonical) stack trace string from the frames in the given `stackInfo` object.
|
|
306
|
+
*
|
|
307
|
+
* The canonical format is used for cross-browser error grouping in NR1, as different browsers
|
|
308
|
+
* format stack traces differently. Each frame is separated by a newline character and takes
|
|
309
|
+
* the form: `<functionName>@<url>:<lineNumber>`
|
|
310
|
+
*
|
|
311
|
+
* Note: Column numbers are intentionally excluded from the canonical format to improve
|
|
312
|
+
* grouping accuracy, as the same error across different minified builds might have different
|
|
313
|
+
* column numbers but should still be grouped together.
|
|
314
|
+
*
|
|
315
|
+
* Example output:
|
|
316
|
+
* ```
|
|
317
|
+
* handleClick@https://example.com/app.js:42
|
|
318
|
+
* EventEmitter.emit@https://example.com/vendor.js:1337
|
|
319
|
+
* ```
|
|
320
|
+
*
|
|
321
|
+
* @param {StackInfo} stackInfo - An object containing parsed stack frames from computeStackTrace
|
|
322
|
+
* @returns {string} A canonical stack string built from the URLs and function names in the given `stackInfo` object
|
|
323
|
+
*/
|
|
324
|
+
#buildCanonicalStackString (stackInfo) {
|
|
325
|
+
var canonicalStackString = ''
|
|
326
|
+
|
|
327
|
+
for (var i = 0; i < stackInfo.frames.length; i++) {
|
|
328
|
+
var frame = stackInfo.frames[i]
|
|
329
|
+
var func = canonicalFunctionName(frame.func)
|
|
330
|
+
|
|
331
|
+
if (canonicalStackString) canonicalStackString += '\n'
|
|
332
|
+
if (func) canonicalStackString += func + '@'
|
|
333
|
+
if (typeof frame.url === 'string') canonicalStackString += frame.url
|
|
334
|
+
if (frame.line) canonicalStackString += ':' + frame.line
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return canonicalStackString
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Adds a processed error to the harvest buffer with all custom attributes merged.
|
|
342
|
+
*
|
|
343
|
+
* This is the final step before an error is stored to be sent to the backend. It handles:
|
|
344
|
+
* - Merging all custom attributes (global, soft nav, MFE, and local)
|
|
345
|
+
* - Creating a unique aggregate hash for deduplication
|
|
346
|
+
* - Adding the error to the events buffer for harvest
|
|
347
|
+
* - Duplicating the error for MFE scenarios when needed (v2 endpoints)
|
|
348
|
+
*
|
|
349
|
+
* Custom Attribute Precedence (lowest to highest):
|
|
350
|
+
* 1. Global jsAttributes from agent config
|
|
351
|
+
* 2. Soft navigation attributes (if from a soft nav interaction)
|
|
352
|
+
* 3. MFE v2 attributes (source/parent metadata)
|
|
353
|
+
* 4. Local custom attributes passed with the specific error
|
|
354
|
+
*
|
|
355
|
+
* @param {Array} errorInfoArr - Array containing [type, bucketHash, params, metrics, customAttributes, target]
|
|
356
|
+
* @param {object} [attrs={}] - Additional attributes to merge (e.g., from soft nav interactions)
|
|
357
|
+
* @returns {void}
|
|
358
|
+
*/
|
|
359
|
+
#storeJserrorForHarvest (errorInfoArr, attrs = {}, target) {
|
|
360
|
+
let [type, bucketHash, params, newMetrics, localAttrs] = errorInfoArr
|
|
209
361
|
const allCustomAttrs = {
|
|
210
362
|
/** MFE specific attributes if in "multiple" mode (ie consumer version 2) */
|
|
211
363
|
...getVersion2Attributes(target, this)
|
|
212
364
|
}
|
|
213
365
|
|
|
214
366
|
Object.entries(this.agentRef.info.jsAttributes).forEach(([k, v]) => setCustom(k, v))
|
|
215
|
-
Object.entries(
|
|
367
|
+
Object.entries(attrs).forEach(([k, v]) => setCustom(k, v)) // when an ixn finishes, it'll pass attrs specific to the ixn; if no associated ixn, this defaults to empty
|
|
216
368
|
if (params.browserInteractionId) bucketHash += params.browserInteractionId
|
|
217
369
|
if (localAttrs) Object.entries(localAttrs).forEach(([k, v]) => setCustom(k, v)) // local custom attrs are applied in either case with the highest precedence
|
|
218
370
|
|
|
@@ -221,6 +373,11 @@ export class Aggregate extends AggregateBase {
|
|
|
221
373
|
|
|
222
374
|
this.events.add([type, aggregateHash, params, newMetrics, allCustomAttrs])
|
|
223
375
|
|
|
376
|
+
if (shouldDuplicate(target, this)) {
|
|
377
|
+
// Clone the array with new object references to prevent deduplication in the shared aggregator
|
|
378
|
+
this.#storeJserrorForHarvest([type, bucketHash, { ...params }, { ...newMetrics }, localAttrs && { ...localAttrs }], getVersion2DuplicationAttributes(target, this))
|
|
379
|
+
}
|
|
380
|
+
|
|
224
381
|
function setCustom (key, val) {
|
|
225
382
|
allCustomAttrs[key] = (val && typeof val === 'object' ? stringify(val) : val)
|
|
226
383
|
}
|