@newrelic/browser-agent 1.271.0 → 1.273.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 +15 -0
- package/dist/cjs/common/aggregate/aggregator.js +23 -30
- package/dist/cjs/common/aggregate/event-aggregator.js +84 -0
- package/dist/cjs/common/config/init.js +8 -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/harvest/harvest-scheduler.js +1 -1
- package/dist/cjs/common/harvest/harvest.js +1 -5
- package/dist/cjs/common/harvest/types.js +0 -1
- package/dist/cjs/features/ajax/aggregate/index.js +52 -62
- package/dist/cjs/features/generic_events/aggregate/index.js +57 -36
- package/dist/cjs/features/generic_events/instrument/index.js +1 -1
- package/dist/cjs/features/jserrors/aggregate/index.js +23 -69
- package/dist/cjs/features/logging/aggregate/index.js +52 -59
- package/dist/cjs/features/metrics/aggregate/index.js +8 -5
- package/dist/cjs/features/page_view_timing/aggregate/index.js +8 -25
- package/dist/cjs/features/session_replay/aggregate/index.js +11 -10
- package/dist/cjs/features/session_replay/shared/recorder-events.js +2 -2
- package/dist/cjs/features/session_trace/aggregate/index.js +77 -88
- package/dist/cjs/features/session_trace/aggregate/trace/storage.js +22 -13
- package/dist/cjs/features/soft_navigations/aggregate/index.js +10 -20
- package/dist/cjs/features/soft_navigations/instrument/index.js +5 -9
- package/dist/cjs/features/spa/aggregate/index.js +10 -26
- package/dist/cjs/features/utils/aggregate-base.js +37 -0
- package/dist/cjs/features/utils/event-buffer.js +36 -87
- package/dist/cjs/features/utils/instrument-base.js +3 -3
- package/dist/cjs/loaders/features/features.js +13 -1
- package/dist/esm/common/aggregate/aggregator.js +23 -30
- package/dist/esm/common/aggregate/event-aggregator.js +78 -0
- package/dist/esm/common/config/init.js +8 -4
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/harvest/harvest-scheduler.js +1 -1
- package/dist/esm/common/harvest/harvest.js +1 -5
- package/dist/esm/common/harvest/types.js +0 -1
- package/dist/esm/features/ajax/aggregate/index.js +53 -62
- package/dist/esm/features/generic_events/aggregate/index.js +57 -36
- package/dist/esm/features/generic_events/instrument/index.js +1 -1
- package/dist/esm/features/jserrors/aggregate/index.js +24 -70
- package/dist/esm/features/logging/aggregate/index.js +52 -59
- package/dist/esm/features/metrics/aggregate/index.js +8 -5
- package/dist/esm/features/page_view_timing/aggregate/index.js +9 -26
- package/dist/esm/features/session_replay/aggregate/index.js +12 -11
- package/dist/esm/features/session_replay/shared/recorder-events.js +2 -2
- package/dist/esm/features/session_trace/aggregate/index.js +77 -88
- package/dist/esm/features/session_trace/aggregate/trace/storage.js +22 -13
- package/dist/esm/features/soft_navigations/aggregate/index.js +11 -21
- package/dist/esm/features/soft_navigations/instrument/index.js +5 -9
- package/dist/esm/features/spa/aggregate/index.js +11 -27
- package/dist/esm/features/utils/aggregate-base.js +37 -0
- package/dist/esm/features/utils/event-buffer.js +36 -88
- package/dist/esm/features/utils/instrument-base.js +3 -3
- package/dist/esm/loaders/features/features.js +12 -0
- package/dist/types/common/aggregate/aggregator.d.ts +4 -6
- package/dist/types/common/aggregate/aggregator.d.ts.map +1 -1
- package/dist/types/common/aggregate/event-aggregator.d.ts +26 -0
- package/dist/types/common/aggregate/event-aggregator.d.ts.map +1 -0
- package/dist/types/common/config/init.d.ts.map +1 -1
- package/dist/types/common/harvest/harvest.d.ts.map +1 -1
- package/dist/types/common/harvest/types.d.ts +1 -4
- package/dist/types/common/harvest/types.d.ts.map +1 -1
- package/dist/types/features/ajax/aggregate/index.d.ts +2 -10
- package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/generic_events/aggregate/index.d.ts +5 -11
- package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/generic_events/instrument/index.d.ts.map +1 -1
- package/dist/types/features/jserrors/aggregate/index.d.ts +4 -7
- package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/logging/aggregate/index.d.ts +10 -28
- package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_timing/aggregate/index.d.ts +1 -9
- package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +3 -4
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder-events.d.ts +1 -1
- package/dist/types/features/session_replay/shared/recorder-events.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder.d.ts +1 -1
- package/dist/types/features/session_trace/aggregate/index.d.ts +17 -19
- package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/trace/storage.d.ts +10 -6
- package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/aggregate/index.d.ts +3 -9
- package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/instrument/index.d.ts.map +1 -1
- package/dist/types/features/spa/aggregate/index.d.ts +2 -3
- package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/utils/aggregate-base.d.ts +14 -0
- package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
- package/dist/types/features/utils/event-buffer.d.ts +19 -56
- package/dist/types/features/utils/event-buffer.d.ts.map +1 -1
- package/dist/types/loaders/features/features.d.ts +3 -0
- package/dist/types/loaders/features/features.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/common/aggregate/aggregator.js +22 -32
- package/src/common/aggregate/event-aggregator.js +76 -0
- package/src/common/config/init.js +6 -2
- package/src/common/harvest/harvest-scheduler.js +1 -1
- package/src/common/harvest/harvest.js +1 -5
- package/src/common/harvest/types.js +0 -1
- package/src/features/ajax/aggregate/index.js +60 -67
- package/src/features/generic_events/aggregate/index.js +48 -38
- package/src/features/generic_events/instrument/index.js +2 -0
- package/src/features/jserrors/aggregate/index.js +21 -77
- package/src/features/logging/aggregate/index.js +46 -60
- package/src/features/metrics/aggregate/index.js +6 -4
- package/src/features/page_view_timing/aggregate/index.js +9 -30
- package/src/features/session_replay/aggregate/index.js +10 -14
- package/src/features/session_replay/shared/recorder-events.js +2 -2
- package/src/features/session_trace/aggregate/index.js +64 -73
- package/src/features/session_trace/aggregate/trace/storage.js +25 -14
- package/src/features/soft_navigations/aggregate/index.js +11 -22
- package/src/features/soft_navigations/instrument/index.js +6 -9
- package/src/features/spa/aggregate/index.js +12 -27
- package/src/features/utils/aggregate-base.js +39 -0
- package/src/features/utils/event-buffer.js +36 -83
- package/src/features/utils/instrument-base.js +3 -3
- package/src/loaders/features/features.js +13 -0
- package/dist/cjs/features/ajax/aggregate/chunk.js +0 -51
- package/dist/esm/features/ajax/aggregate/chunk.js +0 -44
- package/dist/types/features/ajax/aggregate/chunk.d.ts +0 -8
- package/dist/types/features/ajax/aggregate/chunk.d.ts.map +0 -1
- package/src/features/ajax/aggregate/chunk.js +0 -52
|
@@ -8,13 +8,11 @@ import { handle } from '../../../common/event-emitter/handle'
|
|
|
8
8
|
import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler'
|
|
9
9
|
import { setDenyList, shouldCollectEvent } from '../../../common/deny-list/deny-list'
|
|
10
10
|
import { FEATURE_NAME } from '../constants'
|
|
11
|
-
import { FEATURE_NAMES } from '../../../loaders/features/features'
|
|
11
|
+
import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
|
|
12
12
|
import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants'
|
|
13
13
|
import { AggregateBase } from '../../utils/aggregate-base'
|
|
14
14
|
import { parseGQL } from './gql'
|
|
15
|
-
import {
|
|
16
|
-
import Chunk from './chunk'
|
|
17
|
-
import { EventBuffer } from '../../utils/event-buffer'
|
|
15
|
+
import { nullable, numeric, getAddStringContext, addCustomAttributes } from '../../../common/serialize/bel-serializer'
|
|
18
16
|
|
|
19
17
|
export class Aggregate extends AggregateBase {
|
|
20
18
|
static featureName = FEATURE_NAME
|
|
@@ -25,31 +23,30 @@ export class Aggregate extends AggregateBase {
|
|
|
25
23
|
const harvestTimeSeconds = agentRef.init.ajax.harvestTimeSeconds || 10
|
|
26
24
|
setDenyList(agentRef.runtime.denyList)
|
|
27
25
|
|
|
28
|
-
this.
|
|
29
|
-
this.spaAjaxEvents = {}
|
|
26
|
+
this.underSpaEvents = {}
|
|
30
27
|
const classThis = this
|
|
31
28
|
|
|
32
29
|
// --- v Used by old spa feature
|
|
33
30
|
this.ee.on('interactionDone', (interaction, wasSaved) => {
|
|
34
|
-
if (!this.
|
|
31
|
+
if (!this.underSpaEvents[interaction.id]) return
|
|
35
32
|
|
|
36
33
|
if (!wasSaved) { // if the ixn was saved, then its ajax reqs are part of the payload whereas if it was discarded, it should still be harvested in the ajax feature itself
|
|
37
|
-
this.
|
|
34
|
+
this.underSpaEvents[interaction.id].forEach((item) => this.events.add(item))
|
|
38
35
|
}
|
|
39
|
-
delete this.
|
|
36
|
+
delete this.underSpaEvents[interaction.id]
|
|
40
37
|
})
|
|
41
38
|
// --- ^
|
|
42
39
|
// --- v Used by new soft nav
|
|
43
|
-
registerHandler('returnAjax', event => this.
|
|
40
|
+
registerHandler('returnAjax', event => this.events.add(event), this.featureName, this.ee)
|
|
44
41
|
// --- ^
|
|
45
42
|
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.
|
|
46
43
|
classThis.storeXhr(...arguments, this) // this switches the context back to the class instance while passing the NR context as an argument -- see "ctx" in storeXhr
|
|
47
44
|
}, this.featureName, this.ee)
|
|
48
45
|
|
|
49
46
|
this.waitForFlags(([])).then(() => {
|
|
50
|
-
const scheduler = new HarvestScheduler(
|
|
51
|
-
onFinished: this.
|
|
52
|
-
getPayload: this.
|
|
47
|
+
const scheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
|
|
48
|
+
onFinished: (result) => this.postHarvestCleanup(result.sent && result.retry),
|
|
49
|
+
getPayload: (options) => this.makeHarvestPayload(options.retry)
|
|
53
50
|
}, this)
|
|
54
51
|
scheduler.startTimer(harvestTimeSeconds)
|
|
55
52
|
this.drain()
|
|
@@ -69,10 +66,11 @@ export class Aggregate extends AggregateBase {
|
|
|
69
66
|
|
|
70
67
|
const shouldCollect = shouldCollectEvent(params)
|
|
71
68
|
const shouldOmitAjaxMetrics = this.agentRef.init.feature_flags?.includes('ajax_metrics_deny_list')
|
|
69
|
+
const jserrorsInUse = Boolean(this.agentRef.features?.[FEATURE_NAMES.jserrors])
|
|
72
70
|
|
|
73
|
-
//
|
|
74
|
-
if (shouldCollect || !shouldOmitAjaxMetrics) {
|
|
75
|
-
this.agentRef.sharedAggregator.
|
|
71
|
+
// Report ajax timeslice metric (to be harvested by jserrors feature, but only if it's running).
|
|
72
|
+
if (jserrorsInUse && (shouldCollect || !shouldOmitAjaxMetrics)) {
|
|
73
|
+
this.agentRef.sharedAggregator.add('xhr', hash, params, metrics)
|
|
76
74
|
}
|
|
77
75
|
|
|
78
76
|
if (!shouldCollect) {
|
|
@@ -119,66 +117,61 @@ export class Aggregate extends AggregateBase {
|
|
|
119
117
|
})
|
|
120
118
|
if (event.gql) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/GraphQL/Bytes-Added', stringify(event.gql).length], undefined, FEATURE_NAMES.metrics, this.ee)
|
|
121
119
|
|
|
122
|
-
const softNavInUse = Boolean(
|
|
120
|
+
const softNavInUse = Boolean(this.agentRef.features?.[FEATURE_NAMES.softNav])
|
|
123
121
|
if (softNavInUse) { // For newer soft nav (when running), pass the event to it for evaluation -- either part of an interaction or is given back
|
|
124
122
|
handle('ajax', [event], undefined, FEATURE_NAMES.softNav, this.ee)
|
|
125
123
|
} else if (ctx.spaNode) { // For old spa (when running), if the ajax happened inside an interaction, hold it until the interaction finishes
|
|
126
124
|
const interactionId = ctx.spaNode.interaction.id
|
|
127
|
-
this.
|
|
128
|
-
this.
|
|
125
|
+
this.underSpaEvents[interactionId] ??= []
|
|
126
|
+
this.underSpaEvents[interactionId].push(event)
|
|
129
127
|
} else {
|
|
130
|
-
this.
|
|
128
|
+
this.events.add(event)
|
|
131
129
|
}
|
|
132
130
|
}
|
|
133
131
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
// Check if the current payload string is too big, if so then run getPayload again with more buckets.
|
|
173
|
-
return tooBig ? this.#getPayload(events, ++numberOfChunks) : payload
|
|
174
|
-
|
|
175
|
-
function splitChunks (arr, chunkSize) {
|
|
176
|
-
chunkSize = chunkSize || arr.length
|
|
177
|
-
const chunks = []
|
|
178
|
-
for (let i = 0, len = arr.length; i < len; i += chunkSize) {
|
|
179
|
-
chunks.push(new Chunk(arr.slice(i, i + chunkSize), this))
|
|
132
|
+
serializer (eventBuffer) {
|
|
133
|
+
const addString = getAddStringContext(this.agentIdentifier)
|
|
134
|
+
let payload = 'bel.7;'
|
|
135
|
+
|
|
136
|
+
for (let i = 0; i < eventBuffer.length; i++) {
|
|
137
|
+
const event = eventBuffer[i]
|
|
138
|
+
const fields = [
|
|
139
|
+
numeric(event.startTime),
|
|
140
|
+
numeric(event.endTime - event.startTime),
|
|
141
|
+
numeric(0), // callbackEnd
|
|
142
|
+
numeric(0), // no callbackDuration for non-SPA events
|
|
143
|
+
addString(event.method),
|
|
144
|
+
numeric(event.status),
|
|
145
|
+
addString(event.domain),
|
|
146
|
+
addString(event.path),
|
|
147
|
+
numeric(event.requestSize),
|
|
148
|
+
numeric(event.responseSize),
|
|
149
|
+
event.type === 'fetch' ? 1 : '',
|
|
150
|
+
addString(0), // nodeId
|
|
151
|
+
nullable(event.spanId, addString, true) + // guid
|
|
152
|
+
nullable(event.traceId, addString, true) + // traceId
|
|
153
|
+
nullable(event.spanTimestamp, numeric, false) // timestamp
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
let insert = '2,'
|
|
157
|
+
|
|
158
|
+
// Since configuration objects (like info) are created new each time they are set, we have to grab the current pointer to the attr object here.
|
|
159
|
+
const jsAttributes = this.agentRef.info.jsAttributes
|
|
160
|
+
|
|
161
|
+
// add custom attributes
|
|
162
|
+
// gql decorators are added as custom attributes to alleviate need for new BEL schema
|
|
163
|
+
const attrParts = addCustomAttributes({ ...(jsAttributes || {}), ...(event.gql || {}) }, addString)
|
|
164
|
+
fields.unshift(numeric(attrParts.length))
|
|
165
|
+
|
|
166
|
+
insert += fields.join(',')
|
|
167
|
+
if (attrParts && attrParts.length > 0) {
|
|
168
|
+
insert += ';' + attrParts.join(';')
|
|
180
169
|
}
|
|
181
|
-
|
|
170
|
+
if ((i + 1) < eventBuffer.length) insert += ';'
|
|
171
|
+
|
|
172
|
+
payload += insert
|
|
182
173
|
}
|
|
174
|
+
|
|
175
|
+
return payload
|
|
183
176
|
}
|
|
184
177
|
}
|
|
@@ -12,9 +12,9 @@ import { warn } from '../../../common/util/console'
|
|
|
12
12
|
import { now } from '../../../common/timing/now'
|
|
13
13
|
import { registerHandler } from '../../../common/event-emitter/register-handler'
|
|
14
14
|
import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants'
|
|
15
|
-
import { EventBuffer } from '../../utils/event-buffer'
|
|
16
15
|
import { applyFnToProps } from '../../../common/util/traverse'
|
|
17
16
|
import { IDEAL_PAYLOAD_SIZE } from '../../../common/constants/agent-constants'
|
|
17
|
+
import { FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
|
|
18
18
|
import { UserActionsAggregator } from './user-actions/user-actions-aggregator'
|
|
19
19
|
import { isIFrameWindow } from '../../../common/dom/iframe'
|
|
20
20
|
|
|
@@ -27,7 +27,6 @@ export class Aggregate extends AggregateBase {
|
|
|
27
27
|
this.harvestTimeSeconds = agentRef.init.generic_events.harvestTimeSeconds
|
|
28
28
|
|
|
29
29
|
this.referrerUrl = (isBrowserScope && document.referrer) ? cleanURL(document.referrer) : undefined
|
|
30
|
-
this.events = new EventBuffer()
|
|
31
30
|
|
|
32
31
|
this.waitForFlags(['ins']).then(([ins]) => {
|
|
33
32
|
if (!ins) {
|
|
@@ -36,8 +35,6 @@ export class Aggregate extends AggregateBase {
|
|
|
36
35
|
return
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
const preHarvestMethods = []
|
|
40
|
-
|
|
41
38
|
if (agentRef.init.page_action.enabled) {
|
|
42
39
|
registerHandler('api-addPageAction', (timestamp, name, attributes) => {
|
|
43
40
|
this.addEvent({
|
|
@@ -55,10 +52,11 @@ export class Aggregate extends AggregateBase {
|
|
|
55
52
|
}, this.featureName, this.ee)
|
|
56
53
|
}
|
|
57
54
|
|
|
55
|
+
let addUserAction
|
|
58
56
|
if (isBrowserScope && agentRef.init.user_actions.enabled) {
|
|
59
57
|
this.userActionAggregator = new UserActionsAggregator()
|
|
60
58
|
|
|
61
|
-
|
|
59
|
+
addUserAction = (aggregatedUserAction) => {
|
|
62
60
|
try {
|
|
63
61
|
/** The aggregator process only returns an event when it is "done" aggregating -
|
|
64
62
|
* so we still need to validate that an event was given to this method before we try to add */
|
|
@@ -87,21 +85,50 @@ export class Aggregate extends AggregateBase {
|
|
|
87
85
|
|
|
88
86
|
registerHandler('ua', (evt) => {
|
|
89
87
|
/** the processor will return the previously aggregated event if it has been completed by processing the current event */
|
|
90
|
-
|
|
88
|
+
addUserAction(this.userActionAggregator.process(evt))
|
|
91
89
|
}, this.featureName, this.ee)
|
|
90
|
+
}
|
|
92
91
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
/**
|
|
93
|
+
* is it worth complicating the agent and skipping the POs for single repeating queries? maybe,
|
|
94
|
+
* but right now it was less desirable simply because it is a nice benefit of populating the event buffer
|
|
95
|
+
* immediately as events happen for payload evaluation purposes and that becomes a little more chaotic
|
|
96
|
+
* with an arbitrary query method. note: eventTypes: [...types] does not support the 'buffered' flag so we have
|
|
97
|
+
* to create up to two PO's here.
|
|
98
|
+
*/
|
|
99
|
+
const performanceTypesToCapture = [...(agentRef.init.performance.capture_marks ? ['mark'] : []), ...(agentRef.init.performance.capture_measures ? ['measure'] : [])]
|
|
100
|
+
if (performanceTypesToCapture.length) {
|
|
101
|
+
try {
|
|
102
|
+
performanceTypesToCapture.forEach(type => {
|
|
103
|
+
if (PerformanceObserver.supportedEntryTypes.includes(type)) {
|
|
104
|
+
const observer = new PerformanceObserver((list) => {
|
|
105
|
+
list.getEntries().forEach(entry => {
|
|
106
|
+
try {
|
|
107
|
+
this.addEvent({
|
|
108
|
+
eventType: 'BrowserPerformance',
|
|
109
|
+
timestamp: Math.floor(agentRef.runtime.timeKeeper.correctRelativeTimestamp(entry.startTime)),
|
|
110
|
+
entryName: entry.name,
|
|
111
|
+
entryDuration: entry.duration,
|
|
112
|
+
entryType: type,
|
|
113
|
+
...(entry.detail && { entryDetail: entry.detail })
|
|
114
|
+
})
|
|
115
|
+
} catch (err) {
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
observer.observe({ buffered: true, type })
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
} catch (err) {
|
|
123
|
+
// Something failed in our set up, likely the browser does not support PO's... do nothing
|
|
124
|
+
}
|
|
98
125
|
}
|
|
99
126
|
|
|
100
|
-
this.harvestScheduler = new HarvestScheduler(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
127
|
+
this.harvestScheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
|
|
128
|
+
onFinished: (result) => this.postHarvestCleanup(result.sent && result.retry),
|
|
129
|
+
onUnload: () => addUserAction?.(this.userActionAggregator.aggregationEvent)
|
|
130
|
+
}, this)
|
|
131
|
+
this.harvestScheduler.harvest.on(FEATURE_TO_ENDPOINT[this.featureName], (options) => this.makeHarvestPayload(options.retry))
|
|
105
132
|
this.harvestScheduler.startTimer(this.harvestTimeSeconds, 0)
|
|
106
133
|
|
|
107
134
|
this.drain()
|
|
@@ -155,34 +182,17 @@ export class Aggregate extends AggregateBase {
|
|
|
155
182
|
this.checkEventLimits()
|
|
156
183
|
}
|
|
157
184
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if (!this.events.hasData) return
|
|
161
|
-
var payload = ({
|
|
162
|
-
qs: {
|
|
163
|
-
ua: userAttributes,
|
|
164
|
-
at: atts
|
|
165
|
-
},
|
|
166
|
-
body: applyFnToProps(
|
|
167
|
-
{ ins: this.events.buffer },
|
|
168
|
-
this.obfuscator.obfuscateString.bind(this.obfuscator), 'string'
|
|
169
|
-
)
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
if (options.retry) this.events.hold()
|
|
173
|
-
else this.events.clear()
|
|
174
|
-
|
|
175
|
-
return payload
|
|
185
|
+
serializer (eventBuffer) {
|
|
186
|
+
return applyFnToProps({ ins: eventBuffer }, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string')
|
|
176
187
|
}
|
|
177
188
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
else this.events.held.clear()
|
|
189
|
+
queryStringsBuilder () {
|
|
190
|
+
return { ua: this.agentRef.info.userAttributes, at: this.agentRef.info.atts }
|
|
181
191
|
}
|
|
182
192
|
|
|
183
193
|
checkEventLimits () {
|
|
184
194
|
// check if we've reached any harvest limits...
|
|
185
|
-
if (this.events.
|
|
195
|
+
if (this.events.byteSize() > IDEAL_PAYLOAD_SIZE) {
|
|
186
196
|
this.ee.emit(SUPPORTABILITY_METRIC_CHANNEL, ['GenericEvents/Harvest/Max/Seen'])
|
|
187
197
|
this.harvestScheduler.runHarvest()
|
|
188
198
|
}
|
|
@@ -14,6 +14,8 @@ export class Instrument extends InstrumentBase {
|
|
|
14
14
|
super(agentRef, FEATURE_NAME, auto)
|
|
15
15
|
const genericEventSourceConfigs = [
|
|
16
16
|
agentRef.init.page_action.enabled,
|
|
17
|
+
agentRef.init.performance.capture_marks,
|
|
18
|
+
agentRef.init.performance.capture_measures,
|
|
17
19
|
agentRef.init.user_actions.enabled
|
|
18
20
|
// other future generic event source configs to go here, like M&Ms, PageResouce, etc.
|
|
19
21
|
]
|
|
@@ -15,9 +15,8 @@ import { handle } from '../../../common/event-emitter/handle'
|
|
|
15
15
|
import { globalScope } from '../../../common/constants/runtime'
|
|
16
16
|
|
|
17
17
|
import { FEATURE_NAME } from '../constants'
|
|
18
|
-
import { FEATURE_NAMES } from '../../../loaders/features/features'
|
|
18
|
+
import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
|
|
19
19
|
import { AggregateBase } from '../../utils/aggregate-base'
|
|
20
|
-
import { getNREUMInitializedAgent } from '../../../common/window/nreum'
|
|
21
20
|
import { now } from '../../../common/timing/now'
|
|
22
21
|
import { applyFnToProps } from '../../../common/util/traverse'
|
|
23
22
|
import { evaluateInternalError } from './internal-errors'
|
|
@@ -36,7 +35,6 @@ export class Aggregate extends AggregateBase {
|
|
|
36
35
|
this.observedAt = {}
|
|
37
36
|
this.pageviewReported = {}
|
|
38
37
|
this.bufferedErrorsUnderSpa = {}
|
|
39
|
-
this.currentBody = undefined
|
|
40
38
|
this.errorOnPage = false
|
|
41
39
|
|
|
42
40
|
// this will need to change to match whatever ee we use in the instrument
|
|
@@ -48,12 +46,15 @@ export class Aggregate extends AggregateBase {
|
|
|
48
46
|
this.onSoftNavNotification(interactionId, wasFinished, softNavAttrs), this.featureName, this.ee) // when an ixn is done or cancelled
|
|
49
47
|
|
|
50
48
|
const harvestTimeSeconds = agentRef.init.jserrors.harvestTimeSeconds || 10
|
|
49
|
+
const aggregatorTypes = ['err', 'ierr', 'xhr'] // the types in EventAggregator this feature cares about
|
|
51
50
|
|
|
52
51
|
// 0 == off, 1 == on
|
|
53
52
|
this.waitForFlags(['err']).then(([errFlag]) => {
|
|
54
53
|
if (errFlag) {
|
|
55
|
-
const scheduler = new HarvestScheduler(
|
|
56
|
-
|
|
54
|
+
const scheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
|
|
55
|
+
onFinished: (result) => this.postHarvestCleanup(result.sent && result.retry, { aggregatorTypes })
|
|
56
|
+
}, this)
|
|
57
|
+
scheduler.harvest.on(FEATURE_TO_ENDPOINT[this.featureName], (options) => this.makeHarvestPayload(options.retry, { aggregatorTypes }))
|
|
57
58
|
scheduler.startTimer(harvestTimeSeconds)
|
|
58
59
|
this.drain()
|
|
59
60
|
} else {
|
|
@@ -63,58 +64,24 @@ export class Aggregate extends AggregateBase {
|
|
|
63
64
|
})
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
this.agentRef.sharedAggregator.take(['err', 'ierr', 'xhr']),
|
|
70
|
-
this.obfuscator.obfuscateString.bind(this.obfuscator), 'string'
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
if (options.retry) {
|
|
74
|
-
this.currentBody = body
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
var payload = { body, qs: {} }
|
|
78
|
-
var releaseIds = stringify(this.agentRef.runtime.releaseIds)
|
|
67
|
+
serializer (aggregatorTypeToBucketsMap) {
|
|
68
|
+
return applyFnToProps(aggregatorTypeToBucketsMap, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string')
|
|
69
|
+
}
|
|
79
70
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
71
|
+
queryStringsBuilder (aggregatorTakeReturnedData) {
|
|
72
|
+
const qs = {}
|
|
73
|
+
const releaseIds = stringify(this.agentRef.runtime.releaseIds)
|
|
74
|
+
if (releaseIds !== '{}') qs.ri = releaseIds
|
|
83
75
|
|
|
84
|
-
if (
|
|
85
|
-
this.#runCrossFeatureChecks(body.err)
|
|
76
|
+
if (aggregatorTakeReturnedData?.err?.length) {
|
|
86
77
|
if (!this.errorOnPage) {
|
|
87
|
-
|
|
78
|
+
qs.pve = '1'
|
|
88
79
|
this.errorOnPage = true
|
|
89
80
|
}
|
|
81
|
+
// For assurance, erase any `hasReplay` flag from all errors if replay is not recording, not-yet imported, or not running at all.
|
|
82
|
+
if (!this.agentRef.features?.[FEATURE_NAMES.sessionReplay]?.featAggregate?.replayIsActive()) aggregatorTakeReturnedData.err.forEach(error => delete error.params.hasReplay)
|
|
90
83
|
}
|
|
91
|
-
|
|
92
|
-
return payload
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
onHarvestFinished (result) {
|
|
96
|
-
if (result.retry && this.currentBody) {
|
|
97
|
-
Object.entries(this.currentBody || {}).forEach(([key, value]) => {
|
|
98
|
-
for (var i = 0; i < value.length; i++) {
|
|
99
|
-
var bucket = value[i]
|
|
100
|
-
var name = this.getBucketName(key, bucket.params, bucket.custom)
|
|
101
|
-
this.agentRef.sharedAggregator.merge(key, name, bucket.metrics, bucket.params, bucket.custom)
|
|
102
|
-
}
|
|
103
|
-
})
|
|
104
|
-
this.currentBody = null
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
nameHash (params) {
|
|
109
|
-
return stringHashCode(`${params.exceptionClass}_${params.message}_${params.stack_trace || params.browser_stack_hash}`)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
getBucketName (objType, params, customParams) {
|
|
113
|
-
if (objType === 'xhr') {
|
|
114
|
-
return stringHashCode(stringify(params)) + ':' + stringHashCode(stringify(customParams))
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return this.nameHash(params) + ':' + stringHashCode(stringify(customParams))
|
|
84
|
+
return qs
|
|
118
85
|
}
|
|
119
86
|
|
|
120
87
|
/**
|
|
@@ -230,7 +197,7 @@ export class Aggregate extends AggregateBase {
|
|
|
230
197
|
params._interactionNodeId = err.__newrelic[this.agentIdentifier].interactionNodeId
|
|
231
198
|
}
|
|
232
199
|
|
|
233
|
-
const softNavInUse = Boolean(
|
|
200
|
+
const softNavInUse = Boolean(this.agentRef.features?.[FEATURE_NAMES.softNav])
|
|
234
201
|
// 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.
|
|
235
202
|
// They each will also tack on their respective properties to the params object as part of the decision flow.
|
|
236
203
|
if (softNavInUse) handle('jserror', [params, time], undefined, FEATURE_NAMES.softNav, this.ee)
|
|
@@ -267,7 +234,7 @@ export class Aggregate extends AggregateBase {
|
|
|
267
234
|
|
|
268
235
|
const jsAttributesHash = stringHashCode(stringify(allCustomAttrs))
|
|
269
236
|
const aggregateHash = bucketHash + ':' + jsAttributesHash
|
|
270
|
-
this.
|
|
237
|
+
this.events.add(type, aggregateHash, params, newMetrics, allCustomAttrs)
|
|
271
238
|
|
|
272
239
|
function setCustom (key, val) {
|
|
273
240
|
allCustomAttrs[key] = (val && typeof val === 'object' ? stringify(val) : val)
|
|
@@ -297,7 +264,7 @@ export class Aggregate extends AggregateBase {
|
|
|
297
264
|
var jsAttributesHash = stringHashCode(stringify(allCustomAttrs))
|
|
298
265
|
var aggregateHash = hash + ':' + jsAttributesHash
|
|
299
266
|
|
|
300
|
-
this.
|
|
267
|
+
this.events.add(item[0], aggregateHash, params, item[3], allCustomAttrs)
|
|
301
268
|
|
|
302
269
|
function setCustom ([key, val]) {
|
|
303
270
|
allCustomAttrs[key] = (val && typeof val === 'object' ? stringify(val) : val)
|
|
@@ -314,27 +281,4 @@ export class Aggregate extends AggregateBase {
|
|
|
314
281
|
)
|
|
315
282
|
delete this.bufferedErrorsUnderSpa[interactionId] // wipe the list of jserrors so they aren't duplicated by another call to the same id
|
|
316
283
|
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Dispatches a cross-feature communication event to allow other
|
|
320
|
-
* features to provide flags and data that can be used to mutation
|
|
321
|
-
* to the payload and to allow features to know about a feature
|
|
322
|
-
* harvest happening.
|
|
323
|
-
* @param {any[]} errors Array of errors from the payload body
|
|
324
|
-
*/
|
|
325
|
-
#runCrossFeatureChecks (errors) {
|
|
326
|
-
const errorHashes = errors.map(error => error.params.stackHash)
|
|
327
|
-
const crossFeatureData = {
|
|
328
|
-
errorHashes
|
|
329
|
-
}
|
|
330
|
-
this.ee.emit(`cfc.${this.featureName}`, [crossFeatureData])
|
|
331
|
-
|
|
332
|
-
let hasReplayFlag = errors.find(err => err.params.hasReplay)
|
|
333
|
-
if (hasReplayFlag && !crossFeatureData.hasReplay) {
|
|
334
|
-
// Some errors have `hasReplay` and a replay is not being recorded
|
|
335
|
-
errors.forEach(error => {
|
|
336
|
-
delete error.params.hasReplay
|
|
337
|
-
})
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
284
|
}
|
|
@@ -10,23 +10,19 @@ import { Log } from '../shared/log'
|
|
|
10
10
|
import { isValidLogLevel } from '../shared/utils'
|
|
11
11
|
import { applyFnToProps } from '../../../common/util/traverse'
|
|
12
12
|
import { MAX_PAYLOAD_SIZE } from '../../../common/constants/agent-constants'
|
|
13
|
-
import {
|
|
13
|
+
import { FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
|
|
14
14
|
|
|
15
15
|
export class Aggregate extends AggregateBase {
|
|
16
16
|
static featureName = FEATURE_NAME
|
|
17
17
|
constructor (agentRef) {
|
|
18
18
|
super(agentRef, FEATURE_NAME)
|
|
19
|
-
|
|
20
|
-
/** held logs before sending */
|
|
21
|
-
this.bufferedLogs = new EventBuffer()
|
|
22
|
-
|
|
23
19
|
this.harvestTimeSeconds = agentRef.init.logging.harvestTimeSeconds
|
|
24
20
|
|
|
25
21
|
this.waitForFlags([]).then(() => {
|
|
26
|
-
this.scheduler = new HarvestScheduler(
|
|
27
|
-
onFinished: this.
|
|
22
|
+
this.scheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
|
|
23
|
+
onFinished: (result) => this.postHarvestCleanup(result.sent && result.retry),
|
|
28
24
|
retryDelay: this.harvestTimeSeconds,
|
|
29
|
-
getPayload: this.
|
|
25
|
+
getPayload: (options) => this.makeHarvestPayload(options.retry),
|
|
30
26
|
raw: true
|
|
31
27
|
}, this)
|
|
32
28
|
/** emitted by instrument class (wrapped loggers) or the api methods directly */
|
|
@@ -69,65 +65,55 @@ export class Aggregate extends AggregateBase {
|
|
|
69
65
|
)
|
|
70
66
|
const logBytes = log.message.length + stringify(log.attributes).length + log.level.length + 10 // timestamp == 10 chars
|
|
71
67
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (logBytes < MAX_PAYLOAD_SIZE) this.bufferedLogs.add(log)
|
|
77
|
-
} else {
|
|
78
|
-
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Logging/Harvest/Failed/Seen', logBytes])
|
|
79
|
-
warn(31, log.message.slice(0, 25) + '...')
|
|
80
|
-
}
|
|
68
|
+
const failToHarvestMessage = 'Logging/Harvest/Failed/Seen'
|
|
69
|
+
if (logBytes > MAX_PAYLOAD_SIZE) { // cannot possibly send this, even with an empty buffer
|
|
70
|
+
handle(SUPPORTABILITY_METRIC_CHANNEL, [failToHarvestMessage, logBytes])
|
|
71
|
+
warn(31, log.message.slice(0, 25) + '...')
|
|
81
72
|
return
|
|
82
73
|
}
|
|
83
74
|
|
|
84
|
-
this.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
prepareHarvest (options = {}) {
|
|
88
|
-
if (this.blocked || !this.bufferedLogs.hasData) return
|
|
89
|
-
/** These attributes are evaluated and dropped at ingest processing time and do not get stored on NRDB */
|
|
90
|
-
const unbilledAttributes = {
|
|
91
|
-
'instrumentation.provider': 'browser',
|
|
92
|
-
'instrumentation.version': this.agentRef.runtime.version,
|
|
93
|
-
'instrumentation.name': this.agentRef.runtime.loaderType
|
|
94
|
-
}
|
|
95
|
-
/** see https://source.datanerd.us/agents/rum-specs/blob/main/browser/Log for logging spec */
|
|
96
|
-
const payload = {
|
|
97
|
-
qs: {
|
|
98
|
-
browser_monitoring_key: this.agentRef.info.licenseKey
|
|
99
|
-
},
|
|
100
|
-
body: [{
|
|
101
|
-
common: {
|
|
102
|
-
/** Attributes in the `common` section are added to `all` logs generated in the payload */
|
|
103
|
-
attributes: {
|
|
104
|
-
'entity.guid': this.agentRef.runtime.appMetadata?.agents?.[0]?.entityGuid, // browser entity guid as provided from RUM response
|
|
105
|
-
session: this.agentRef.runtime.session?.state.value || '0', // The session ID that we generate and keep across page loads
|
|
106
|
-
hasReplay: this.agentRef.runtime.session?.state.sessionReplayMode === 1, // True if a session replay recording is running
|
|
107
|
-
hasTrace: this.agentRef.runtime.session?.state.sessionTraceMode === 1, // True if a session trace recording is running
|
|
108
|
-
ptid: this.agentRef.runtime.ptid, // page trace id
|
|
109
|
-
appId: this.agentRef.info.applicationID, // Application ID from info object,
|
|
110
|
-
standalone: Boolean(this.agentRef.info.sa), // copy paste (true) vs APM (false)
|
|
111
|
-
agentVersion: this.agentRef.runtime.version, // browser agent version
|
|
112
|
-
...unbilledAttributes
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
|
-
/** logs section contains individual unique log entries */
|
|
116
|
-
logs: applyFnToProps(
|
|
117
|
-
this.bufferedLogs.buffer,
|
|
118
|
-
this.obfuscator.obfuscateString.bind(this.obfuscator), 'string'
|
|
119
|
-
)
|
|
120
|
-
}]
|
|
75
|
+
if (this.events.wouldExceedMaxSize(logBytes)) {
|
|
76
|
+
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Logging/Harvest/Early/Seen', this.events.bytes + logBytes])
|
|
77
|
+
this.scheduler.runHarvest() // force a harvest to try adding again
|
|
121
78
|
}
|
|
122
79
|
|
|
123
|
-
if (
|
|
124
|
-
|
|
80
|
+
if (!this.events.add(log)) { // still failed after a harvest attempt despite not being too large would mean harvest failed with options.retry
|
|
81
|
+
handle(SUPPORTABILITY_METRIC_CHANNEL, [failToHarvestMessage, logBytes])
|
|
82
|
+
warn(31, log.message.slice(0, 25) + '...')
|
|
83
|
+
}
|
|
84
|
+
}
|
|
125
85
|
|
|
126
|
-
|
|
86
|
+
serializer (eventBuffer) {
|
|
87
|
+
const sessionEntity = this.agentRef.runtime.session
|
|
88
|
+
return [{
|
|
89
|
+
common: {
|
|
90
|
+
/** Attributes in the `common` section are added to `all` logs generated in the payload */
|
|
91
|
+
attributes: {
|
|
92
|
+
'entity.guid': this.agentRef.runtime.appMetadata?.agents?.[0]?.entityGuid, // browser entity guid as provided from RUM response
|
|
93
|
+
...(sessionEntity && {
|
|
94
|
+
session: sessionEntity.state.value || '0', // The session ID that we generate and keep across page loads
|
|
95
|
+
hasReplay: sessionEntity.state.sessionReplayMode === 1, // True if a session replay recording is running
|
|
96
|
+
hasTrace: sessionEntity.state.sessionTraceMode === 1 // True if a session trace recording is running
|
|
97
|
+
}),
|
|
98
|
+
ptid: this.agentRef.runtime.ptid, // page trace id
|
|
99
|
+
appId: this.agentRef.info.applicationID, // Application ID from info object,
|
|
100
|
+
standalone: Boolean(this.agentRef.info.sa), // copy paste (true) vs APM (false)
|
|
101
|
+
agentVersion: this.agentRef.runtime.version, // browser agent version
|
|
102
|
+
// The following 3 attributes are evaluated and dropped at ingest processing time and do not get stored on NRDB:
|
|
103
|
+
'instrumentation.provider': 'browser',
|
|
104
|
+
'instrumentation.version': this.agentRef.runtime.version,
|
|
105
|
+
'instrumentation.name': this.agentRef.runtime.loaderType
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
/** logs section contains individual unique log entries */
|
|
109
|
+
logs: applyFnToProps(
|
|
110
|
+
eventBuffer,
|
|
111
|
+
this.obfuscator.obfuscateString.bind(this.obfuscator), 'string'
|
|
112
|
+
)
|
|
113
|
+
}]
|
|
127
114
|
}
|
|
128
115
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
else this.bufferedLogs.held.clear()
|
|
116
|
+
queryStringsBuilder () {
|
|
117
|
+
return { browser_monitoring_key: this.agentRef.info.licenseKey }
|
|
132
118
|
}
|
|
133
119
|
}
|