@newrelic/browser-agent 1.311.0 → 1.312.0
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 +14 -0
- package/dist/cjs/common/constants/agent-constants.js +4 -5
- package/dist/cjs/common/constants/env.cdn.js +2 -2
- package/dist/cjs/common/constants/env.npm.js +2 -2
- package/dist/cjs/common/util/script-tracker.js +2 -0
- package/dist/cjs/common/util/stringify.js +6 -21
- package/dist/cjs/common/util/v2.js +90 -6
- package/dist/cjs/common/wrap/wrap-fetch.js +10 -5
- package/dist/cjs/common/wrap/wrap-function.js +17 -9
- package/dist/cjs/common/wrap/wrap-logger.js +6 -4
- package/dist/cjs/common/wrap/wrap-xhr.js +3 -1
- package/dist/cjs/features/ajax/aggregate/index.js +24 -6
- package/dist/cjs/features/ajax/instrument/index.js +12 -10
- package/dist/cjs/features/generic_events/instrument/index.js +3 -3
- package/dist/cjs/features/logging/aggregate/index.js +12 -7
- package/dist/cjs/features/logging/instrument/index.js +6 -4
- package/dist/cjs/features/logging/shared/utils.js +4 -4
- package/dist/cjs/features/soft_navigations/aggregate/ajax-node.js +6 -3
- package/dist/cjs/loaders/api/register.js +24 -23
- package/dist/cjs/loaders/api/wrapLogger.js +2 -2
- package/dist/esm/common/constants/agent-constants.js +4 -5
- package/dist/esm/common/constants/env.cdn.js +2 -2
- package/dist/esm/common/constants/env.npm.js +2 -2
- package/dist/esm/common/util/script-tracker.js +2 -2
- package/dist/esm/common/util/stringify.js +6 -21
- package/dist/esm/common/util/v2.js +86 -6
- package/dist/esm/common/wrap/wrap-fetch.js +10 -5
- package/dist/esm/common/wrap/wrap-function.js +17 -9
- package/dist/esm/common/wrap/wrap-logger.js +6 -4
- package/dist/esm/common/wrap/wrap-xhr.js +3 -1
- package/dist/esm/features/ajax/aggregate/index.js +24 -6
- package/dist/esm/features/ajax/instrument/index.js +12 -10
- package/dist/esm/features/generic_events/instrument/index.js +3 -3
- package/dist/esm/features/logging/aggregate/index.js +13 -8
- package/dist/esm/features/logging/instrument/index.js +6 -4
- package/dist/esm/features/logging/shared/utils.js +4 -4
- package/dist/esm/features/soft_navigations/aggregate/ajax-node.js +6 -3
- package/dist/esm/loaders/api/register.js +24 -23
- package/dist/esm/loaders/api/wrapLogger.js +2 -2
- package/dist/types/common/constants/agent-constants.d.ts.map +1 -1
- package/dist/types/common/util/script-tracker.d.ts +11 -0
- package/dist/types/common/util/script-tracker.d.ts.map +1 -1
- package/dist/types/common/util/stringify.d.ts.map +1 -1
- package/dist/types/common/util/v2.d.ts +37 -0
- package/dist/types/common/util/v2.d.ts.map +1 -1
- package/dist/types/common/wrap/wrap-fetch.d.ts +1 -1
- package/dist/types/common/wrap/wrap-fetch.d.ts.map +1 -1
- package/dist/types/common/wrap/wrap-function.d.ts +1 -1
- package/dist/types/common/wrap/wrap-function.d.ts.map +1 -1
- package/dist/types/common/wrap/wrap-logger.d.ts +1 -1
- package/dist/types/common/wrap/wrap-logger.d.ts.map +1 -1
- package/dist/types/common/wrap/wrap-xhr.d.ts +1 -1
- package/dist/types/common/wrap/wrap-xhr.d.ts.map +1 -1
- package/dist/types/features/ajax/aggregate/index.d.ts +2 -1
- package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/ajax/instrument/index.d.ts.map +1 -1
- package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/logging/instrument/index.d.ts.map +1 -1
- package/dist/types/features/logging/shared/utils.d.ts +2 -2
- package/dist/types/features/logging/shared/utils.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts +1 -0
- package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts.map +1 -1
- package/dist/types/loaders/api/register.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/common/constants/agent-constants.js +4 -5
- package/src/common/util/script-tracker.js +2 -2
- package/src/common/util/stringify.js +6 -23
- package/src/common/util/v2.js +84 -7
- package/src/common/wrap/wrap-fetch.js +12 -5
- package/src/common/wrap/wrap-function.js +16 -9
- package/src/common/wrap/wrap-logger.js +6 -4
- package/src/common/wrap/wrap-xhr.js +3 -1
- package/src/features/ajax/aggregate/index.js +22 -6
- package/src/features/ajax/instrument/index.js +13 -10
- package/src/features/generic_events/instrument/index.js +3 -3
- package/src/features/logging/aggregate/index.js +13 -14
- package/src/features/logging/instrument/index.js +6 -4
- package/src/features/logging/shared/utils.js +4 -4
- package/src/features/soft_navigations/aggregate/ajax-node.js +6 -3
- package/src/loaders/api/register.js +19 -12
- package/src/loaders/api/wrapLogger.js +2 -2
|
@@ -54,7 +54,7 @@ if (globalScope.PerformanceObserver?.supportedEntryTypes.includes('resource')) {
|
|
|
54
54
|
* @param {string} stack The error stack trace
|
|
55
55
|
* @returns {string[]} Array of cleaned URLs found in the stack trace
|
|
56
56
|
*/
|
|
57
|
-
function extractUrlsFromStack (stack) {
|
|
57
|
+
export function extractUrlsFromStack (stack) {
|
|
58
58
|
if (!stack || typeof stack !== 'string') return []
|
|
59
59
|
|
|
60
60
|
const urls = new Set()
|
|
@@ -80,7 +80,7 @@ function extractUrlsFromStack (stack) {
|
|
|
80
80
|
* Returns a deep stack trace by temporarily increasing the stack trace limit.
|
|
81
81
|
* @returns {Error.stack | undefined}
|
|
82
82
|
*/
|
|
83
|
-
function getDeepStackTrace () {
|
|
83
|
+
export function getDeepStackTrace () {
|
|
84
84
|
let stack
|
|
85
85
|
try {
|
|
86
86
|
const originalStackLimit = Error.stackTraceLimit
|
|
@@ -7,35 +7,18 @@ import { ee } from '../event-emitter/contextual-ee'
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Returns a function for use as a replacer parameter in JSON.stringify() to handle circular references.
|
|
10
|
-
* Uses an array to track the current ancestor chain, allowing the same object to appear
|
|
11
|
-
* multiple times in the structure as long as it's not a circular reference.
|
|
12
10
|
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value MDN - Cyclical object value}
|
|
13
|
-
* @returns {Function} A function that filters out
|
|
11
|
+
* @returns {Function} A function that filters out values it has seen before.
|
|
14
12
|
*/
|
|
15
13
|
const getCircularReplacer = () => {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// Find where we are in the stack
|
|
21
|
-
const thisPos = stack.indexOf(this)
|
|
22
|
-
if (~thisPos) {
|
|
23
|
-
// We're still in the stack, trim it
|
|
24
|
-
stack.splice(thisPos + 1)
|
|
25
|
-
} else {
|
|
26
|
-
// We're not in the stack, add ourselves
|
|
27
|
-
stack.push(this)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Check if value is in the current ancestor chain
|
|
31
|
-
if (~stack.indexOf(value)) {
|
|
14
|
+
const seen = new WeakSet()
|
|
15
|
+
return (key, value) => {
|
|
16
|
+
if (typeof value === 'object' && value !== null) {
|
|
17
|
+
if (seen.has(value)) {
|
|
32
18
|
return
|
|
33
19
|
}
|
|
34
|
-
|
|
35
|
-
// First call, initialize with root
|
|
36
|
-
stack.push(value)
|
|
20
|
+
seen.add(value)
|
|
37
21
|
}
|
|
38
|
-
|
|
39
22
|
return value
|
|
40
23
|
}
|
|
41
24
|
}
|
package/src/common/util/v2.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { extractUrlsFromStack, getDeepStackTrace } from './script-tracker'
|
|
7
|
+
|
|
6
8
|
/**
|
|
7
9
|
* @enum {string}
|
|
8
10
|
* @readonly
|
|
@@ -14,6 +16,30 @@ export const V2_TYPES = {
|
|
|
14
16
|
BA: 'BA'
|
|
15
17
|
}
|
|
16
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Returns the registered target associated with a given ID. Returns an empty array if no target is found.
|
|
21
|
+
* @param {string|number} id
|
|
22
|
+
* @param {*} agentRef the agent reference
|
|
23
|
+
* @returns {import("../../interfaces/registered-entity").RegisterAPIMetadataTarget[]}
|
|
24
|
+
*/
|
|
25
|
+
export function getRegisteredTargetsFromId (id, agentRef) {
|
|
26
|
+
if (!id || !agentRef?.init.api.allow_registered_children) return []
|
|
27
|
+
const registeredEntities = agentRef.runtime.registeredEntities
|
|
28
|
+
return registeredEntities?.filter(entity => String(entity.metadata.target.id) === String(id)).map(entity => entity.metadata.target) || []
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Returns the registered target(s) associated with a given filename if found in the resource timing API during registration. Returns an empty array if no target is found.
|
|
33
|
+
* @param {string} filename
|
|
34
|
+
* @param {*} agentRef
|
|
35
|
+
* @returns {import("../../interfaces/registered-entity").RegisterAPIMetadataTarget[]}
|
|
36
|
+
*/
|
|
37
|
+
export function getRegisteredTargetsFromFilename (filename, agentRef) {
|
|
38
|
+
if (!filename || !agentRef?.init.api.allow_registered_children) return []
|
|
39
|
+
const registeredEntities = agentRef.runtime.registeredEntities
|
|
40
|
+
return registeredEntities?.filter(entity => entity.metadata.timings?.asset?.endsWith(filename)).map(entity => entity.metadata.target) || []
|
|
41
|
+
}
|
|
42
|
+
|
|
17
43
|
/**
|
|
18
44
|
* When given a valid target, returns an object with the V2 payload attributes. Returns an empty object otherwise.
|
|
19
45
|
* @note Field names may change as the schema is finalized
|
|
@@ -23,7 +49,7 @@ export const V2_TYPES = {
|
|
|
23
49
|
* @returns {Object} returns an empty object if args are not supplied or the aggregate instance is not supporting version 2
|
|
24
50
|
*/
|
|
25
51
|
export function getVersion2Attributes (target, aggregateInstance) {
|
|
26
|
-
if (aggregateInstance
|
|
52
|
+
if (!supportsV2(aggregateInstance)) return {}
|
|
27
53
|
const containerAgentEntityGuid = aggregateInstance.agentRef.runtime.appMetadata.agents[0].entityGuid
|
|
28
54
|
/** if there's no target, but we are in v2 mode, this means the data belongs to the container agent */
|
|
29
55
|
if (!target) {
|
|
@@ -33,11 +59,62 @@ export function getVersion2Attributes (target, aggregateInstance) {
|
|
|
33
59
|
}
|
|
34
60
|
}
|
|
35
61
|
/** otherwise, the data belongs to the target (MFE) and should be attributed as such */
|
|
36
|
-
return
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
62
|
+
return target.attributes
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Returns the attributes used for duplicating data in version 2 of the harvest endpoint.
|
|
67
|
+
* If not valid for duplication, returns an empty object.
|
|
68
|
+
* @note BEST PRACTICE - Caller should call shouldDuplicate() before utilizing this method to determine if duplication attributes should be added to the event.
|
|
69
|
+
* @param {import("../../interfaces/registered-entity").RegisterAPIMetadataTarget} target
|
|
70
|
+
* @param {*} aggregateInstance the aggregate instance calling the method
|
|
71
|
+
* @returns {Object}
|
|
72
|
+
*/
|
|
73
|
+
export function getVersion2DuplicationAttributes (target, aggregateInstance) {
|
|
74
|
+
if (!shouldDuplicate(target, aggregateInstance)) return {}
|
|
75
|
+
return { 'child.id': target.id, 'child.type': target.type, ...getVersion2Attributes(undefined, aggregateInstance) }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Determines if an event should be duplicated for a given target and aggregate instance. This is used to determine if duplication attributes should be added to an event and if the event should be sent to the soft nav feature for evaluation.
|
|
80
|
+
* @note This method is intended to be used in conjunction with getVersion2DuplicationAttributes and should be called before it to determine if duplication attributes should be added to an event.
|
|
81
|
+
* @param {import("../../interfaces/registered-entity").RegisterAPIMetadataTarget} target
|
|
82
|
+
* @param {*} aggregateInstance The aggregate instance calling the method. This is needed to check if duplication is enabled and if the harvest endpoint version supports it.
|
|
83
|
+
* @returns {boolean} returns true if the event should be duplicated for the target, false otherwise
|
|
84
|
+
*/
|
|
85
|
+
export function shouldDuplicate (target, aggregateInstance) {
|
|
86
|
+
return !!target && !!supportsV2(aggregateInstance) && aggregateInstance.agentRef.init.api.duplicate_registered_data
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Finds the registered targets from the stack trace for a given agent reference.
|
|
91
|
+
* @param {*} agentRef The agent reference to use for finding targets.
|
|
92
|
+
* @returns {Array} An array of targets found from the stack trace. If no targets are found or allowed, returns an array with undefined.
|
|
93
|
+
*/
|
|
94
|
+
export function findTargetsFromStackTrace (agentRef) {
|
|
95
|
+
if (!agentRef?.init.api.allow_registered_children) return [undefined]
|
|
96
|
+
|
|
97
|
+
const targets = []
|
|
98
|
+
try {
|
|
99
|
+
var urls = extractUrlsFromStack(getDeepStackTrace())
|
|
100
|
+
let iterator = urls.length - 1
|
|
101
|
+
while (urls[iterator]) {
|
|
102
|
+
targets.push(...getRegisteredTargetsFromFilename(urls[iterator--], agentRef))
|
|
103
|
+
}
|
|
104
|
+
} catch (err) {
|
|
105
|
+
// Silent catch to prevent errors from propagating
|
|
42
106
|
}
|
|
107
|
+
if (!targets.length) targets.push(undefined) // if we can't find any targets from the stack trace, return an array with undefined to signify the container agent is the target
|
|
108
|
+
return targets
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Determines if the aggregate instance supports version 2 of the harvest endpoint. Nearly all the V2 logic "depends" on
|
|
113
|
+
* the harvest endpoint version, so this is the main gatekeeper method for whether or not V2 logic should be executed across the
|
|
114
|
+
* various functions in this module.
|
|
115
|
+
* @param {*} aggregateInstance The aggregate instance to check.
|
|
116
|
+
* @returns {boolean} Returns true if the aggregate instance supports version 2, false otherwise.
|
|
117
|
+
*/
|
|
118
|
+
function supportsV2 (aggregateInstance) {
|
|
119
|
+
return aggregateInstance?.harvestEndpointVersion === 2
|
|
43
120
|
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { ee as baseEE, contextId } from '../event-emitter/contextual-ee'
|
|
11
11
|
import { globalScope } from '../constants/runtime'
|
|
12
|
+
import { findTargetsFromStackTrace } from '../util/v2'
|
|
12
13
|
|
|
13
14
|
var prefix = 'fetch-'
|
|
14
15
|
var bodyPrefix = prefix + 'body-'
|
|
@@ -27,7 +28,7 @@ const wrapped = {}
|
|
|
27
28
|
* event emitter will be based.
|
|
28
29
|
* @returns {Object} Scoped event emitter with a debug ID of `fetch`.
|
|
29
30
|
*/
|
|
30
|
-
export function wrapFetch (sharedEE) {
|
|
31
|
+
export function wrapFetch (sharedEE, agentRef) {
|
|
31
32
|
const ee = scopedEE(sharedEE)
|
|
32
33
|
if (!(Req && Res && globalScope.fetch)) {
|
|
33
34
|
return ee
|
|
@@ -44,13 +45,16 @@ export function wrapFetch (sharedEE) {
|
|
|
44
45
|
})
|
|
45
46
|
wrapPromiseMethod(globalScope, 'fetch', prefix)
|
|
46
47
|
|
|
47
|
-
ee.on(prefix + 'end', function (err, res) {
|
|
48
|
+
ee.on(prefix + 'end', function (err, res, targets) {
|
|
48
49
|
var ctx = this
|
|
50
|
+
// undefined target reports to container
|
|
51
|
+
ctx.targets = targets || [undefined]
|
|
49
52
|
if (res) {
|
|
50
53
|
var size = res.headers.get('content-length')
|
|
51
54
|
if (size !== null) {
|
|
52
55
|
ctx.rxSize = size
|
|
53
56
|
}
|
|
57
|
+
|
|
54
58
|
ee.emit(prefix + 'done', [null, res], ctx)
|
|
55
59
|
} else {
|
|
56
60
|
ee.emit(prefix + 'done', [err], ctx)
|
|
@@ -67,11 +71,14 @@ export function wrapFetch (sharedEE) {
|
|
|
67
71
|
*/
|
|
68
72
|
function wrapPromiseMethod (target, name, prefix) {
|
|
69
73
|
var fn = target[name]
|
|
74
|
+
|
|
70
75
|
if (typeof fn === 'function') {
|
|
71
76
|
target[name] = function () {
|
|
72
77
|
var args = [...arguments]
|
|
73
78
|
|
|
74
|
-
|
|
79
|
+
const ctx = {}
|
|
80
|
+
const targets = findTargetsFromStackTrace(agentRef)
|
|
81
|
+
|
|
75
82
|
// we are wrapping args in an array so we can preserve the reference
|
|
76
83
|
ee.emit(prefix + 'before-start', [args], ctx)
|
|
77
84
|
var dtPayload
|
|
@@ -83,10 +90,10 @@ export function wrapFetch (sharedEE) {
|
|
|
83
90
|
|
|
84
91
|
// Note we need to cast the returned (orig) Promise from native APIs into the current global Promise, which may or may not be our WrappedPromise.
|
|
85
92
|
return origPromiseFromFetch.then(function (val) {
|
|
86
|
-
ee.emit(prefix + 'end', [null, val], origPromiseFromFetch)
|
|
93
|
+
ee.emit(prefix + 'end', [null, val, targets], origPromiseFromFetch)
|
|
87
94
|
return val
|
|
88
95
|
}, function (err) {
|
|
89
|
-
ee.emit(prefix + 'end', [err], origPromiseFromFetch)
|
|
96
|
+
ee.emit(prefix + 'end', [err, undefined, targets], origPromiseFromFetch)
|
|
90
97
|
throw err
|
|
91
98
|
})
|
|
92
99
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { ee } from '../event-emitter/contextual-ee'
|
|
11
11
|
import { bundleId } from '../ids/bundle-id'
|
|
12
|
+
import { findTargetsFromStackTrace } from '../util/v2'
|
|
12
13
|
|
|
13
14
|
export const flag = `nr@original:${bundleId}`
|
|
14
15
|
const LONG_TASK_THRESHOLD = 50
|
|
@@ -34,7 +35,7 @@ export default createWrapperWithEmitter
|
|
|
34
35
|
* @param {boolean} always - If `true`, emit events even if already emitting an event.
|
|
35
36
|
* @returns {function} The wrapped function.
|
|
36
37
|
*/
|
|
37
|
-
export function createWrapperWithEmitter (emitter, always) {
|
|
38
|
+
export function createWrapperWithEmitter (emitter, always, agentRef) {
|
|
38
39
|
emitter || (emitter = ee)
|
|
39
40
|
|
|
40
41
|
wrapFn.inPlace = inPlace
|
|
@@ -55,9 +56,10 @@ export function createWrapperWithEmitter (emitter, always) {
|
|
|
55
56
|
* @param {function|object} getContext - The function or object that will serve as the 'this' context for handlers of events emitted by this wrapper.
|
|
56
57
|
* @param {string} methodName - The name of the method being wrapped.
|
|
57
58
|
* @param {boolean} bubble - If true, emitted events should also bubble up to the old emitter upon which the `emitter` in the current scope was based (if it defines one).
|
|
59
|
+
* @param {boolean} [evaluateStack] - If true, the wrapper will attempt to evaluate the stack of the executed wrapped function to find targets of the execution (ex. the MFE source of a console.log).
|
|
58
60
|
* @returns {function} The wrapped function.
|
|
59
61
|
*/
|
|
60
|
-
function wrapFn (fn, prefix, getContext, methodName, bubble) {
|
|
62
|
+
function wrapFn (fn, prefix, getContext, methodName, bubble, evaluateStack) {
|
|
61
63
|
// Unless fn is both wrappable and unwrapped, return it unchanged.
|
|
62
64
|
if (notWrappable(fn)) return fn
|
|
63
65
|
|
|
@@ -78,11 +80,16 @@ export function createWrapperWithEmitter (emitter, always) {
|
|
|
78
80
|
var ctx
|
|
79
81
|
var result
|
|
80
82
|
let thrownError
|
|
83
|
+
let targets
|
|
81
84
|
|
|
82
85
|
try {
|
|
83
86
|
originalThis = this
|
|
84
87
|
args = [...arguments]
|
|
85
88
|
|
|
89
|
+
// certain wrappers can inform the function wrapper to evaluate the stack of the executed wrapped function to find targets of the execution
|
|
90
|
+
// (e.g. wrap-logger can inform this method to find try to find the MFE source of a console.log)
|
|
91
|
+
targets = evaluateStack ? findTargetsFromStackTrace(agentRef) : [undefined] // undefined target always maps to the container agent
|
|
92
|
+
|
|
86
93
|
if (typeof getContext === 'function') {
|
|
87
94
|
ctx = getContext(args, originalThis)
|
|
88
95
|
} else {
|
|
@@ -93,7 +100,7 @@ export function createWrapperWithEmitter (emitter, always) {
|
|
|
93
100
|
}
|
|
94
101
|
|
|
95
102
|
// Warning: start events may mutate args!
|
|
96
|
-
safeEmit(prefix + 'start', [args, originalThis, methodName], ctx, bubble)
|
|
103
|
+
safeEmit(prefix + 'start', [args, originalThis, methodName, targets], ctx, bubble)
|
|
97
104
|
|
|
98
105
|
const fnStartTime = performance.now()
|
|
99
106
|
let fnEndTime
|
|
@@ -103,7 +110,7 @@ export function createWrapperWithEmitter (emitter, always) {
|
|
|
103
110
|
return result
|
|
104
111
|
} catch (err) {
|
|
105
112
|
fnEndTime = performance.now()
|
|
106
|
-
safeEmit(prefix + 'err', [args, originalThis, err], ctx, bubble)
|
|
113
|
+
safeEmit(prefix + 'err', [args, originalThis, err, targets], ctx, bubble)
|
|
107
114
|
// rethrow error so we don't effect execution by observing.
|
|
108
115
|
thrownError = err
|
|
109
116
|
throw thrownError
|
|
@@ -120,10 +127,10 @@ export function createWrapperWithEmitter (emitter, always) {
|
|
|
120
127
|
}
|
|
121
128
|
// standalone long task message
|
|
122
129
|
if (task.isLongTask) {
|
|
123
|
-
safeEmit('long-task', [task, originalThis], ctx, bubble)
|
|
130
|
+
safeEmit('long-task', [task, originalThis, targets], ctx, bubble)
|
|
124
131
|
}
|
|
125
132
|
// -end message also includes the task execution info
|
|
126
|
-
safeEmit(prefix + 'end', [args, originalThis, result], ctx, bubble)
|
|
133
|
+
safeEmit(prefix + 'end', [args, originalThis, result, targets], ctx, bubble)
|
|
127
134
|
}
|
|
128
135
|
}
|
|
129
136
|
}
|
|
@@ -139,7 +146,7 @@ export function createWrapperWithEmitter (emitter, always) {
|
|
|
139
146
|
* @param {boolean} [bubble=false] If `true`, emitted events should also bubble up to the old emitter upon which
|
|
140
147
|
* the `emitter` in the current scope was based (if it defines one).
|
|
141
148
|
*/
|
|
142
|
-
function inPlace (obj, methods, prefix, getContext, bubble) {
|
|
149
|
+
function inPlace (obj, methods, prefix, getContext, bubble, evaluateStack) {
|
|
143
150
|
if (!prefix) prefix = ''
|
|
144
151
|
|
|
145
152
|
// If prefix starts with '-' set this boolean to add the method name to the prefix before passing each one to wrap.
|
|
@@ -152,7 +159,7 @@ export function createWrapperWithEmitter (emitter, always) {
|
|
|
152
159
|
// Unless fn is both wrappable and unwrapped, bail so we don't add extra properties with undefined values.
|
|
153
160
|
if (notWrappable(fn)) continue
|
|
154
161
|
|
|
155
|
-
obj[method] = wrapFn(fn, (prependMethodPrefix ? method + prefix : prefix), getContext, method, bubble)
|
|
162
|
+
obj[method] = wrapFn(fn, (prependMethodPrefix ? method + prefix : prefix), getContext, method, bubble, evaluateStack)
|
|
156
163
|
}
|
|
157
164
|
}
|
|
158
165
|
|
|
@@ -24,10 +24,10 @@ const contextMap = new Map()
|
|
|
24
24
|
* @returns {Object} Scoped event emitter with a debug ID of `logger`.
|
|
25
25
|
*/
|
|
26
26
|
// eslint-disable-next-line
|
|
27
|
-
export function wrapLogger(sharedEE, parent, loggerFn, context, autoCaptured = true) {
|
|
27
|
+
export function wrapLogger(sharedEE, parent, loggerFn, context, autoCaptured = true, agentRef) {
|
|
28
28
|
if (!(typeof parent === 'object' && !!parent && typeof loggerFn === 'string' && !!loggerFn && typeof parent[loggerFn] === 'function')) return warn(29)
|
|
29
29
|
const ee = scopedEE(sharedEE)
|
|
30
|
-
const wrapFn = wfn(ee)
|
|
30
|
+
const wrapFn = wfn(ee, undefined, agentRef)
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* This section contains the context that will be shared across all invoked calls of the wrapped function,
|
|
@@ -41,8 +41,10 @@ export function wrapLogger(sharedEE, parent, loggerFn, context, autoCaptured = t
|
|
|
41
41
|
const contextLookupKey = parent[loggerFn]?.[flag] || parent[loggerFn]
|
|
42
42
|
contextMap.set(contextLookupKey, ctx)
|
|
43
43
|
|
|
44
|
-
/** observe calls to <loggerFn> and emit events prefixed with `wrap-logger-`
|
|
45
|
-
|
|
44
|
+
/** observe calls to <loggerFn> and emit events prefixed with `wrap-logger-`
|
|
45
|
+
* inform the inplace wrapper to evaluate the stack for targets of the log execution,
|
|
46
|
+
* so that logs can be attributed to a matching MFE source if being used. */
|
|
47
|
+
wrapFn.inPlace(parent, [loggerFn], 'wrap-logger-', () => contextMap.get(contextLookupKey), undefined, true)
|
|
46
48
|
|
|
47
49
|
return ee
|
|
48
50
|
}
|
|
@@ -14,6 +14,7 @@ import { eventListenerOpts } from '../event-listener/event-listener-opts'
|
|
|
14
14
|
import { createWrapperWithEmitter as wfn } from './wrap-function'
|
|
15
15
|
import { globalScope } from '../constants/runtime'
|
|
16
16
|
import { warn } from '../util/console'
|
|
17
|
+
import { findTargetsFromStackTrace } from '../util/v2'
|
|
17
18
|
|
|
18
19
|
const wrapped = {}
|
|
19
20
|
const XHR_PROPS = ['open', 'send'] // these are the specific funcs being wrapped on all XMLHttpRequests(.prototype)
|
|
@@ -25,7 +26,7 @@ const XHR_PROPS = ['open', 'send'] // these are the specific funcs being wrapped
|
|
|
25
26
|
* @returns {Object} Scoped event emitter with a debug ID of `xhr`.
|
|
26
27
|
*/
|
|
27
28
|
// eslint-disable-next-line
|
|
28
|
-
export function wrapXhr (sharedEE) {
|
|
29
|
+
export function wrapXhr (sharedEE, agentRef) {
|
|
29
30
|
var baseEE = sharedEE || contextualEE
|
|
30
31
|
const ee = scopedEE(baseEE)
|
|
31
32
|
|
|
@@ -54,6 +55,7 @@ export function wrapXhr (sharedEE) {
|
|
|
54
55
|
function newXHR (opts) {
|
|
55
56
|
const xhr = new OrigXHR(opts)
|
|
56
57
|
const context = ee.context(xhr)
|
|
58
|
+
context.targets = findTargetsFromStackTrace(agentRef)
|
|
57
59
|
|
|
58
60
|
try {
|
|
59
61
|
ee.emit('new-xhr', [xhr], context)
|
|
@@ -12,6 +12,7 @@ import { AggregateBase } from '../../utils/aggregate-base'
|
|
|
12
12
|
import { parseGQL } from './gql'
|
|
13
13
|
import { nullable, numeric, getAddStringContext, addCustomAttributes } from '../../../common/serialize/bel-serializer'
|
|
14
14
|
import { gosNREUMOriginals } from '../../../common/window/nreum'
|
|
15
|
+
import { getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/util/v2'
|
|
15
16
|
|
|
16
17
|
export class Aggregate extends AggregateBase {
|
|
17
18
|
static featureName = FEATURE_NAME
|
|
@@ -30,8 +31,8 @@ export class Aggregate extends AggregateBase {
|
|
|
30
31
|
|
|
31
32
|
registerHandler('returnAjax', event => this.events.add(event), this.featureName, this.ee)
|
|
32
33
|
|
|
33
|
-
registerHandler('xhr', function () { // the EE-drain system not only switches "this" but also passes a new EventContext with info. Should consider platform refactor to another system which passes a mutable context around separately and predictably to avoid problems like this.
|
|
34
|
-
classThis.storeXhr(
|
|
34
|
+
registerHandler('xhr', function (params, metrics, startTime, endTime, type, target) { // the EE-drain system not only switches "this" but also passes a new EventContext with info. Should consider platform refactor to another system which passes a mutable context around separately and predictably to avoid problems like this.
|
|
35
|
+
classThis.storeXhr(params, metrics, startTime, endTime, type, target, this) // this switches the context back to the class instance while passing the NR context as an argument -- see "ctx" in storeXhr
|
|
35
36
|
}, this.featureName, this.ee)
|
|
36
37
|
|
|
37
38
|
this.ee.on('long-task', (task, originator) => {
|
|
@@ -44,7 +45,7 @@ export class Aggregate extends AggregateBase {
|
|
|
44
45
|
this.waitForFlags(([])).then(() => this.drain())
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
storeXhr (params, metrics, startTime, endTime, type, ctx) {
|
|
48
|
+
storeXhr (params, metrics, startTime, endTime, type, target, ctx) {
|
|
48
49
|
metrics.time = startTime
|
|
49
50
|
|
|
50
51
|
// send to session traces
|
|
@@ -108,11 +109,21 @@ export class Aggregate extends AggregateBase {
|
|
|
108
109
|
})
|
|
109
110
|
if (event.gql) this.reportSupportabilityMetric('Ajax/Events/GraphQL/Bytes-Added', stringify(event.gql).length)
|
|
110
111
|
|
|
112
|
+
/** make a copy of the event for the MFE target if it exists */
|
|
113
|
+
if (target) {
|
|
114
|
+
this.events.add({ ...event, targetAttributes: getVersion2Attributes(target, this) })
|
|
115
|
+
if (shouldDuplicate(target, this)) this.reportContainerEvent({ ...event, targetAttributes: getVersion2DuplicationAttributes(target, this) }, ctx)
|
|
116
|
+
} else {
|
|
117
|
+
this.reportContainerEvent(event, ctx)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
reportContainerEvent (evt, ctx) {
|
|
111
122
|
const softNavInUse = Boolean(this.agentRef.features?.[FEATURE_NAMES.softNav])
|
|
112
123
|
if (softNavInUse) { // when SN is running, pass the event w/ info to it for evaluation -- either part of an interaction or is given back
|
|
113
|
-
handle('ajax', [
|
|
124
|
+
handle('ajax', [evt, ctx], undefined, FEATURE_NAMES.softNav, this.ee)
|
|
114
125
|
} else {
|
|
115
|
-
this.events.add(
|
|
126
|
+
this.events.add(evt)
|
|
116
127
|
}
|
|
117
128
|
}
|
|
118
129
|
|
|
@@ -154,7 +165,12 @@ export class Aggregate extends AggregateBase {
|
|
|
154
165
|
|
|
155
166
|
// add custom attributes
|
|
156
167
|
// gql decorators are added as custom attributes to alleviate need for new BEL schema
|
|
157
|
-
const attrParts = addCustomAttributes({
|
|
168
|
+
const attrParts = addCustomAttributes({
|
|
169
|
+
...(jsAttributes || {}),
|
|
170
|
+
...(event.gql || {}),
|
|
171
|
+
...(event.targetAttributes || {}) // used to supply the version 2 attributes, either MFE target or duplication attributes for the main agent app
|
|
172
|
+
}, addString)
|
|
173
|
+
|
|
158
174
|
fields.unshift(numeric(attrParts.length))
|
|
159
175
|
|
|
160
176
|
insert += fields.join(',')
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
import { gosNREUMOriginals } from '../../../common/window/nreum'
|
|
@@ -59,8 +59,8 @@ export class Instrument extends InstrumentBase {
|
|
|
59
59
|
// do nothing
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
wrapFetch(this.ee)
|
|
63
|
-
wrapXhr(this.ee)
|
|
62
|
+
wrapFetch(this.ee, agentRef)
|
|
63
|
+
wrapXhr(this.ee, agentRef)
|
|
64
64
|
subscribeToEvents(agentRef, this.ee, this.handler, this.dt)
|
|
65
65
|
|
|
66
66
|
this.importAggregator(agentRef, () => import(/* webpackChunkName: "ajax-aggregate" */ '../aggregate/index.js'))
|
|
@@ -314,14 +314,11 @@ function subscribeToEvents (agentRef, ee, handler, dt) {
|
|
|
314
314
|
this.startTime = now()
|
|
315
315
|
this.dt = dtPayload
|
|
316
316
|
|
|
317
|
-
|
|
318
|
-
if (fetchArguments.length >= 2) this.opts = fetchArguments[1]
|
|
317
|
+
let [target, opts = {}] = fetchArguments
|
|
319
318
|
|
|
320
|
-
var opts = this.opts || {}
|
|
321
|
-
var target = this.target
|
|
322
319
|
addUrl(this, extractUrl(target))
|
|
323
320
|
|
|
324
|
-
|
|
321
|
+
const method = ('' + ((target && target instanceof origRequest && target.method) ||
|
|
325
322
|
opts.method || 'GET')).toUpperCase()
|
|
326
323
|
this.params.method = method
|
|
327
324
|
this.body = opts.body
|
|
@@ -350,7 +347,8 @@ function subscribeToEvents (agentRef, ee, handler, dt) {
|
|
|
350
347
|
duration: now() - this.startTime
|
|
351
348
|
}
|
|
352
349
|
|
|
353
|
-
|
|
350
|
+
const payload = [this.params, metrics, this.startTime, this.endTime, 'fetch']
|
|
351
|
+
this.targets.forEach(target => reportToAgg(payload, this, target))
|
|
354
352
|
}
|
|
355
353
|
|
|
356
354
|
// Create report for XHR request that has finished
|
|
@@ -377,7 +375,12 @@ function subscribeToEvents (agentRef, ee, handler, dt) {
|
|
|
377
375
|
// Always send cbTime, even if no noticeable time was taken.
|
|
378
376
|
metrics.cbTime = this.cbTime
|
|
379
377
|
|
|
380
|
-
|
|
378
|
+
const payload = [params, metrics, this.startTime, this.endTime, 'xhr']
|
|
379
|
+
this.targets.forEach(target => reportToAgg(payload, this, target))
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function reportToAgg (payload, context, target) {
|
|
383
|
+
handler('xhr', [...payload, target], context, FEATURE_NAMES.ajax)
|
|
381
384
|
}
|
|
382
385
|
|
|
383
386
|
function captureXhrData (ctx, xhr) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -48,8 +48,8 @@ export class Instrument extends InstrumentBase {
|
|
|
48
48
|
let historyEE, websocketsEE
|
|
49
49
|
if (websocketsEnabled) websocketsEE = wrapWebSocket(this.ee)
|
|
50
50
|
if (isBrowserScope) {
|
|
51
|
-
wrapFetch(this.ee)
|
|
52
|
-
wrapXhr(this.ee)
|
|
51
|
+
wrapFetch(this.ee, agentRef)
|
|
52
|
+
wrapXhr(this.ee, agentRef)
|
|
53
53
|
historyEE = wrapHistory(this.ee)
|
|
54
54
|
|
|
55
55
|
if (agentRef.init.user_actions.enabled) {
|
|
@@ -13,7 +13,7 @@ import { applyFnToProps } from '../../../common/util/traverse'
|
|
|
13
13
|
import { SESSION_EVENT_TYPES, SESSION_EVENTS } from '../../../common/session/constants'
|
|
14
14
|
import { ABORT_REASONS } from '../../session_replay/constants'
|
|
15
15
|
import { canEnableSessionTracking } from '../../utils/feature-gates'
|
|
16
|
-
import { getVersion2Attributes } from '../../../common/util/v2'
|
|
16
|
+
import { getVersion2Attributes, getVersion2DuplicationAttributes, shouldDuplicate } from '../../../common/util/v2'
|
|
17
17
|
|
|
18
18
|
const LOGGING_EVENT = 'Logging/Event/'
|
|
19
19
|
|
|
@@ -73,12 +73,6 @@ export class Aggregate extends AggregateBase {
|
|
|
73
73
|
|
|
74
74
|
if (!attributes || typeof attributes !== 'object') attributes = {}
|
|
75
75
|
|
|
76
|
-
attributes = {
|
|
77
|
-
...attributes,
|
|
78
|
-
/** Specific attributes only supplied if harvesting to endpoint version 2 */
|
|
79
|
-
...(getVersion2Attributes(target, this))
|
|
80
|
-
}
|
|
81
|
-
|
|
82
76
|
if (typeof level === 'string') level = level.toUpperCase()
|
|
83
77
|
if (!isValidLogLevel(level)) return warn(30, level)
|
|
84
78
|
if (modeForThisLog < (LOGGING_MODE[level] || Infinity)) {
|
|
@@ -104,14 +98,19 @@ export class Aggregate extends AggregateBase {
|
|
|
104
98
|
}
|
|
105
99
|
if (typeof message !== 'string' || !message) return warn(32)
|
|
106
100
|
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
101
|
+
const addEvent = (attributes) => {
|
|
102
|
+
const log = new Log(
|
|
103
|
+
Math.floor(this.agentRef.runtime.timeKeeper.correctRelativeTimestamp(timestamp)),
|
|
104
|
+
message,
|
|
105
|
+
attributes,
|
|
106
|
+
level
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if (this.events.add(log)) this.reportSupportabilityMetric(LOGGING_EVENT + (autoCaptured ? 'Auto' : 'API') + '/Added')
|
|
110
|
+
}
|
|
113
111
|
|
|
114
|
-
|
|
112
|
+
addEvent({ ...attributes, ...getVersion2Attributes(target, this) })
|
|
113
|
+
if (shouldDuplicate(target, this)) addEvent({ ...attributes, ...getVersion2DuplicationAttributes(target, this) })
|
|
115
114
|
}
|
|
116
115
|
|
|
117
116
|
serializer (eventBuffer) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
import { InstrumentBase } from '../../utils/instrument-base'
|
|
@@ -27,13 +27,15 @@ export class Instrument extends InstrumentBase {
|
|
|
27
27
|
|
|
28
28
|
globals.forEach((method) => {
|
|
29
29
|
isNative(globalScope.console[method])
|
|
30
|
-
wrapLogger(instanceEE, globalScope.console, method, { level: method === 'log' ? 'info' : method })
|
|
30
|
+
wrapLogger(instanceEE, globalScope.console, method, { level: method === 'log' ? 'info' : method }, undefined, agentRef)
|
|
31
31
|
})
|
|
32
32
|
|
|
33
33
|
/** emitted by wrap-logger function */
|
|
34
|
-
this.ee.on('wrap-logger-end', function handleLog ([message]) {
|
|
34
|
+
this.ee.on('wrap-logger-end', function handleLog ([message], _, __, targets = []) {
|
|
35
35
|
const { level, customAttributes, autoCaptured } = this
|
|
36
|
-
|
|
36
|
+
targets.forEach(target => {
|
|
37
|
+
bufferLog(instanceEE, message, customAttributes, level, autoCaptured, target)
|
|
38
|
+
})
|
|
37
39
|
})
|
|
38
40
|
this.importAggregator(agentRef, () => import(/* webpackChunkName: "logging-aggregate" */ '../aggregate'))
|
|
39
41
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
import { handle } from '../../../common/event-emitter/handle'
|
|
@@ -14,11 +14,11 @@ import { LOGGING_EVENT_EMITTER_CHANNEL, LOG_LEVELS } from '../constants'
|
|
|
14
14
|
* @param {{[key: string]: *}} customAttributes - The log's custom attributes if any
|
|
15
15
|
* @param {enum} level - the log level enum
|
|
16
16
|
* @param {boolean} [autoCaptured=true] - True if log was captured from auto wrapping. False if it was captured from the API manual usage.
|
|
17
|
-
* @param {object=}
|
|
17
|
+
* @param {object=} targets - the optional targets found
|
|
18
18
|
*/
|
|
19
|
-
export function bufferLog (ee, message, customAttributes = {}, level = LOG_LEVELS.INFO, autoCaptured = true,
|
|
19
|
+
export function bufferLog (ee, message, customAttributes = {}, level = LOG_LEVELS.INFO, autoCaptured = true, targets, timestamp = now()) {
|
|
20
20
|
handle(SUPPORTABILITY_METRIC_CHANNEL, [`API/logging/${level.toLowerCase()}/called`], undefined, FEATURE_NAMES.metrics, ee)
|
|
21
|
-
handle(LOGGING_EVENT_EMITTER_CHANNEL, [timestamp, message, customAttributes, level, autoCaptured,
|
|
21
|
+
handle(LOGGING_EVENT_EMITTER_CHANNEL, [timestamp, message, customAttributes, level, autoCaptured, targets], undefined, FEATURE_NAMES.logging, ee)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|