newrelic 11.0.0 → 11.1.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/NEWS.md CHANGED
@@ -1,3 +1,21 @@
1
+ ### v11.1.0 (2023-09-07)
2
+
3
+ #### Features
4
+
5
+ * Added instrumentation for fetch. ([#1776](https://github.com/newrelic/node-newrelic/pull/1776)) ([980b4dc](https://github.com/newrelic/node-newrelic/commit/980b4dc31d085cafc45961a157f76cb73768741a))
6
+ * Removed undici feature flag. This will now instrument undici by default. ([#1772](https://github.com/newrelic/node-newrelic/pull/1772)) ([683b254](https://github.com/newrelic/node-newrelic/commit/683b254c1c4686527a37e628f80eed9fcc03cb37))
7
+ * Updated undici instrumentation to create segment on request create instead of after connection and opening socket to more accurately record time it takes to make an undici request. ([#1770](https://github.com/newrelic/node-newrelic/pull/1770)) ([3f494f8](https://github.com/newrelic/node-newrelic/commit/3f494f8ad8dd1350fbe034e9c4a582a40228bc45))
8
+
9
+ #### Performance Improvements
10
+
11
+ * **Trace:** optimise getTotalTimeDurationInMillis function to push to an array instead of concat arrays. ([#1774](https://github.com/newrelic/node-newrelic/pull/1774)) ([6ecde97](https://github.com/newrelic/node-newrelic/commit/6ecde97a5efeef4d816b2e82ba762f2130914658))
12
+ * Thanks @infinity-naveen for your contribution 🎉
13
+
14
+ #### Miscellaneous Chores
15
+
16
+ * Updated Next.js plugin versioned tests to use main now that require-in-the-middle fixes have been merged. ([#1768](https://github.com/newrelic/node-newrelic/pull/1768)) ([6a72d1f](https://github.com/newrelic/node-newrelic/commit/6a72d1fb452ea2655c35d7cd008fa78935821ab8))
17
+ * Upgraded `@newrelic/test-utilities` and removed unused shimmer.patchModule and shimmer.unpatchModule ([#1769](https://github.com/newrelic/node-newrelic/pull/1769)) ([96620ef](https://github.com/newrelic/node-newrelic/commit/96620efa725f0f548b683825cb3cefff270fb252))
18
+
1
19
  ### v11.0.0 (2023-08-28)
2
20
 
3
21
  #### ⚠ BREAKING CHANGES
@@ -1892,7 +1892,7 @@ This product includes source derived from [@newrelic/newrelic-oss-cli](https://g
1892
1892
 
1893
1893
  ### @newrelic/test-utilities
1894
1894
 
1895
- This product includes source derived from [@newrelic/test-utilities](https://github.com/newrelic/node-test-utilities) ([v8.0.0](https://github.com/newrelic/node-test-utilities/tree/v8.0.0)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-test-utilities/blob/v8.0.0/LICENSE):
1895
+ This product includes source derived from [@newrelic/test-utilities](https://github.com/newrelic/node-test-utilities) ([v8.1.0](https://github.com/newrelic/node-test-utilities/tree/v8.1.0)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-test-utilities/blob/v8.1.0/LICENSE):
1896
1896
 
1897
1897
  ```
1898
1898
  Apache License
@@ -2218,7 +2218,7 @@ THE SOFTWARE.
2218
2218
 
2219
2219
  ### c8
2220
2220
 
2221
- This product includes source derived from [c8](https://github.com/bcoe/c8) ([v7.14.0](https://github.com/bcoe/c8/tree/v7.14.0)), distributed under the [ISC License](https://github.com/bcoe/c8/blob/v7.14.0/LICENSE.txt):
2221
+ This product includes source derived from [c8](https://github.com/bcoe/c8) ([v8.0.1](https://github.com/bcoe/c8/tree/v8.0.1)), distributed under the [ISC License](https://github.com/bcoe/c8/blob/v8.0.1/LICENSE.txt):
2222
2222
 
2223
2223
  ```
2224
2224
  Copyright (c) 2017, Contributors
@@ -10,7 +10,6 @@ exports.prerelease = {
10
10
  express5: false,
11
11
  promise_segments: false,
12
12
  reverse_naming_rules: false,
13
- undici_instrumentation: false,
14
13
  undici_async_tracking: true,
15
14
  unresolved_promise_cleanup: true,
16
15
  legacy_context_manager: false
@@ -33,7 +32,8 @@ exports.released = [
33
32
  'fastify_instrumentation',
34
33
  'await_support',
35
34
  'certificate_bundle',
36
- 'async_local_context'
35
+ 'async_local_context',
36
+ 'undici_instrumentation'
37
37
  ]
38
38
 
39
39
  // flags that are no longer used for unreleased features
@@ -12,48 +12,38 @@ const NAMES = require('../metrics/names')
12
12
  const NEWRELIC_SYNTHETICS_HEADER = 'x-newrelic-synthetics'
13
13
  const symbols = require('../symbols')
14
14
  const { executionAsyncResource } = require('async_hooks')
15
- const tls = require('tls')
16
- const net = require('net')
17
-
18
- let diagnosticsChannel = null
19
- try {
20
- diagnosticsChannel = require('diagnostics_channel')
21
- } catch (e) {
22
- // quick check to see if module exists
23
- // module was not added until v15.x
24
- }
15
+ const diagnosticsChannel = require('diagnostics_channel')
25
16
 
26
- module.exports = function addUndiciChannels(agent, _undici, _modName, shim) {
27
- if (!diagnosticsChannel || !agent.config.feature_flag.undici_instrumentation) {
28
- logger.warn(
29
- 'diagnostics_channel or feature_flag.undici_instrumentation = false. Skipping undici instrumentation.'
30
- )
31
- return
32
- }
33
-
34
- registerHookPoints(shim)
35
- }
17
+ const channels = [
18
+ { channel: diagnosticsChannel.channel('undici:request:create'), hook: requestCreateHook },
19
+ { channel: diagnosticsChannel.channel('undici:request:headers'), hook: requestHeadersHook },
20
+ { channel: diagnosticsChannel.channel('undici:request:trailers'), hook: endAndRestoreSegment },
21
+ { channel: diagnosticsChannel.channel('undici:request:error'), hook: endAndRestoreSegment }
22
+ ]
36
23
 
37
24
  /**
38
25
  * Subscribes to all relevant undici hook points
39
26
  * See: https://github.com/nodejs/undici/blob/main/docs/api/DiagnosticsChannel.md
40
27
  *
41
- * @param {Shim} shim instance of shim
28
+ * @param agent
29
+ * @param _undici
30
+ * @param _modName
31
+ * @param shim
42
32
  */
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))
33
+ module.exports = function addUndiciChannels(agent, _undici, _modName, shim) {
34
+ channels.forEach(({ channel, hook }) => {
35
+ if (!channel.hasSubscribers) {
36
+ channel.subscribe(hook.bind(null, shim))
37
+ }
38
+ })
39
+ }
40
+
41
+ module.exports.unsubscribe = function unsubscribe() {
42
+ channels.forEach(({ channel, hook }) => {
43
+ if (channel.hasSubscribers) {
44
+ channel.unsubscribe(hook)
45
+ }
46
+ })
57
47
  }
58
48
 
59
49
  /**
@@ -90,29 +80,14 @@ function getParentSegment(shim) {
90
80
  }
91
81
 
92
82
  /**
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
83
+ * Injects relevant DT headers for the external request
96
84
  *
97
- * @param {Shim} shim instance of shim
98
- * @param {object} params object from undici hook
85
+ * @param {object} params object to fn
86
+ * @param {Shim} params.transaction current transaction
99
87
  * @param {object} params.request undici request object
88
+ * @param {object} params.config agent config
100
89
  */
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
- )
111
-
112
- return
113
- }
114
-
115
- const transaction = parent.transaction
90
+ function addDTHeaders({ transaction, config, request }) {
116
91
  const outboundHeaders = Object.create(null)
117
92
  if (config.encoding_key && transaction.syntheticsHeader) {
118
93
  outboundHeaders[NEWRELIC_SYNTHETICS_HEADER] = transaction.syntheticsHeader
@@ -133,37 +108,19 @@ function requestCreateHook(shim, { request }) {
133
108
  }
134
109
 
135
110
  /**
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
111
+ * Creates the external segment with url, procedure and request.parameters attributes
141
112
  *
142
- * @param {Shim} shim instance of shim
143
- * @param {object} params object from undici hook
113
+ * @param {object} params object to fn
114
+ * @param {Shim} params.shim instance of shim
144
115
  * @param {object} params.request undici request object
145
- * @param {tls.TLSSocket | net.Socket} params.socket active socket connection
116
+ * @param {object} params.parentSegment current active, about to be parent of external segment
146
117
  */
147
- function responseHeadersHook(shim, { request, socket }) {
148
- const parentSegment = request[symbols.parentSegment]
149
- if (!parentSegment || (parentSegment && parentSegment.opaque)) {
150
- return
151
- }
152
-
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
- }
163
-
164
- const url = new URL(urlString)
165
-
118
+ function createExternalSegment({ shim, request, parentSegment }) {
119
+ const url = new URL(request.origin + request.path)
166
120
  const name = NAMES.EXTERNAL.PREFIX + url.host + url.pathname
121
+ // Metrics for `External/<host>` will have a suffix of undici
122
+ // We will have to see if this matters for people only using fetch
123
+ // It's undici under the hood so ¯\_(ツ)_/¯
167
124
  const segment = shim.createSegment(name, recordExternal(url.host, 'undici'), parentSegment)
168
125
  if (segment) {
169
126
  segment.start()
@@ -178,6 +135,38 @@ function responseHeadersHook(shim, { request, socket }) {
178
135
  }
179
136
  }
180
137
 
138
+ /**
139
+ * This event occurs after the Undici Request is created.
140
+ * We will check current segment for opaque before creating the
141
+ * external segment with the standard url/procedure/request.parameters
142
+ * attributes. We will also attach relevant DT headers to outgoing http request.
143
+ *
144
+ * @param {Shim} shim instance of shim
145
+ * @param {object} params object from undici hook
146
+ * @param {object} params.request undici request object
147
+ */
148
+ function requestCreateHook(shim, { request }) {
149
+ const { config } = shim.agent
150
+ const parentSegment = getParentSegment(shim)
151
+ request[symbols.parentSegment] = parentSegment
152
+ if (!parentSegment || (parentSegment && parentSegment.opaque)) {
153
+ logger.trace(
154
+ 'Not capturing data for outbound request (%s) because parent segment opaque (%s)',
155
+ request.path,
156
+ parentSegment && parentSegment.name
157
+ )
158
+
159
+ return
160
+ }
161
+
162
+ try {
163
+ addDTHeaders({ transaction: parentSegment.transaction, config, request })
164
+ createExternalSegment({ shim, request, parentSegment })
165
+ } catch (err) {
166
+ logger.warn(err, 'Unable to create external segment')
167
+ }
168
+ }
169
+
181
170
  /**
182
171
  * This event occurs after the response headers have been received.
183
172
  * We will add the relevant http response attributes to active segment.
@@ -36,7 +36,6 @@ module.exports = function instrumentations() {
36
36
  '@redis/client': { type: MODULE_TYPE.DATASTORE },
37
37
  'restify': { type: MODULE_TYPE.WEB_FRAMEWORK },
38
38
  'superagent': { module: '@newrelic/superagent' },
39
- 'undici': { type: MODULE_TYPE.TRANSACTION },
40
39
  '@hapi/vision': { type: MODULE_TYPE.WEB_FRAMEWORK },
41
40
  'when': { module: './instrumentation/when' },
42
41
  'winston': { type: MODULE_TYPE.GENERIC },
package/lib/shimmer.js CHANGED
@@ -18,6 +18,7 @@ let pkgsToHook = []
18
18
 
19
19
  const NAMES = require('./metrics/names')
20
20
  const symbols = require('./symbols')
21
+ const { unsubscribe } = require('./instrumentation/undici')
21
22
 
22
23
  const MODULE_TYPE = shims.constants.MODULE_TYPE
23
24
 
@@ -328,6 +329,12 @@ const shimmer = (module.exports = {
328
329
  const globalsFilepath = path.join(__dirname, 'instrumentation', 'core', 'globals.js')
329
330
  _firstPartyInstrumentation(agent, globalsFilepath, globalShim, global, 'globals')
330
331
 
332
+ // Since this just registers subscriptions to diagnostics_channel events from undici
333
+ // We register this as core and it'll work for both fetch and undici
334
+ const undiciPath = path.join(__dirname, 'instrumentation', 'undici.js')
335
+ const undiciShim = shims.createShimFromType(MODULE_TYPE.TRANSACTION, agent, 'undici', 'undici')
336
+ _firstPartyInstrumentation(agent, undiciPath, undiciShim)
337
+
331
338
  // Instrument each of the core modules.
332
339
  for (const [mojule, core] of Object.entries(CORE_INSTRUMENTATION)) {
333
340
  const filePath = path.join(__dirname, 'instrumentation', 'core', core.file)
@@ -344,14 +351,6 @@ const shimmer = (module.exports = {
344
351
  _firstPartyInstrumentation(agent, filePath, shim, uninstrumented, mojule)
345
352
  }
346
353
  },
347
- // TODO: remove the following methods once test-utilities have been updated and released
348
- // with the new registerHooks and removeHooks methods
349
- patchModule(agent) {
350
- shimmer.registerHooks(agent)
351
- },
352
- unpatchModule() {
353
- shimmer.removeHooks()
354
- },
355
354
  registerHooks(agent) {
356
355
  this._ritm = new Hook(pkgsToHook, function onHook(exports, name, basedir) {
357
356
  return _postLoad(agent, exports, name, basedir)
@@ -372,6 +371,7 @@ const shimmer = (module.exports = {
372
371
  }
373
372
 
374
373
  pkgsToHook = []
374
+ unsubscribe()
375
375
  },
376
376
 
377
377
  bootstrapInstrumentation: function bootstrapInstrumentation(agent) {
package/lib/symbols.js CHANGED
@@ -18,6 +18,7 @@ module.exports = {
18
18
  offTheRecord: Symbol('offTheRecord'),
19
19
  original: Symbol('original'),
20
20
  wrapped: Symbol('shimWrapped'),
21
+ parentSegment: Symbol('parentSegment'),
21
22
  prismaConnection: Symbol('prismaConnection'),
22
23
  prismaModelCall: Symbol('modelCall'),
23
24
  segment: Symbol('segment'),
@@ -218,13 +218,13 @@ Trace.prototype.getTotalTimeDurationInMillis = function getTotalTimeDurationInMi
218
218
  if (this.root.children.length === 0) {
219
219
  return 0
220
220
  }
221
- let segments = this.root.getChildren()
221
+ const segments = this.root.getChildren()
222
222
  let totalTimeInMillis = 0
223
223
 
224
224
  while (segments.length !== 0) {
225
225
  const segment = segments.pop()
226
226
  totalTimeInMillis += segment.getExclusiveDurationInMillis()
227
- segments = segments.concat(segment.getChildren())
227
+ segment.getChildren().forEach((childSegment) => segments.push(childSegment))
228
228
  }
229
229
 
230
230
  if (!this.transaction.isActive()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "11.0.0",
3
+ "version": "11.1.0",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "license": "Apache-2.0",
6
6
  "contributors": [
@@ -202,12 +202,12 @@
202
202
  "devDependencies": {
203
203
  "@newrelic/eslint-config": "^0.3.0",
204
204
  "@newrelic/newrelic-oss-cli": "^0.1.2",
205
- "@newrelic/test-utilities": "^8.0.0",
205
+ "@newrelic/test-utilities": "^8.1.0",
206
206
  "@octokit/rest": "^18.0.15",
207
207
  "@slack/bolt": "^3.7.0",
208
208
  "ajv": "^6.12.6",
209
209
  "async": "^3.2.4",
210
- "c8": "^7.14.0",
210
+ "c8": "^8.0.1",
211
211
  "chai": "^4.1.2",
212
212
  "clean-jsdoc-theme": "^4.2.4",
213
213
  "commander": "^7.0.0",