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.
Files changed (37) hide show
  1. package/NEWS.md +42 -18
  2. package/THIRD_PARTY_NOTICES.md +203 -2
  3. package/api.js +155 -103
  4. package/lib/agent.js +29 -82
  5. package/lib/collector/api.js +234 -201
  6. package/lib/config/attribute-filter.js +35 -25
  7. package/lib/config/default.js +45 -19
  8. package/lib/config/index.js +260 -199
  9. package/lib/environment.js +16 -12
  10. package/lib/errors/error-collector.js +124 -55
  11. package/lib/errors/index.js +59 -49
  12. package/lib/instrumentation/@hapi/hapi.js +56 -50
  13. package/lib/instrumentation/@node-redis/client.js +50 -41
  14. package/lib/instrumentation/amqplib.js +116 -151
  15. package/lib/instrumentation/core/{async_hooks.js → async-hooks.js} +26 -11
  16. package/lib/instrumentation/core/globals.js +1 -1
  17. package/lib/instrumentation/core/http-outbound.js +193 -78
  18. package/lib/instrumentation/core/timers.js +106 -59
  19. package/lib/instrumentation/grpc-js/grpc.js +20 -23
  20. package/lib/instrumentation/mongodb/common.js +87 -85
  21. package/lib/instrumentation/redis.js +112 -90
  22. package/lib/instrumentation/undici.js +204 -192
  23. package/lib/instrumentation/{when.js → when/constants.js} +13 -10
  24. package/lib/instrumentation/when/contextualizer.js +168 -0
  25. package/lib/instrumentation/when/index.js +354 -0
  26. package/lib/instrumentation/when/nr-hooks.js +15 -0
  27. package/lib/instrumentations.js +1 -1
  28. package/lib/shim/shim.js +2 -0
  29. package/lib/shim/webframework-shim.js +19 -0
  30. package/lib/symbols.js +1 -0
  31. package/lib/system-info.js +240 -163
  32. package/lib/util/async-each-limit.js +30 -0
  33. package/lib/util/attributes.js +159 -0
  34. package/lib/util/code-level-metrics.js +58 -0
  35. package/lib/util/deep-equal.js +11 -144
  36. package/package.json +5 -4
  37. 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, undici, modName, shim) {
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
- * Retrieves the current segment in transaction(parent in our context) from executionAsyncResource
36
- * or from `shim.getSegment()` then adds to the executionAsyncResource for future
37
- * undici requests within same async context.
38
- *
39
- * It was found that when running concurrent undici requests
40
- * within a transaction that the parent segment would get out of sync
41
- * depending on the async context of the transaction. By using
42
- * `async_hooks.executionResource` it is more reliable.
43
- *
44
- * Note: However, if you have concurrent undici requests in a transaction
45
- * and the request to the transaction is using a keep alive there is a chance the
46
- * executionAsyncResource may be incorrect because of shared connections. To revert to a more
47
- * naive tracking of parent set `config.feature_flag.undici_async_tracking: false` and
48
- * it will just call `shim.getSegment()`
49
- */
50
- function getParentSegment() {
51
- if (agent.config.feature_flag.undici_async_tracking) {
52
- const resource = executionAsyncResource()
53
-
54
- if (!resource[symbols.parentSegment]) {
55
- const parent = shim.getSegment()
56
- resource[symbols.parentSegment] = parent
57
- }
58
- return resource[symbols.parentSegment]
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 shim.getSegment()
87
+ return resource[symbols.parentSegment]
61
88
  }
89
+ return shim.getSegment()
90
+ }
62
91
 
63
- /**
64
- * This event occurs after the Undici Request is created
65
- * We will check current segment for opaque and also attach
66
- * relevant headers to outgoing http request
67
- *
68
- * @param {object} params
69
- * @param {object} params.request undici request object
70
- */
71
- diagnosticsChannel.channel('undici:request:create').subscribe(({ request }) => {
72
- const parent = getParentSegment()
73
- request[symbols.parentSegment] = parent
74
- if (!parent || (parent && parent.opaque)) {
75
- logger.trace(
76
- 'Not capturing data for outbound request (%s) because parent segment opaque (%s)',
77
- request.path,
78
- parent && parent.name
79
- )
80
-
81
- return
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
- const transaction = parent.transaction
85
- const outboundHeaders = Object.create(null)
86
- if (agent.config.encoding_key && transaction.syntheticsHeader) {
87
- outboundHeaders[NEWRELIC_SYNTHETICS_HEADER] = transaction.syntheticsHeader
88
- }
112
+ return
113
+ }
89
114
 
90
- if (agent.config.distributed_tracing.enabled) {
91
- transaction.insertDistributedTraceHeaders(outboundHeaders)
92
- } else if (agent.config.cross_application_tracer.enabled) {
93
- cat.addCatHeaders(agent.config, transaction, outboundHeaders)
94
- } else {
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
- // eslint-disable-next-line guard-for-in
99
- for (const key in outboundHeaders) {
100
- request.addHeader(key, outboundHeaders[key])
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
- const port = socket.remotePort
122
- const isHttps = socket.servername
123
- let urlString
124
- if (isHttps) {
125
- urlString = `https://${socket.servername}`
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
- const url = new URL(urlString)
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
- const name = NAMES.EXTERNAL.PREFIX + url.host + url.pathname
135
- const segment = shim.createSegment(name, recordExternal(url.host, 'undici'), parentSegment)
136
- if (segment) {
137
- segment.start()
138
- shim.setActiveSegment(segment)
139
- segment.addAttribute('url', `${url.protocol}//${url.host}${url.pathname}`)
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
- url.searchParams.forEach((value, key) => {
142
- segment.addSpanAttribute(`request.parameters.${key}`, value)
143
- })
144
- segment.addAttribute('procedure', request.method || 'GET')
145
- request[symbols.segment] = segment
146
- }
147
- })
148
-
149
- /**
150
- * This event occurs after the response headers have been received.
151
- * We will add the relevant http response attributes to active segment.
152
- * Also add CAT specific keys to active segment.
153
- *
154
- * @param {object} params
155
- * @param {object} params.request undici request object
156
- * @param {object} params.response { statusCode, headers, statusText }
157
- */
158
- diagnosticsChannel.channel('undici:request:headers').subscribe(({ request, response }) => {
159
- const activeSegment = request[symbols.segment]
160
- if (!activeSegment) {
161
- return
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
- activeSegment.addSpanAttribute('http.statusCode', response.statusCode)
165
- activeSegment.addSpanAttribute('http.statusText', response.statusText)
166
-
167
- if (
168
- agent.config.cross_application_tracer.enabled &&
169
- !agent.config.distributed_tracing.enabled
170
- ) {
171
- try {
172
- const { appData } = cat.extractCatHeaders(response.headers)
173
- const decodedAppData = cat.parseAppData(agent.config, appData)
174
- const attrs = activeSegment.getAttributes()
175
- const url = new URL(attrs.url)
176
- cat.assignCatToSegment(decodedAppData, activeSegment, url.host)
177
- } catch (err) {
178
- logger.warn(err, 'Cannot add CAT data to segment')
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
- * Adds the error to the active transaction
230
- *
231
- * @param {TraceSegment} activeSegment
232
- * @param {Error} error
233
- */
234
- function handleError(activeSegment, error) {
235
- logger.trace(error, 'Captured outbound error on behalf of the user.')
236
- const tx = activeSegment.transaction
237
- shim.agent.errors.add(tx, error)
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 2020 New Relic Corporation. All rights reserved.
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 = function initialize(agent, library) {
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
+ }