newrelic 9.7.0 → 9.7.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/NEWS.md +42 -18
- package/THIRD_PARTY_NOTICES.md +203 -2
- package/api.js +155 -103
- package/lib/agent.js +29 -82
- package/lib/collector/api.js +234 -201
- package/lib/config/attribute-filter.js +35 -25
- package/lib/config/default.js +45 -19
- package/lib/config/index.js +260 -199
- package/lib/environment.js +16 -12
- package/lib/errors/error-collector.js +124 -55
- package/lib/errors/index.js +59 -49
- package/lib/instrumentation/@hapi/hapi.js +56 -50
- package/lib/instrumentation/@node-redis/client.js +50 -41
- package/lib/instrumentation/amqplib.js +116 -151
- package/lib/instrumentation/core/{async_hooks.js → async-hooks.js} +26 -11
- package/lib/instrumentation/core/globals.js +1 -1
- package/lib/instrumentation/core/http-outbound.js +193 -78
- package/lib/instrumentation/core/timers.js +106 -59
- package/lib/instrumentation/grpc-js/grpc.js +20 -23
- package/lib/instrumentation/mongodb/common.js +87 -85
- package/lib/instrumentation/redis.js +112 -90
- package/lib/instrumentation/undici.js +204 -192
- package/lib/instrumentation/{when.js → when/constants.js} +13 -10
- package/lib/instrumentation/when/contextualizer.js +168 -0
- package/lib/instrumentation/when/index.js +354 -0
- package/lib/instrumentation/when/nr-hooks.js +15 -0
- package/lib/instrumentations.js +1 -1
- package/lib/shim/shim.js +2 -0
- package/lib/shim/webframework-shim.js +19 -0
- package/lib/symbols.js +1 -0
- package/lib/system-info.js +240 -163
- package/lib/util/async-each-limit.js +30 -0
- package/lib/util/attributes.js +159 -0
- package/lib/util/code-level-metrics.js +58 -0
- package/lib/util/deep-equal.js +11 -144
- package/package.json +5 -4
- package/lib/instrumentation/promise.js +0 -572
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict'
|
|
7
7
|
|
|
8
|
-
/* eslint sonarjs/cognitive-complexity: ["error", 53] -- TODO: https://issues.newrelic.com/browse/NEWRELIC-5252 */
|
|
9
|
-
|
|
10
8
|
const cat = require('../util/cat')
|
|
11
9
|
const recordExternal = require('../metrics/recorders/http_external')
|
|
12
10
|
const logger = require('../logger').child({ component: 'undici' })
|
|
@@ -14,6 +12,8 @@ const NAMES = require('../metrics/names')
|
|
|
14
12
|
const NEWRELIC_SYNTHETICS_HEADER = 'x-newrelic-synthetics'
|
|
15
13
|
const symbols = require('../symbols')
|
|
16
14
|
const { executionAsyncResource } = require('async_hooks')
|
|
15
|
+
const tls = require('tls')
|
|
16
|
+
const net = require('net')
|
|
17
17
|
|
|
18
18
|
let diagnosticsChannel = null
|
|
19
19
|
try {
|
|
@@ -23,7 +23,7 @@ try {
|
|
|
23
23
|
// module was not added until v15.x
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
module.exports = function addUndiciChannels(agent,
|
|
26
|
+
module.exports = function addUndiciChannels(agent, _undici, _modName, shim) {
|
|
27
27
|
if (!diagnosticsChannel || !agent.config.feature_flag.undici_instrumentation) {
|
|
28
28
|
logger.warn(
|
|
29
29
|
'diagnostics_channel or feature_flag.undici_instrumentation = false. Skipping undici instrumentation.'
|
|
@@ -31,209 +31,221 @@ module.exports = function addUndiciChannels(agent, undici, modName, shim) {
|
|
|
31
31
|
return
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
34
|
+
registerHookPoints(shim)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Subscribes to all relevant undici hook points
|
|
39
|
+
* See: https://github.com/nodejs/undici/blob/main/docs/api/DiagnosticsChannel.md
|
|
40
|
+
*
|
|
41
|
+
* @param {Shim} shim instance of shim
|
|
42
|
+
*/
|
|
43
|
+
function registerHookPoints(shim) {
|
|
44
|
+
diagnosticsChannel.channel('undici:request:create').subscribe(requestCreateHook.bind(null, shim))
|
|
45
|
+
diagnosticsChannel
|
|
46
|
+
.channel('undici:client:sendHeaders')
|
|
47
|
+
.subscribe(responseHeadersHook.bind(null, shim))
|
|
48
|
+
diagnosticsChannel
|
|
49
|
+
.channel('undici:request:headers')
|
|
50
|
+
.subscribe(requestHeadersHook.bind(null, shim))
|
|
51
|
+
diagnosticsChannel
|
|
52
|
+
.channel('undici:request:trailers')
|
|
53
|
+
.subscribe(endAndRestoreSegment.bind(null, shim))
|
|
54
|
+
diagnosticsChannel
|
|
55
|
+
.channel('undici:request:error')
|
|
56
|
+
.subscribe(endAndRestoreSegment.bind(null, shim))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Retrieves the current segment in transaction(parent in our context) from executionAsyncResource
|
|
61
|
+
* or from `shim.getSegment()` then adds to the executionAsyncResource for future
|
|
62
|
+
* undici requests within same async context.
|
|
63
|
+
*
|
|
64
|
+
* It was found that when running concurrent undici requests
|
|
65
|
+
* within a transaction that the parent segment would get out of sync
|
|
66
|
+
* depending on the async context of the transaction. By using
|
|
67
|
+
* `async_hooks.executionResource` it is more reliable.
|
|
68
|
+
*
|
|
69
|
+
* Note: However, if you have concurrent undici requests in a transaction
|
|
70
|
+
* and the request to the transaction is using a keep alive there is a chance the
|
|
71
|
+
* executionAsyncResource may be incorrect because of shared connections. To revert to a more
|
|
72
|
+
* naive tracking of parent set `config.feature_flag.undici_async_tracking: false` and
|
|
73
|
+
* it will just call `shim.getSegment()`
|
|
74
|
+
*
|
|
75
|
+
* @param {Shim} shim instance of shim
|
|
76
|
+
* @returns {TraceSegment} parent segment
|
|
77
|
+
*/
|
|
78
|
+
function getParentSegment(shim) {
|
|
79
|
+
const { config } = shim.agent
|
|
80
|
+
if (config.feature_flag.undici_async_tracking) {
|
|
81
|
+
const resource = executionAsyncResource()
|
|
82
|
+
|
|
83
|
+
if (!resource[symbols.parentSegment]) {
|
|
84
|
+
const parent = shim.getSegment()
|
|
85
|
+
resource[symbols.parentSegment] = parent
|
|
59
86
|
}
|
|
60
|
-
return
|
|
87
|
+
return resource[symbols.parentSegment]
|
|
61
88
|
}
|
|
89
|
+
return shim.getSegment()
|
|
90
|
+
}
|
|
62
91
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
92
|
+
/**
|
|
93
|
+
* This event occurs after the Undici Request is created
|
|
94
|
+
* We will check current segment for opaque and also attach
|
|
95
|
+
* relevant headers to outgoing http request
|
|
96
|
+
*
|
|
97
|
+
* @param {Shim} shim instance of shim
|
|
98
|
+
* @param {object} params object from undici hook
|
|
99
|
+
* @param {object} params.request undici request object
|
|
100
|
+
*/
|
|
101
|
+
function requestCreateHook(shim, { request }) {
|
|
102
|
+
const { config } = shim.agent
|
|
103
|
+
const parent = getParentSegment(shim)
|
|
104
|
+
request[symbols.parentSegment] = parent
|
|
105
|
+
if (!parent || (parent && parent.opaque)) {
|
|
106
|
+
logger.trace(
|
|
107
|
+
'Not capturing data for outbound request (%s) because parent segment opaque (%s)',
|
|
108
|
+
request.path,
|
|
109
|
+
parent && parent.name
|
|
110
|
+
)
|
|
83
111
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (agent.config.encoding_key && transaction.syntheticsHeader) {
|
|
87
|
-
outboundHeaders[NEWRELIC_SYNTHETICS_HEADER] = transaction.syntheticsHeader
|
|
88
|
-
}
|
|
112
|
+
return
|
|
113
|
+
}
|
|
89
114
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
logger.trace('Both DT and CAT are disabled, not adding headers!')
|
|
96
|
-
}
|
|
115
|
+
const transaction = parent.transaction
|
|
116
|
+
const outboundHeaders = Object.create(null)
|
|
117
|
+
if (config.encoding_key && transaction.syntheticsHeader) {
|
|
118
|
+
outboundHeaders[NEWRELIC_SYNTHETICS_HEADER] = transaction.syntheticsHeader
|
|
119
|
+
}
|
|
97
120
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
* This event occurs right before the data is written to the socket.
|
|
106
|
-
* Undici has some abstracted headers that are only created at this time, one
|
|
107
|
-
* is the `host` header which we need to name the Undici segment. So in this
|
|
108
|
-
* handler we create, start and set the segment active, name it, and
|
|
109
|
-
* attach the url/procedure/request.parameters
|
|
110
|
-
*
|
|
111
|
-
* @param {object} params
|
|
112
|
-
* @param {object} params.request undicie request object
|
|
113
|
-
* @param {TLSSocket | net.Socket} socket active socket connection
|
|
114
|
-
*/
|
|
115
|
-
diagnosticsChannel.channel('undici:client:sendHeaders').subscribe(({ request, socket }) => {
|
|
116
|
-
const parentSegment = request[symbols.parentSegment]
|
|
117
|
-
if (!parentSegment || (parentSegment && parentSegment.opaque)) {
|
|
118
|
-
return
|
|
119
|
-
}
|
|
121
|
+
if (config.distributed_tracing.enabled) {
|
|
122
|
+
transaction.insertDistributedTraceHeaders(outboundHeaders)
|
|
123
|
+
} else if (config.cross_application_tracer.enabled) {
|
|
124
|
+
cat.addCatHeaders(config, transaction, outboundHeaders)
|
|
125
|
+
} else {
|
|
126
|
+
logger.trace('Both DT and CAT are disabled, not adding headers!')
|
|
127
|
+
}
|
|
120
128
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
urlString += port === 443 ? request.path : `:${port}${request.path}`
|
|
127
|
-
} else {
|
|
128
|
-
urlString = `http://${socket._host}`
|
|
129
|
-
urlString += port === 80 ? request.path : `:${port}${request.path}`
|
|
130
|
-
}
|
|
129
|
+
// eslint-disable-next-line guard-for-in
|
|
130
|
+
for (const key in outboundHeaders) {
|
|
131
|
+
request.addHeader(key, outboundHeaders[key])
|
|
132
|
+
}
|
|
133
|
+
}
|
|
131
134
|
|
|
132
|
-
|
|
135
|
+
/**
|
|
136
|
+
* This event occurs right before the data is written to the socket.
|
|
137
|
+
* Undici has some abstracted headers that are only created at this time, one
|
|
138
|
+
* is the `host` header which we need to name the Undici segment. So in this
|
|
139
|
+
* handler we create, start and set the segment active, name it, and
|
|
140
|
+
* attach the url/procedure/request.parameters
|
|
141
|
+
*
|
|
142
|
+
* @param {Shim} shim instance of shim
|
|
143
|
+
* @param {object} params object from undici hook
|
|
144
|
+
* @param {object} params.request undici request object
|
|
145
|
+
* @param {tls.TLSSocket | net.Socket} params.socket active socket connection
|
|
146
|
+
*/
|
|
147
|
+
function responseHeadersHook(shim, { request, socket }) {
|
|
148
|
+
const parentSegment = request[symbols.parentSegment]
|
|
149
|
+
if (!parentSegment || (parentSegment && parentSegment.opaque)) {
|
|
150
|
+
return
|
|
151
|
+
}
|
|
133
152
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
153
|
+
const port = socket.remotePort
|
|
154
|
+
const isHttps = socket.servername
|
|
155
|
+
let urlString
|
|
156
|
+
if (isHttps) {
|
|
157
|
+
urlString = `https://${socket.servername}`
|
|
158
|
+
urlString += port === 443 ? request.path : `:${port}${request.path}`
|
|
159
|
+
} else {
|
|
160
|
+
urlString = `http://${socket._host}`
|
|
161
|
+
urlString += port === 80 ? request.path : `:${port}${request.path}`
|
|
162
|
+
}
|
|
140
163
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
164
|
+
const url = new URL(urlString)
|
|
165
|
+
|
|
166
|
+
const name = NAMES.EXTERNAL.PREFIX + url.host + url.pathname
|
|
167
|
+
const segment = shim.createSegment(name, recordExternal(url.host, 'undici'), parentSegment)
|
|
168
|
+
if (segment) {
|
|
169
|
+
segment.start()
|
|
170
|
+
shim.setActiveSegment(segment)
|
|
171
|
+
segment.addAttribute('url', `${url.protocol}//${url.host}${url.pathname}`)
|
|
172
|
+
|
|
173
|
+
url.searchParams.forEach((value, key) => {
|
|
174
|
+
segment.addSpanAttribute(`request.parameters.${key}`, value)
|
|
175
|
+
})
|
|
176
|
+
segment.addAttribute('procedure', request.method || 'GET')
|
|
177
|
+
request[symbols.segment] = segment
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* This event occurs after the response headers have been received.
|
|
183
|
+
* We will add the relevant http response attributes to active segment.
|
|
184
|
+
* Also add CAT specific keys to active segment.
|
|
185
|
+
*
|
|
186
|
+
* @param {Shim} shim instance of shim
|
|
187
|
+
* @param {object} params object from undici hook
|
|
188
|
+
* @param {object} params.request undici request object
|
|
189
|
+
* @param {object} params.response { statusCode, headers, statusText }
|
|
190
|
+
*/
|
|
191
|
+
function requestHeadersHook(shim, { request, response }) {
|
|
192
|
+
const { config } = shim.agent
|
|
193
|
+
const activeSegment = request[symbols.segment]
|
|
194
|
+
if (!activeSegment) {
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
activeSegment.addSpanAttribute('http.statusCode', response.statusCode)
|
|
199
|
+
activeSegment.addSpanAttribute('http.statusText', response.statusText)
|
|
200
|
+
|
|
201
|
+
if (config.cross_application_tracer.enabled && !config.distributed_tracing.enabled) {
|
|
202
|
+
try {
|
|
203
|
+
const { appData } = cat.extractCatHeaders(response.headers)
|
|
204
|
+
const decodedAppData = cat.parseAppData(config, appData)
|
|
205
|
+
const attrs = activeSegment.getAttributes()
|
|
206
|
+
const url = new URL(attrs.url)
|
|
207
|
+
cat.assignCatToSegment(decodedAppData, activeSegment, url.host)
|
|
208
|
+
} catch (err) {
|
|
209
|
+
logger.warn(err, 'Cannot add CAT data to segment')
|
|
162
210
|
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
163
213
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
214
|
+
/**
|
|
215
|
+
* Gets the active and parent from given ctx(request, client connector)
|
|
216
|
+
* and ends active and restores parent to active. If an error exists
|
|
217
|
+
* it will add the error to the transaction
|
|
218
|
+
*
|
|
219
|
+
* @param {Shim} shim instance of shim
|
|
220
|
+
* @param {object} params object from undici hook
|
|
221
|
+
* @param {object} params.request or client connector
|
|
222
|
+
* @param {Error} params.error error from undici request
|
|
223
|
+
*/
|
|
224
|
+
function endAndRestoreSegment(shim, { request, error }) {
|
|
225
|
+
const activeSegment = request[symbols.segment]
|
|
226
|
+
const parentSegment = request[symbols.parentSegment]
|
|
227
|
+
if (activeSegment) {
|
|
228
|
+
activeSegment.end()
|
|
229
|
+
|
|
230
|
+
if (error) {
|
|
231
|
+
handleError(shim, activeSegment, error)
|
|
180
232
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
* This event occurs after the response body has been received.
|
|
185
|
-
* We will end the active segment and set the active back to parent before request
|
|
186
|
-
*
|
|
187
|
-
* @param {object} params.request undici request object
|
|
188
|
-
*/
|
|
189
|
-
diagnosticsChannel.channel('undici:request:trailers').subscribe(({ request }) => {
|
|
190
|
-
endAndRestoreSegment(request)
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* This event occurs right before the request emits an error.
|
|
195
|
-
* We will end the active segment and set the active back to parent before request.
|
|
196
|
-
* We will also log errors to NR
|
|
197
|
-
*
|
|
198
|
-
* Note: This event occurs before the error handler so we will always log it for now.
|
|
199
|
-
*/
|
|
200
|
-
diagnosticsChannel.channel('undici:request:error').subscribe(({ request, error }) => {
|
|
201
|
-
endAndRestoreSegment(request, error)
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Gets the active and parent from given ctx(request, client connector)
|
|
206
|
-
* and ends active and restores parent to active. If an error exists
|
|
207
|
-
* it will add the error to the transaction
|
|
208
|
-
*
|
|
209
|
-
* @param {object} ctx request or client connector
|
|
210
|
-
* @param {Error} error
|
|
211
|
-
*/
|
|
212
|
-
function endAndRestoreSegment(ctx, error) {
|
|
213
|
-
const activeSegment = ctx[symbols.segment]
|
|
214
|
-
const parentSegment = ctx[symbols.parentSegment]
|
|
215
|
-
if (activeSegment) {
|
|
216
|
-
activeSegment.end()
|
|
217
|
-
|
|
218
|
-
if (error) {
|
|
219
|
-
handleError(activeSegment, error)
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
if (parentSegment) {
|
|
223
|
-
shim.setActiveSegment(parentSegment)
|
|
224
|
-
}
|
|
233
|
+
|
|
234
|
+
if (parentSegment) {
|
|
235
|
+
shim.setActiveSegment(parentSegment)
|
|
225
236
|
}
|
|
226
237
|
}
|
|
238
|
+
}
|
|
227
239
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
240
|
+
/**
|
|
241
|
+
* Adds the error to the active transaction
|
|
242
|
+
*
|
|
243
|
+
* @param {Shim} shim instance of shim
|
|
244
|
+
* @param {TraceSegment} activeSegment active segment
|
|
245
|
+
* @param {Error} error error from undici request
|
|
246
|
+
*/
|
|
247
|
+
function handleError(shim, activeSegment, error) {
|
|
248
|
+
logger.trace(error, 'Captured outbound error on behalf of the user.')
|
|
249
|
+
const { transaction } = activeSegment
|
|
250
|
+
shim.agent.errors.add(transaction, error)
|
|
239
251
|
}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
2
|
+
* Copyright 2022 New Relic Corporation. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
'use strict'
|
|
7
7
|
|
|
8
|
-
const promInit = require('./promise')
|
|
9
|
-
|
|
10
8
|
const STATIC_PROMISE_METHODS = [
|
|
11
9
|
'reject',
|
|
12
10
|
'resolve',
|
|
@@ -21,12 +19,22 @@ const STATIC_PROMISE_METHODS = [
|
|
|
21
19
|
|
|
22
20
|
const WHEN_SPEC = {
|
|
23
21
|
name: 'when',
|
|
22
|
+
// name of the property where the Promise constructor lives
|
|
24
23
|
constructor: 'Promise',
|
|
24
|
+
// wrap The Promise constructor
|
|
25
25
|
executor: true,
|
|
26
|
+
/**
|
|
27
|
+
* The mapping for Promise instance method concepts (i.e. `then`). These are
|
|
28
|
+
* mapped on the Promise class' prototype.
|
|
29
|
+
*/
|
|
26
30
|
$proto: {
|
|
27
31
|
then: ['then', 'done', 'spread', 'finally', 'ensure'],
|
|
28
32
|
catch: ['catch', 'otherwise']
|
|
29
33
|
},
|
|
34
|
+
/**
|
|
35
|
+
* The mapping for Promise static method concepts (i.e. `settle`, `race`). These
|
|
36
|
+
* are mapped on the Promise class itself.
|
|
37
|
+
*/
|
|
30
38
|
$static: {
|
|
31
39
|
cast: STATIC_PROMISE_METHODS,
|
|
32
40
|
$copy: STATIC_PROMISE_METHODS.concat([
|
|
@@ -49,15 +57,10 @@ const WHEN_SPEC = {
|
|
|
49
57
|
'onPotentiallyUnhandledRejection'
|
|
50
58
|
]
|
|
51
59
|
},
|
|
60
|
+
// The mapping for library-level static method concepts (i.e. `reject`, `resolve`).
|
|
52
61
|
$library: {
|
|
53
62
|
cast: STATIC_PROMISE_METHODS
|
|
54
63
|
}
|
|
55
64
|
}
|
|
56
65
|
|
|
57
|
-
module.exports =
|
|
58
|
-
if (!library || !library.Promise) {
|
|
59
|
-
return false
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
promInit(agent, library, WHEN_SPEC)
|
|
63
|
-
}
|
|
66
|
+
module.exports.WHEN_SPEC = WHEN_SPEC
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2022 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
const symbols = require('../../symbols')
|
|
8
|
+
|
|
9
|
+
function Context(segment) {
|
|
10
|
+
this.segments = [segment]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
Context.prototype = Object.create(null)
|
|
14
|
+
|
|
15
|
+
Context.prototype.branch = function branch() {
|
|
16
|
+
return this.segments.push(null) - 1
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function Contextualizer(idx, context) {
|
|
20
|
+
this.parentIdx = -1
|
|
21
|
+
this.idx = idx
|
|
22
|
+
this.context = context
|
|
23
|
+
this.child = null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = Contextualizer
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Manually bind the async context to every
|
|
30
|
+
* child within the promise chain
|
|
31
|
+
*
|
|
32
|
+
* @param {object} ctxlzr current promise
|
|
33
|
+
* @param {object} next the next link in promise chain
|
|
34
|
+
* @returns {void}
|
|
35
|
+
*/
|
|
36
|
+
function bindChild(ctxlzr, next) {
|
|
37
|
+
// If prev has one child already, branch the context and update the child.
|
|
38
|
+
if (ctxlzr.child) {
|
|
39
|
+
// When the branch-point is the 2nd through nth link in the chain, it is
|
|
40
|
+
// necessary to track its segment separately so the branches can parent
|
|
41
|
+
// their segments on the branch-point.
|
|
42
|
+
if (ctxlzr.parentIdx !== -1) {
|
|
43
|
+
ctxlzr.idx = ctxlzr.context.branch()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// The first child needs to be updated to have its own branch as well. And
|
|
47
|
+
// each of that child's children must be updated with the new parent index.
|
|
48
|
+
// This is the only non-constant-time action for linking, but it only
|
|
49
|
+
// happens with branching promise chains specifically when the 2nd branch
|
|
50
|
+
// is added.
|
|
51
|
+
//
|
|
52
|
+
// Note: This does not account for branches of branches. That may result
|
|
53
|
+
// in improperly parented segments.
|
|
54
|
+
let parent = ctxlzr
|
|
55
|
+
let child = ctxlzr.child
|
|
56
|
+
const branchIdx = ctxlzr.context.branch()
|
|
57
|
+
do {
|
|
58
|
+
child.parentIdx = parent.idx
|
|
59
|
+
child.idx = branchIdx
|
|
60
|
+
parent = child
|
|
61
|
+
child = child.child
|
|
62
|
+
} while (child)
|
|
63
|
+
|
|
64
|
+
// We set the child to something falsey that isn't `null` so we can
|
|
65
|
+
// distinguish between having no child, having one child, and having
|
|
66
|
+
// multiple children.
|
|
67
|
+
ctxlzr.child = false
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// If this is a branching link then create a new branch for the next promise.
|
|
71
|
+
// Otherwise, we can just piggy-back on the previous link's spot.
|
|
72
|
+
const idx = ctxlzr.child === false ? ctxlzr.context.branch() : ctxlzr.idx
|
|
73
|
+
|
|
74
|
+
// Create a new context for this next promise.
|
|
75
|
+
next[symbols.context] = new Contextualizer(idx, ctxlzr.context)
|
|
76
|
+
next[symbols.context].parentIdx = ctxlzr.idx
|
|
77
|
+
|
|
78
|
+
// If this was our first child, remember it in case we have a 2nd.
|
|
79
|
+
if (ctxlzr.child === null) {
|
|
80
|
+
ctxlzr.child = next[symbols.context]
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Binds segment to the entire promise chain
|
|
86
|
+
*
|
|
87
|
+
* @param {Function} prev previous function in chain
|
|
88
|
+
* @param {Function} next next function in chain
|
|
89
|
+
* @param {object} segment proper segment to bind
|
|
90
|
+
* @returns {void}
|
|
91
|
+
*/
|
|
92
|
+
Contextualizer.link = function link(prev, next, segment) {
|
|
93
|
+
let ctxlzr = prev && prev[symbols.context]
|
|
94
|
+
if (ctxlzr && !ctxlzr.isActive()) {
|
|
95
|
+
ctxlzr = prev[symbols.context] = null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (ctxlzr) {
|
|
99
|
+
bindChild(ctxlzr, next)
|
|
100
|
+
} else if (segment) {
|
|
101
|
+
// This next promise is the root of a chain. Either there was no previous
|
|
102
|
+
// promise or the promise was created out of context.
|
|
103
|
+
next[symbols.context] = new Contextualizer(0, new Context(segment))
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
Contextualizer.prototype = Object.create(null)
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Checks if segment is currently active
|
|
111
|
+
*
|
|
112
|
+
* @returns {boolean} if segment is active
|
|
113
|
+
*/
|
|
114
|
+
Contextualizer.prototype.isActive = function isActive() {
|
|
115
|
+
const segments = this.context.segments
|
|
116
|
+
const segment = segments[this.idx] || segments[this.parentIdx] || segments[0]
|
|
117
|
+
return segment && segment.transaction.isActive()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Gets the segment at the appropriate index.
|
|
122
|
+
* If there is none it will get the segment at the parent index or the first one
|
|
123
|
+
* then assign to current index.
|
|
124
|
+
*
|
|
125
|
+
* @returns {object} segment by idx or parentIdx or the first one
|
|
126
|
+
*/
|
|
127
|
+
Contextualizer.prototype.getSegment = function getSegment() {
|
|
128
|
+
const segments = this.context.segments
|
|
129
|
+
let segment = segments[this.idx]
|
|
130
|
+
if (segment == null) {
|
|
131
|
+
segment = segments[this.idx] = segments[this.parentIdx] || segments[0]
|
|
132
|
+
}
|
|
133
|
+
return segment
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Sets the set to the appropriate index
|
|
138
|
+
*
|
|
139
|
+
* @param {object} segment segment to assign to index
|
|
140
|
+
* @returns {object} same segment passed in
|
|
141
|
+
*/
|
|
142
|
+
Contextualizer.prototype.setSegment = function setSegment(segment) {
|
|
143
|
+
return (this.context.segments[this.idx] = segment)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Propagates the segment to the finally function
|
|
148
|
+
*
|
|
149
|
+
* @param {Function} prom current promise
|
|
150
|
+
* @returns {Function} current promise or wrapped finally that will propagate the segment
|
|
151
|
+
*/
|
|
152
|
+
Contextualizer.prototype.continue = function continueContext(prom) {
|
|
153
|
+
const self = this
|
|
154
|
+
const nextContext = prom[symbols.context]
|
|
155
|
+
if (!nextContext) {
|
|
156
|
+
return prom
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// If we have `finally`, use that to sneak our context update.
|
|
160
|
+
if (typeof prom.finally === 'function') {
|
|
161
|
+
return prom.finally(__NR_continueContext)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// eslint-disable-next-line camelcase
|
|
165
|
+
function __NR_continueContext() {
|
|
166
|
+
self.setSegment(nextContext.getSegment())
|
|
167
|
+
}
|
|
168
|
+
}
|