@newrelic/browser-agent 1.258.0 → 1.258.2
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/CHANGELOG.md +18 -0
- package/README.md +3 -4
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/deny-list/deny-list.js +6 -8
- package/dist/cjs/common/vitals/time-to-first-byte.js +9 -1
- package/dist/cjs/features/ajax/instrument/index.js +11 -8
- package/dist/cjs/features/jserrors/aggregate/index.js +7 -2
- package/dist/cjs/features/jserrors/instrument/index.js +4 -87
- package/dist/cjs/features/jserrors/shared/cast-error.js +66 -0
- package/dist/cjs/features/jserrors/{instrument → shared}/uncaught-error.js +4 -2
- package/dist/cjs/features/session_trace/aggregate/index.js +6 -2
- package/dist/cjs/features/spa/aggregate/index.js +11 -1
- package/dist/cjs/features/spa/instrument/index.js +9 -0
- package/dist/cjs/features/utils/instrument-base.js +1 -1
- package/dist/cjs/loaders/api/api.js +6 -14
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/deny-list/deny-list.js +5 -8
- package/dist/esm/common/vitals/time-to-first-byte.js +9 -1
- package/dist/esm/features/ajax/instrument/index.js +11 -8
- package/dist/esm/features/jserrors/aggregate/index.js +7 -2
- package/dist/esm/features/jserrors/instrument/index.js +4 -87
- package/dist/esm/features/jserrors/shared/cast-error.js +59 -0
- package/dist/esm/features/jserrors/{instrument → shared}/uncaught-error.js +5 -2
- package/dist/esm/features/session_trace/aggregate/index.js +6 -2
- package/dist/esm/features/spa/aggregate/index.js +11 -1
- package/dist/esm/features/spa/instrument/index.js +9 -0
- package/dist/esm/features/utils/instrument-base.js +1 -1
- package/dist/esm/loaders/api/api.js +6 -14
- package/dist/types/common/deny-list/deny-list.d.ts +1 -0
- package/dist/types/common/deny-list/deny-list.d.ts.map +1 -1
- package/dist/types/features/ajax/instrument/index.d.ts.map +1 -1
- package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/jserrors/instrument/index.d.ts.map +1 -1
- package/dist/types/features/jserrors/shared/cast-error.d.ts +21 -0
- package/dist/types/features/jserrors/shared/cast-error.d.ts.map +1 -0
- package/dist/types/features/jserrors/{instrument → shared}/uncaught-error.d.ts +3 -2
- package/dist/types/features/jserrors/shared/uncaught-error.d.ts.map +1 -0
- package/dist/types/features/session_trace/aggregate/index.d.ts +1 -0
- package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/spa/instrument/index.d.ts.map +1 -1
- package/dist/types/loaders/api/api.d.ts +1 -1
- package/dist/types/loaders/api/api.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/common/deny-list/deny-list.js +6 -7
- package/src/common/vitals/time-to-first-byte.js +8 -1
- package/src/features/ajax/instrument/index.js +11 -9
- package/src/features/jserrors/aggregate/index.js +8 -2
- package/src/features/jserrors/instrument/index.js +4 -99
- package/src/features/jserrors/shared/cast-error.js +69 -0
- package/src/features/jserrors/{instrument → shared}/uncaught-error.js +5 -2
- package/src/features/session_trace/aggregate/index.js +6 -2
- package/src/features/spa/aggregate/index.js +10 -1
- package/src/features/spa/instrument/index.js +3 -0
- package/src/features/utils/instrument-base.js +1 -1
- package/src/loaders/api/api.js +6 -15
- package/dist/types/features/jserrors/instrument/uncaught-error.d.ts.map +0 -1
|
@@ -17,6 +17,7 @@ import { FEATURE_NAME } from '../constants'
|
|
|
17
17
|
import { FEATURE_NAMES } from '../../../loaders/features/features'
|
|
18
18
|
import { SUPPORTABILITY_METRIC } from '../../metrics/constants'
|
|
19
19
|
import { now } from '../../../common/timing/now'
|
|
20
|
+
import { hasUndefinedHostname } from '../../../common/deny-list/deny-list'
|
|
20
21
|
|
|
21
22
|
var handlers = ['load', 'error', 'abort', 'timeout']
|
|
22
23
|
var handlersLen = handlers.length
|
|
@@ -338,18 +339,18 @@ function subscribeToEvents (agentIdentifier, ee, handler, dt) {
|
|
|
338
339
|
// eslint-disable-next-line handle-callback-err
|
|
339
340
|
function onFetchDone (_, res) {
|
|
340
341
|
this.endTime = now()
|
|
341
|
-
if (!this.params) {
|
|
342
|
-
|
|
343
|
-
|
|
342
|
+
if (!this.params) this.params = {}
|
|
343
|
+
if (hasUndefinedHostname(this.params)) return // don't bother with fetch to url with no hostname
|
|
344
|
+
|
|
344
345
|
this.params.status = res ? res.status : 0
|
|
345
346
|
|
|
346
347
|
// convert rxSize to a number
|
|
347
|
-
|
|
348
|
+
let responseSize
|
|
348
349
|
if (typeof this.rxSize === 'string' && this.rxSize.length > 0) {
|
|
349
350
|
responseSize = +this.rxSize
|
|
350
351
|
}
|
|
351
352
|
|
|
352
|
-
|
|
353
|
+
const metrics = {
|
|
353
354
|
txSize: this.txSize,
|
|
354
355
|
rxSize: responseSize,
|
|
355
356
|
duration: now() - this.startTime
|
|
@@ -360,17 +361,18 @@ function subscribeToEvents (agentIdentifier, ee, handler, dt) {
|
|
|
360
361
|
|
|
361
362
|
// Create report for XHR request that has finished
|
|
362
363
|
function end (xhr) {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
364
|
+
const params = this.params
|
|
365
|
+
const metrics = this.metrics
|
|
366
366
|
if (this.ended) return
|
|
367
367
|
this.ended = true
|
|
368
368
|
|
|
369
|
-
for (
|
|
369
|
+
for (let i = 0; i < handlersLen; i++) {
|
|
370
370
|
xhr.removeEventListener(handlers[i], this.listener, false)
|
|
371
371
|
}
|
|
372
372
|
|
|
373
373
|
if (params.aborted) return
|
|
374
|
+
if (hasUndefinedHostname(params)) return // don't bother with XHR of url with no hostname
|
|
375
|
+
|
|
374
376
|
metrics.duration = now() - this.startTime
|
|
375
377
|
if (!this.loadCaptureCalled && xhr.readyState === 4) {
|
|
376
378
|
captureXhrData(this, xhr)
|
|
@@ -138,6 +138,7 @@ export class Aggregate extends AggregateBase {
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
storeError (err, time, internal, customAttributes, hasReplay) {
|
|
141
|
+
if (!err) return
|
|
141
142
|
// are we in an interaction
|
|
142
143
|
time = time || now()
|
|
143
144
|
const agentRuntime = getRuntime(this.agentIdentifier)
|
|
@@ -202,15 +203,20 @@ export class Aggregate extends AggregateBase {
|
|
|
202
203
|
|
|
203
204
|
// Trace sends the error in its payload, and both trace & replay simply listens for any error to occur.
|
|
204
205
|
const jsErrorEvent = [type, bucketHash, params, newMetrics, customAttributes]
|
|
205
|
-
handle('
|
|
206
|
+
handle('trace-jserror', jsErrorEvent, undefined, FEATURE_NAMES.sessionTrace, this.ee)
|
|
206
207
|
// still send EE events for other features such as above, but stop this one from aggregating internal data
|
|
207
208
|
if (this.blocked) return
|
|
208
209
|
|
|
210
|
+
if (err?.__newrelic?.[this.agentIdentifier]) {
|
|
211
|
+
params._interactionId = err.__newrelic[this.agentIdentifier].interactionId
|
|
212
|
+
params._interactionNodeId = err.__newrelic[this.agentIdentifier].interactionNodeId
|
|
213
|
+
}
|
|
214
|
+
|
|
209
215
|
const softNavInUse = Boolean(getNREUMInitializedAgent(this.agentIdentifier)?.features[FEATURE_NAMES.softNav])
|
|
210
216
|
// Note: the following are subject to potential race cond wherein if the other feature aren't fully initialized, it'll be treated as there being no associated interaction.
|
|
211
217
|
// They each will also tack on their respective properties to the params object as part of the decision flow.
|
|
212
218
|
if (softNavInUse) handle('jserror', [params, time], undefined, FEATURE_NAMES.softNav, this.ee)
|
|
213
|
-
else handle('
|
|
219
|
+
else handle('spa-jserror', jsErrorEvent, undefined, FEATURE_NAMES.spa, this.ee)
|
|
214
220
|
|
|
215
221
|
if (params.browserInteractionId && !params._softNavFinished) { // hold onto the error until the in-progress interaction is done, eithered saved or discarded
|
|
216
222
|
this.bufferedErrorsUnderSpa[params.browserInteractionId] ??= []
|
|
@@ -9,15 +9,13 @@ import { FEATURE_NAME } from '../constants'
|
|
|
9
9
|
import { FEATURE_NAMES } from '../../../loaders/features/features'
|
|
10
10
|
import { globalScope } from '../../../common/constants/runtime'
|
|
11
11
|
import { eventListenerOpts } from '../../../common/event-listener/event-listener-opts'
|
|
12
|
-
import { stringify } from '../../../common/util/stringify'
|
|
13
|
-
import { UncaughtError } from './uncaught-error'
|
|
14
12
|
import { now } from '../../../common/timing/now'
|
|
15
13
|
import { SR_EVENT_EMITTER_TYPES } from '../../session_replay/constants'
|
|
14
|
+
import { castError, castErrorEvent, castPromiseRejectionEvent } from '../shared/cast-error'
|
|
16
15
|
|
|
17
16
|
export class Instrument extends InstrumentBase {
|
|
18
17
|
static featureName = FEATURE_NAME
|
|
19
18
|
|
|
20
|
-
#seenErrors = new Set()
|
|
21
19
|
#replayRunning = false
|
|
22
20
|
|
|
23
21
|
constructor (agentIdentifier, aggregator, auto = true) {
|
|
@@ -28,17 +26,9 @@ export class Instrument extends InstrumentBase {
|
|
|
28
26
|
this.removeOnAbort = new AbortController()
|
|
29
27
|
} catch (e) {}
|
|
30
28
|
|
|
31
|
-
// Capture function errors early in case the spa feature is loaded
|
|
32
|
-
this.ee.on('fn-err', (args, obj, error) => {
|
|
33
|
-
if (!this.abortHandler || this.#seenErrors.has(error)) return
|
|
34
|
-
this.#seenErrors.add(error)
|
|
35
|
-
|
|
36
|
-
handle('err', [this.#castError(error), now()], undefined, FEATURE_NAMES.jserrors, this.ee)
|
|
37
|
-
})
|
|
38
|
-
|
|
39
29
|
this.ee.on('internal-error', (error) => {
|
|
40
30
|
if (!this.abortHandler) return
|
|
41
|
-
handle('ierr', [
|
|
31
|
+
handle('ierr', [castError(error), now(), true, {}, this.#replayRunning], undefined, FEATURE_NAMES.jserrors, this.ee)
|
|
42
32
|
})
|
|
43
33
|
|
|
44
34
|
this.ee.on(SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, (isRunning) => {
|
|
@@ -47,22 +37,12 @@ export class Instrument extends InstrumentBase {
|
|
|
47
37
|
|
|
48
38
|
globalScope.addEventListener('unhandledrejection', (promiseRejectionEvent) => {
|
|
49
39
|
if (!this.abortHandler) return
|
|
50
|
-
handle('err', [
|
|
40
|
+
handle('err', [castPromiseRejectionEvent(promiseRejectionEvent), now(), false, { unhandledPromiseRejection: 1 }, this.#replayRunning], undefined, FEATURE_NAMES.jserrors, this.ee)
|
|
51
41
|
}, eventListenerOpts(false, this.removeOnAbort?.signal))
|
|
52
42
|
|
|
53
43
|
globalScope.addEventListener('error', (errorEvent) => {
|
|
54
44
|
if (!this.abortHandler) return
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* If the spa feature is loaded, errors may already have been captured in the `fn-err` listener above.
|
|
58
|
-
* This ensures those errors are not captured twice.
|
|
59
|
-
*/
|
|
60
|
-
if (this.#seenErrors.has(errorEvent.error)) {
|
|
61
|
-
this.#seenErrors.delete(errorEvent.error)
|
|
62
|
-
return
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
handle('err', [this.#castErrorEvent(errorEvent), now(), false, {}, this.#replayRunning], undefined, FEATURE_NAMES.jserrors, this.ee)
|
|
45
|
+
handle('err', [castErrorEvent(errorEvent), now(), false, {}, this.#replayRunning], undefined, FEATURE_NAMES.jserrors, this.ee)
|
|
66
46
|
}, eventListenerOpts(false, this.removeOnAbort?.signal))
|
|
67
47
|
|
|
68
48
|
this.abortHandler = this.#abort // we also use this as a flag to denote that the feature is active or on and handling errors
|
|
@@ -72,81 +52,6 @@ export class Instrument extends InstrumentBase {
|
|
|
72
52
|
/** Restoration and resource release tasks to be done if JS error loader is being aborted. Unwind changes to globals. */
|
|
73
53
|
#abort () {
|
|
74
54
|
this.removeOnAbort?.abort()
|
|
75
|
-
this.#seenErrors.clear()
|
|
76
55
|
this.abortHandler = undefined // weakly allow this abort op to run only once
|
|
77
56
|
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Any value can be used with the `throw` keyword. This function ensures that the value is
|
|
81
|
-
* either a proper Error instance or attempts to convert it to an UncaughtError instance.
|
|
82
|
-
* @param {any} error The value thrown
|
|
83
|
-
* @returns {Error|UncaughtError} The converted error instance
|
|
84
|
-
*/
|
|
85
|
-
#castError (error) {
|
|
86
|
-
if (error instanceof Error) {
|
|
87
|
-
return error
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* The thrown value may contain a message property. If it does, try to treat the thrown
|
|
92
|
-
* value as an Error-like object.
|
|
93
|
-
*/
|
|
94
|
-
if (typeof error?.message !== 'undefined') {
|
|
95
|
-
return new UncaughtError(
|
|
96
|
-
error.message,
|
|
97
|
-
error.filename || error.sourceURL,
|
|
98
|
-
error.lineno || error.line,
|
|
99
|
-
error.colno || error.col
|
|
100
|
-
)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return new UncaughtError(typeof error === 'string' ? error : stringify(error))
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Attempts to convert a PromiseRejectionEvent object to an Error object
|
|
108
|
-
* @param {PromiseRejectionEvent} unhandledRejectionEvent The unhandled promise rejection event
|
|
109
|
-
* @returns {Error} An Error object with the message as the casted reason
|
|
110
|
-
*/
|
|
111
|
-
#castPromiseRejectionEvent (promiseRejectionEvent) {
|
|
112
|
-
let prefix = 'Unhandled Promise Rejection: '
|
|
113
|
-
|
|
114
|
-
if (promiseRejectionEvent?.reason instanceof Error) {
|
|
115
|
-
try {
|
|
116
|
-
promiseRejectionEvent.reason.message = prefix + promiseRejectionEvent.reason.message
|
|
117
|
-
return promiseRejectionEvent.reason
|
|
118
|
-
} catch (e) {
|
|
119
|
-
return promiseRejectionEvent.reason
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (typeof promiseRejectionEvent.reason === 'undefined') return new UncaughtError(prefix)
|
|
124
|
-
|
|
125
|
-
const error = this.#castError(promiseRejectionEvent.reason)
|
|
126
|
-
error.message = prefix + error.message
|
|
127
|
-
return error
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Attempts to convert an ErrorEvent object to an Error object
|
|
132
|
-
* @param {ErrorEvent} errorEvent The error event
|
|
133
|
-
* @returns {Error|UncaughtError} The error event converted to an Error object
|
|
134
|
-
*/
|
|
135
|
-
#castErrorEvent (errorEvent) {
|
|
136
|
-
if (errorEvent.error instanceof SyntaxError && !/:\d+$/.test(errorEvent.error.stack?.trim())) {
|
|
137
|
-
const error = new UncaughtError(errorEvent.message, errorEvent.filename, errorEvent.lineno, errorEvent.colno)
|
|
138
|
-
error.name = SyntaxError.name
|
|
139
|
-
return error
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (errorEvent.error instanceof Error) {
|
|
143
|
-
return errorEvent.error
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Older browsers do not contain the `error` property on the ErrorEvent instance.
|
|
148
|
-
* https://caniuse.com/mdn-api_errorevent_error
|
|
149
|
-
*/
|
|
150
|
-
return new UncaughtError(errorEvent.message, errorEvent.filename, errorEvent.lineno, errorEvent.colno)
|
|
151
|
-
}
|
|
152
57
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { UncaughtError } from './uncaught-error'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Any value can be used with the `throw` keyword. This function ensures that the value is
|
|
5
|
+
* either a proper Error instance or attempts to convert it to an UncaughtError instance.
|
|
6
|
+
* @param {any} error The value thrown
|
|
7
|
+
* @returns {Error|UncaughtError} The converted error instance
|
|
8
|
+
*/
|
|
9
|
+
export function castError (error) {
|
|
10
|
+
/** Sometimes a browser can emit an error object with no stack */
|
|
11
|
+
if (canTrustError(error)) {
|
|
12
|
+
return error
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The thrown value may contain a message property. If it does, try to treat the thrown
|
|
17
|
+
* value as an Error-like object.
|
|
18
|
+
*/
|
|
19
|
+
return new UncaughtError(
|
|
20
|
+
error?.message !== undefined ? error.message : error,
|
|
21
|
+
error?.filename || error?.sourceURL,
|
|
22
|
+
error?.lineno || error?.line,
|
|
23
|
+
error?.colno || error?.col,
|
|
24
|
+
error?.__newrelic
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Attempts to convert a PromiseRejectionEvent object to an Error object
|
|
30
|
+
* @param {PromiseRejectionEvent} unhandledRejectionEvent The unhandled promise rejection event
|
|
31
|
+
* @returns {Error} An Error object with the message as the casted reason
|
|
32
|
+
*/
|
|
33
|
+
export function castPromiseRejectionEvent (promiseRejectionEvent) {
|
|
34
|
+
let prefix = 'Unhandled Promise Rejection'
|
|
35
|
+
|
|
36
|
+
if (canTrustError(promiseRejectionEvent?.reason)) {
|
|
37
|
+
try {
|
|
38
|
+
promiseRejectionEvent.reason.message = prefix + ': ' + promiseRejectionEvent.reason.message
|
|
39
|
+
return castError(promiseRejectionEvent.reason)
|
|
40
|
+
} catch (e) {
|
|
41
|
+
return castError(promiseRejectionEvent.reason)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (typeof promiseRejectionEvent.reason === 'undefined') return castError(prefix)
|
|
46
|
+
|
|
47
|
+
const error = castError(promiseRejectionEvent.reason)
|
|
48
|
+
error.message = prefix + ': ' + error?.message
|
|
49
|
+
return error
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Attempts to convert an ErrorEvent object to an Error object
|
|
54
|
+
* @param {ErrorEvent} errorEvent The error event
|
|
55
|
+
* @returns {Error|UncaughtError} The error event converted to an Error object
|
|
56
|
+
*/
|
|
57
|
+
export function castErrorEvent (errorEvent) {
|
|
58
|
+
if (errorEvent.error instanceof SyntaxError && !/:\d+$/.test(errorEvent.error.stack?.trim())) {
|
|
59
|
+
const error = new UncaughtError(errorEvent.message, errorEvent.filename, errorEvent.lineno, errorEvent.colno, errorEvent.error.__newrelic)
|
|
60
|
+
error.name = SyntaxError.name
|
|
61
|
+
return error
|
|
62
|
+
}
|
|
63
|
+
if (canTrustError(errorEvent.error)) return errorEvent.error
|
|
64
|
+
return castError(errorEvent)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function canTrustError (error) {
|
|
68
|
+
return error instanceof Error && !!error.stack
|
|
69
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { stringify } from '../../../common/util/stringify'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Represents an uncaught non Error type error. This class does
|
|
3
5
|
* not extend the Error class to prevent an invalid stack trace
|
|
@@ -5,11 +7,12 @@
|
|
|
5
7
|
* do not use the Error class (strings, etc) to an object.
|
|
6
8
|
*/
|
|
7
9
|
export class UncaughtError {
|
|
8
|
-
constructor (message, filename, lineno, colno) {
|
|
10
|
+
constructor (message, filename, lineno, colno, newrelic) {
|
|
9
11
|
this.name = 'UncaughtError'
|
|
10
|
-
this.message = message
|
|
12
|
+
this.message = typeof message === 'string' ? message : stringify(message)
|
|
11
13
|
this.sourceURL = filename
|
|
12
14
|
this.line = lineno
|
|
13
15
|
this.column = colno
|
|
16
|
+
this.__newrelic = newrelic
|
|
14
17
|
}
|
|
15
18
|
}
|
|
@@ -44,6 +44,7 @@ export class Aggregate extends AggregateBase {
|
|
|
44
44
|
this.trace = {}
|
|
45
45
|
this.nodeCount = 0
|
|
46
46
|
this.sentTrace = null
|
|
47
|
+
this.prevStoredEvents = new Set()
|
|
47
48
|
this.harvestTimeSeconds = getConfigurationValue(agentIdentifier, 'session_trace.harvestTimeSeconds') || 10
|
|
48
49
|
this.maxNodesPerHarvest = getConfigurationValue(agentIdentifier, 'session_trace.maxNodesPerHarvest') || 1000
|
|
49
50
|
/**
|
|
@@ -108,7 +109,7 @@ export class Aggregate extends AggregateBase {
|
|
|
108
109
|
this.isStandalone = true
|
|
109
110
|
this.waitForFlags((['stn'])).then(([on]) => controlTraceOp(on), this.featureName, this.ee)
|
|
110
111
|
} else {
|
|
111
|
-
registerHandler('
|
|
112
|
+
registerHandler('trace-jserror', () => {
|
|
112
113
|
seenAnError = true
|
|
113
114
|
switchToFull()
|
|
114
115
|
}, this.featureName, this.ee)
|
|
@@ -169,7 +170,7 @@ export class Aggregate extends AggregateBase {
|
|
|
169
170
|
registerHandler('bstHist', (...args) => operationalGate.settle(() => this.storeHist(...args)), this.featureName, this.ee)
|
|
170
171
|
registerHandler('bstXhrAgg', (...args) => operationalGate.settle(() => this.storeXhrAgg(...args)), this.featureName, this.ee)
|
|
171
172
|
registerHandler('bstApi', (...args) => operationalGate.settle(() => this.storeSTN(...args)), this.featureName, this.ee)
|
|
172
|
-
registerHandler('
|
|
173
|
+
registerHandler('trace-jserror', (...args) => operationalGate.settle(() => this.storeErrorAgg(...args)), this.featureName, this.ee)
|
|
173
174
|
registerHandler('pvtAdded', (...args) => operationalGate.settle(() => this.processPVT(...args)), this.featureName, this.ee)
|
|
174
175
|
this.drain()
|
|
175
176
|
}
|
|
@@ -208,6 +209,7 @@ export class Aggregate extends AggregateBase {
|
|
|
208
209
|
}
|
|
209
210
|
|
|
210
211
|
#prepareHarvest (options) {
|
|
212
|
+
this.prevStoredEvents.clear() // release references to past events for GC
|
|
211
213
|
if (this.isStandalone) {
|
|
212
214
|
if (this.ptid && now() >= MAX_TRACE_DURATION) {
|
|
213
215
|
// Perform a final harvest once we hit or exceed the max session trace time
|
|
@@ -272,6 +274,8 @@ export class Aggregate extends AggregateBase {
|
|
|
272
274
|
// Tracks the events and their listener's duration on objects wrapped by wrap-events.
|
|
273
275
|
storeEvent (currentEvent, target, start, end) {
|
|
274
276
|
if (this.shouldIgnoreEvent(currentEvent, target)) return
|
|
277
|
+
if (this.prevStoredEvents.has(currentEvent)) return // prevent multiple listeners of an event from creating duplicate trace nodes per occurrence. Cleared every harvest. near-zero chance for re-duplication after clearing per harvest since the timestamps of the event are considered for uniqueness.
|
|
278
|
+
this.prevStoredEvents.add(currentEvent)
|
|
275
279
|
|
|
276
280
|
const evt = {
|
|
277
281
|
n: this.evtName(currentEvent.type),
|
|
@@ -698,7 +698,7 @@ export class Aggregate extends AggregateBase {
|
|
|
698
698
|
}
|
|
699
699
|
}
|
|
700
700
|
|
|
701
|
-
baseEE.on('
|
|
701
|
+
baseEE.on('spa-jserror', function (type, name, params, metrics) {
|
|
702
702
|
if (!state.currentNode) return
|
|
703
703
|
params._interactionId = state.currentNode.interaction.id
|
|
704
704
|
// do not capture parentNodeId when in root node
|
|
@@ -707,6 +707,15 @@ export class Aggregate extends AggregateBase {
|
|
|
707
707
|
}
|
|
708
708
|
})
|
|
709
709
|
|
|
710
|
+
register('function-err', function (args, obj, error) {
|
|
711
|
+
if (!state.currentNode) return
|
|
712
|
+
error.__newrelic ??= {}
|
|
713
|
+
error.__newrelic[agentIdentifier] = { interactionId: state.currentNode.interaction.id }
|
|
714
|
+
if (state.currentNode.type && state.currentNode.type !== 'interaction') {
|
|
715
|
+
error.__newrelic[agentIdentifier].interactionNodeId = state.currentNode.id
|
|
716
|
+
}
|
|
717
|
+
}, this.featureName, baseEE)
|
|
718
|
+
|
|
710
719
|
baseEE.on('interaction', saveInteraction)
|
|
711
720
|
|
|
712
721
|
function getActionText (node) {
|
|
@@ -10,6 +10,7 @@ import { InstrumentBase } from '../../utils/instrument-base'
|
|
|
10
10
|
import * as CONSTANTS from '../constants'
|
|
11
11
|
import { isBrowserScope } from '../../../common/constants/runtime'
|
|
12
12
|
import { now } from '../../../common/timing/now'
|
|
13
|
+
import { handle } from '../../../common/event-emitter/handle'
|
|
13
14
|
|
|
14
15
|
const {
|
|
15
16
|
FEATURE_NAME, START, END, BODY, CB_END, JS_TIME, FETCH, FN_START, CB_START, FN_END
|
|
@@ -46,6 +47,8 @@ export class Instrument extends InstrumentBase {
|
|
|
46
47
|
promiseEE.on(CB_END, endTimestamp)
|
|
47
48
|
jsonpEE.on(CB_END, endTimestamp)
|
|
48
49
|
|
|
50
|
+
this.ee.on('fn-err', (...args) => { if (!args[2]?.__newrelic?.[agentIdentifier]) handle('function-err', [...args], undefined, this.featureName, this.ee) })
|
|
51
|
+
|
|
49
52
|
this.ee.buffer([FN_START, FN_END, 'xhr-resolved'], this.featureName)
|
|
50
53
|
eventsEE.buffer([FN_START], this.featureName)
|
|
51
54
|
timerEE.buffer(['setTimeout' + END, 'clearTimeout' + START, FN_START], this.featureName)
|
|
@@ -52,7 +52,7 @@ export class InstrumentBase extends FeatureBase {
|
|
|
52
52
|
/** if the feature requires opt-in (!auto-start), it will get registered once the api has been called */
|
|
53
53
|
if (this.auto) registerDrain(agentIdentifier, featureName)
|
|
54
54
|
else {
|
|
55
|
-
this.ee.on(
|
|
55
|
+
this.ee.on('manual-start-all', single(() => {
|
|
56
56
|
// register the feature to drain only once the API has been called, it will drain when importAggregator finishes for all the features
|
|
57
57
|
// called by the api in that cycle
|
|
58
58
|
registerDrain(this.agentIdentifier, this.featureName)
|
package/src/loaders/api/api.js
CHANGED
|
@@ -117,20 +117,10 @@ export function setAPI (agentIdentifier, forceDrain, runSoftNavOverSpa = false)
|
|
|
117
117
|
return appendJsAttribute('application.version', value, 'setApplicationVersion', false)
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
apiInterface.start = (
|
|
120
|
+
apiInterface.start = () => {
|
|
121
121
|
try {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const featNames = Object.values(FEATURE_NAMES)
|
|
125
|
-
if (features === undefined) features = featNames
|
|
126
|
-
else {
|
|
127
|
-
features = Array.isArray(features) && features.length ? features : [features]
|
|
128
|
-
if (features.some(f => !featNames.includes(f))) return warn(`Invalid feature name supplied. Acceptable feature names are: ${featNames}`)
|
|
129
|
-
if (!features.includes(FEATURE_NAMES.pageViewEvent)) features.push(FEATURE_NAMES.pageViewEvent)
|
|
130
|
-
}
|
|
131
|
-
features.forEach(feature => {
|
|
132
|
-
instanceEE.emit(`${feature}-opt-in`)
|
|
133
|
-
})
|
|
122
|
+
handle(SUPPORTABILITY_METRIC_CHANNEL, ['API/start/called'], undefined, FEATURE_NAMES.metrics, instanceEE)
|
|
123
|
+
instanceEE.emit('manual-start-all')
|
|
134
124
|
} catch (err) {
|
|
135
125
|
warn('An unexpected issue occurred', err)
|
|
136
126
|
}
|
|
@@ -166,9 +156,10 @@ export function setAPI (agentIdentifier, forceDrain, runSoftNavOverSpa = false)
|
|
|
166
156
|
try {
|
|
167
157
|
return cb.apply(this, arguments)
|
|
168
158
|
} catch (err) {
|
|
169
|
-
|
|
159
|
+
const error = typeof err === 'string' ? new Error(err) : err
|
|
160
|
+
tracerEE.emit('fn-err', [arguments, this, error], contextStore)
|
|
170
161
|
// the error came from outside the agent, so don't swallow
|
|
171
|
-
throw
|
|
162
|
+
throw error
|
|
172
163
|
} finally {
|
|
173
164
|
tracerEE.emit('fn-end', [now()], contextStore)
|
|
174
165
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"uncaught-error.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/instrument/uncaught-error.js"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH;IACE,kEAMC;IALC,aAA2B;IAC3B,aAAsB;IACtB,eAAyB;IACzB,UAAkB;IAClB,YAAmB;CAEtB"}
|