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", 18] -- TODO: https://issues.newrelic.com/browse/NEWRELIC-5252 */
9
-
10
8
  const { CURSOR_OPS, COLLECTION_OPS, DB_OPS } = require('./constants')
11
9
  const common = module.exports
12
10
  common.NR_ATTRS = Symbol('NR_ATTRS')
@@ -15,8 +13,8 @@ common.NR_ATTRS = Symbol('NR_ATTRS')
15
13
  * Instruments all methods from constants.CURSOR_OPS on a given
16
14
  * cursor class
17
15
  *
18
- * @param {Shim} shim
19
- * @param {Cursor} Cursor
16
+ * @param {Shim} shim instance of shim
17
+ * @param {Cursor} Cursor mongodb Cursor prototype
20
18
  */
21
19
  common.instrumentCursor = function instrumentCursor(shim, Cursor) {
22
20
  if (Cursor && Cursor.prototype) {
@@ -34,8 +32,8 @@ common.instrumentCursor = function instrumentCursor(shim, Cursor) {
34
32
  * Instruments all methods from constants.COLLECTION_OPS on
35
33
  * the Collection class
36
34
  *
37
- * @param {Shim} shim
38
- * @param {Collection} Collection
35
+ * @param {Shim} shim instance of shim
36
+ * @param {Collection} Collection mongodb Collection prototype
39
37
  */
40
38
  common.instrumentCollection = function instrumentCollection(shim, Collection) {
41
39
  if (Collection && Collection.prototype) {
@@ -50,15 +48,12 @@ common.instrumentCollection = function instrumentCollection(shim, Collection) {
50
48
  * Instruments the execute method on
51
49
  * the BulkOperationBase class
52
50
  *
53
- * @param {Shim} shim
54
- * @param {BulkOperationModule} bulk operation module, typically from mongodb/lib/bulk/common
55
- * @param BulkOperationModule
51
+ * @param {Shim} shim instance of shim
52
+ * @param {BulkOperationModule} BulkOperationModule operation module, typically from mongodb/lib/bulk/common
56
53
  */
57
54
  common.instrumentBulkOperation = function instrumentBulkOperation(shim, BulkOperationModule) {
58
- const BulkOperationBase = BulkOperationModule && BulkOperationModule.BulkOperationBase
59
-
60
- if (BulkOperationBase && BulkOperationBase.prototype) {
61
- const proto = BulkOperationBase.prototype
55
+ if (BulkOperationModule?.BulkOperationBase?.prototype) {
56
+ const proto = BulkOperationModule.BulkOperationBase.prototype
62
57
  shim.recordBatchQuery(proto, 'execute', common.makeBulkDescFunc(shim, 'execute'))
63
58
  }
64
59
  }
@@ -67,11 +62,8 @@ common.instrumentBulkOperation = function instrumentBulkOperation(shim, BulkOper
67
62
  * Instruments all methods from constants.DB_OPS on
68
63
  * the Db class.
69
64
  *
70
- * @param {object} params
71
- * @param {Shim} params.shim
72
- * @param {Db} params.Db
73
- * @param shim
74
- * @param Db
65
+ * @param {Shim} shim instance of shim
66
+ * @param {Db} Db mongodb Db prototype
75
67
  */
76
68
  common.instrumentDb = function instrumentDb(shim, Db) {
77
69
  if (Db && Db.prototype) {
@@ -85,8 +77,9 @@ common.instrumentDb = function instrumentDb(shim, Db) {
85
77
  /**
86
78
  * Sets up the desc for all instrumented query methods
87
79
  *
88
- * @param {Shim} shim
89
- * @param {string} methodName
80
+ * @param {Shim} shim instance of shim
81
+ * @param {string} methodName name of method getting executed
82
+ * @returns {object} query spec
90
83
  */
91
84
  common.makeQueryDescFunc = function makeQueryDescFunc(shim, methodName) {
92
85
  if (methodName === 'each') {
@@ -107,8 +100,8 @@ common.makeQueryDescFunc = function makeQueryDescFunc(shim, methodName) {
107
100
  /**
108
101
  * Sets up the desc for all instrumented bulk operations
109
102
  *
110
- * @param {Shim} shim
111
- * @param {string} methodName
103
+ * @param {Shim} shim instance of shim
104
+ * @returns {object} query spec
112
105
  */
113
106
  common.makeBulkDescFunc = function makeBulkDescFunc(shim) {
114
107
  return function bulkDescFunc() {
@@ -128,8 +121,8 @@ common.makeBulkDescFunc = function makeBulkDescFunc(shim) {
128
121
  * mongo <4. The listener adds the following attributes to the active segment:
129
122
  * host, port_path_or_id, and database_name
130
123
  *
131
- * @param {Shim} shim
132
- * @param {Instrumentation} instrumenter instance of mongo APM class
124
+ * @param {Shim} shim instance of shim
125
+ * @param {object} instrumenter instance of mongo APM class
133
126
  * @param {object} [options={}] provide command names to skip updating host/port as they are unrelated to the active query. This is only in v3 because after every command is runs `endSessions` which runs on the admin database
134
127
  */
135
128
  common.captureAttributesOnStarted = function captureAttributesOnStarted(
@@ -171,8 +164,8 @@ common.captureAttributesOnStarted = function captureAttributesOnStarted(
171
164
  * want it in the "port", so if we have a domain socket we need to change
172
165
  * the order of our parameters.
173
166
  *
174
- * @param {Shim} shim
175
- * @param {string} connStr
167
+ * @param {Shim} shim instance of shim
168
+ * @param {string} connStr mongo connection string
176
169
  * @param {string} db database name
177
170
  * @param {object} client mongo client instance
178
171
  */
@@ -205,75 +198,84 @@ function setHostPort(shim, connStr, db, client) {
205
198
  * You can now get the details via the client obj that's deeply nested
206
199
  * See: https://github.com/mongodb/node-mongodb-native/pull/2594/files#diff-1d214e57ddda9095d296e5700ebce701333bfefcf417e234c584d14091b2f50dR168
207
200
  *
208
- * @param shim
209
- * @param obj
201
+ * @param {Shim} shim instance of shim
202
+ * @param {object} mongo instance of mongo
203
+ * @returns {object} db params
210
204
  */
211
- function getInstanceAttributeParameters(shim, obj) {
212
- if (obj.s && obj.s.topology) {
213
- shim.logger.trace('Adding datastore instance attributes from obj.s.db + obj.s.topology')
214
- const databaseName =
215
- (obj.s.db && obj.s.db.databaseName) || (obj.s.namespace && obj.s.namespace.db) || null
216
- const topology = obj.s.topology
217
- if (topology) {
218
- return doCapture(topology, databaseName)
219
- }
220
- } else if (
221
- obj.s &&
222
- obj.s.db &&
223
- obj.s.db.s &&
224
- obj.s.db.s.client &&
225
- obj.s.db.s.client.s &&
226
- obj.s.db.s.client.s.options &&
227
- obj.s.db.s.client.s.options.hosts &&
228
- obj.s.db.s.client.s.options.hosts.length
229
- ) {
230
- // hosts is an array but we will always pull the first for consistency
231
- const hosts = obj.s.db.s.client.s.options.hosts
232
- let host = hosts[0].host
233
- let port = hosts[0].port
234
- const socketPath = hosts[0].socketPath
205
+ function getInstanceAttributeParameters(shim, mongo) {
206
+ let params = {
207
+ host: null,
208
+ port_path_or_id: null,
209
+ database_name: null
210
+ }
235
211
 
236
- if (socketPath) {
237
- port = socketPath
238
- host = 'localhost'
239
- }
240
- return {
241
- host,
242
- port_path_or_id: port,
243
- database_name: obj.s.db.databaseName
244
- }
212
+ if (mongo?.s?.topology) {
213
+ shim.logger.trace('Adding datastore instance attributes from mongo.s.db + mongo.s.topology')
214
+ const databaseName = mongo?.s?.db?.databaseName || mongo?.s?.namespace?.db || null
215
+ const topology = mongo.s.topology
216
+ params = getParametersFromTopology(topology, databaseName)
217
+ } else if (mongo?.s?.db?.s?.client?.s?.options?.hosts?.length) {
218
+ const databaseName = mongo?.s?.db?.databaseName || null
219
+ const hosts = mongo.s.db.s.client.s.options.hosts
220
+ params = getParametersFromHosts(hosts, databaseName)
221
+ } else {
222
+ shim.logger.trace('Could not find datastore instance attributes.')
245
223
  }
246
224
 
247
- shim.logger.trace('Could not find datastore instance attributes.')
225
+ return params
226
+ }
227
+
228
+ /**
229
+ * Extracts the database parameters from the first host.
230
+ *
231
+ * @param {Array} hosts mongodb connected hosts
232
+ * @param {string} database name of database
233
+ * @returns {object} db params
234
+ */
235
+ function getParametersFromHosts(hosts, database) {
236
+ let [{ host, port }] = hosts
237
+ const [{ socketPath }] = hosts
238
+
239
+ if (socketPath) {
240
+ port = socketPath
241
+ host = 'localhost'
242
+ }
248
243
  return {
249
- host: null,
250
- port_path_or_id: null,
251
- database_name: null
244
+ host,
245
+ port_path_or_id: port,
246
+ database_name: database
252
247
  }
248
+ }
253
249
 
254
- function doCapture(conf, database) {
255
- // in older versions of 3.x the host/port
256
- // lived directly on the topology
257
- let host = conf.host
258
- let port = conf.port
250
+ /**
251
+ * Extracts the database parameters from the relevant
252
+ * topology configuration
253
+ *
254
+ * @param {object} conf topology configuration
255
+ * @param {string} database name of database
256
+ * @returns {object} db params
257
+ */
258
+ function getParametersFromTopology(conf, database) {
259
+ // in older versions of 3.x the host/port
260
+ // lived directly on the topology
261
+ let { host, port } = conf
259
262
 
260
- // servers is an array but we will always pull the first for consistency
261
- if (conf.s && conf.s.options && conf.s.options.servers && conf.s.options.servers.length) {
262
- ;[{ host, port }] = conf.s.options.servers
263
- }
263
+ // servers is an array but we will always pull the first for consistency
264
+ if (conf?.s?.options?.servers?.length) {
265
+ ;[{ host, port }] = conf.s.options.servers
266
+ }
264
267
 
265
- // host is a domain socket. set host as localhost and use the domain
266
- // socket host as the port
267
- if (host && host.endsWith('.sock')) {
268
- port = host
269
- host = 'localhost'
270
- }
268
+ // host is a domain socket. set host as localhost and use the domain
269
+ // socket host as the port
270
+ if (host && host.endsWith('.sock')) {
271
+ port = host
272
+ host = 'localhost'
273
+ }
271
274
 
272
- return {
273
- host: host,
274
- port_path_or_id: port,
275
- database_name: database
276
- }
275
+ return {
276
+ host: host,
277
+ port_path_or_id: port,
278
+ database_name: database
277
279
  }
278
280
  }
279
281
 
@@ -5,118 +5,140 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- /* eslint sonarjs/cognitive-complexity: ["error", 52] -- TODO: https://issues.newrelic.com/browse/NEWRELIC-5252 */
9
-
10
8
  const hasOwnProperty = require('../util/properties').hasOwn
11
9
  const stringify = require('json-stringify-safe')
12
10
 
13
- module.exports = function initialize(agent, redis, moduleName, shim) {
14
- const proto = redis && redis.RedisClient && redis.RedisClient.prototype
11
+ module.exports = function initialize(_agent, redis, _moduleName, shim) {
12
+ const proto = redis?.RedisClient?.prototype
15
13
  if (!proto) {
16
14
  return false
17
15
  }
18
16
 
19
17
  shim.setDatastore(shim.REDIS)
20
- if (proto.internal_send_command) {
21
- shim.recordOperation(
22
- proto,
23
- 'internal_send_command',
24
- function wrapInternalSendCommand(shim, _, __, args) {
25
- const commandObject = args[0]
26
- const keys = commandObject.args
27
- const parameters = getInstanceParameters(this)
28
-
29
- if (keys && keys.length > 0) {
30
- try {
31
- parameters.key = stringify(keys[0])
32
- } catch (err) {
33
- shim.logger.debug(err, 'Failed to stringify internal send command redis key')
34
- parameters.key = '<unknown>'
35
- }
36
- }
37
18
 
38
- return {
39
- name: commandObject.command || 'other',
40
- parameters: parameters,
41
- callback: function bindCallback(shim, _f, _n, segment) {
42
- if (shim.isFunction(commandObject.callback)) {
43
- shim.bindCallbackSegment(commandObject, 'callback', segment)
44
- } else {
45
- const self = this
46
- commandObject.callback = shim.bindSegment(
47
- function NRCallback(err) {
48
- if (err && self.emit instanceof Function) {
49
- self.emit('error', err)
50
- }
51
- },
52
- segment,
53
- true
54
- )
55
- }
56
- }
57
- }
58
- }
59
- )
19
+ if (proto.internal_send_command) {
20
+ registerInternalSendCommand(shim, proto)
60
21
  } else {
61
- // For redis versions <2.6.1
62
- shim.recordOperation(proto, 'send_command', function wrapSendCommand(shim, _, __, args) {
63
- const keys = args[1]
64
- const parameters = getInstanceParameters(this)
22
+ registerSendCommand(shim, proto)
23
+ }
24
+ }
65
25
 
66
- if (keys && !shim.isFunction(keys)) {
67
- try {
68
- parameters.key = stringify(keys[0])
69
- } catch (err) {
70
- shim.logger.debug(err, 'Failed to stringify redis key for send command')
71
- parameters.key = '<unknown>'
72
- }
73
- }
26
+ /**
27
+ * Instrumentation used in versions of redis > 2.6.1 < 4 to record all redis commands
28
+ *
29
+ * @param {Shim} shim instance of shim
30
+ * @param {object} proto RedisClient prototype
31
+ */
32
+ function registerInternalSendCommand(shim, proto) {
33
+ shim.recordOperation(
34
+ proto,
35
+ 'internal_send_command',
36
+ function wrapInternalSendCommand(shim, _, __, args) {
37
+ const commandObject = args[0]
38
+ const keys = commandObject.args
39
+ const parameters = getInstanceParameters(shim, this)
40
+
41
+ parameters.key = stringifyKeys(shim, keys)
74
42
 
75
43
  return {
76
- name: args[0] || 'other',
77
- parameters: parameters,
44
+ name: commandObject.command || 'other',
45
+ parameters,
78
46
  callback: function bindCallback(shim, _f, _n, segment) {
79
- const last = args[args.length - 1]
80
- if (shim.isFunction(last)) {
81
- shim.bindCallbackSegment(args, shim.LAST, segment)
82
- } else if (shim.isArray(last) && shim.isFunction(last[last.length - 1])) {
83
- shim.bindCallbackSegment(last, shim.LAST, segment)
47
+ if (shim.isFunction(commandObject.callback)) {
48
+ shim.bindCallbackSegment(commandObject, 'callback', segment)
49
+ } else {
50
+ const self = this
51
+ commandObject.callback = shim.bindSegment(
52
+ function NRCallback(err) {
53
+ if (err && self.emit instanceof Function) {
54
+ self.emit('error', err)
55
+ }
56
+ },
57
+ segment,
58
+ true
59
+ )
84
60
  }
85
61
  }
86
62
  }
87
- })
88
- }
89
-
90
- function getInstanceParameters(client) {
91
- if (hasOwnProperty(client, 'port') && hasOwnProperty(client, 'host')) {
92
- // for redis <=0.11
93
- return doCapture(client)
94
- } else if (hasOwnProperty(client, 'connection_options')) {
95
- // for redis 2.4.0 - 2.6.2
96
- return doCapture(client.connection_options)
97
- } else if (hasOwnProperty(client, 'connectionOption')) {
98
- // for redis 0.12 - 2.2.5
99
- return doCapture(client.connectionOption)
100
- } else if (hasOwnProperty(client, 'options')) {
101
- // for redis 2.3.0 - 2.3.1
102
- return doCapture(client.options)
103
63
  }
64
+ )
65
+ }
104
66
 
105
- shim.logger.debug('Could not access instance attributes on connection.')
106
- return {
107
- host: null,
108
- port_path_or_id: null,
109
- database_name: null
110
- }
67
+ /**
68
+ * Instrumentation used in versions of redis < 2.6.1 to record all redis commands
69
+ *
70
+ * @param {Shim} shim instance of shim
71
+ * @param {object} proto RedisClient prototype
72
+ */
73
+ function registerSendCommand(shim, proto) {
74
+ shim.recordOperation(proto, 'send_command', function wrapSendCommand(shim, _, __, args) {
75
+ const [command, keys] = args
76
+ const parameters = getInstanceParameters(shim, this)
111
77
 
112
- function doCapture(opts) {
113
- const db = (hasOwnProperty(client, 'selected_db') ? client.selected_db : opts.db) || 0
78
+ parameters.key = stringifyKeys(shim, keys)
114
79
 
115
- return {
116
- host: opts.host || 'localhost',
117
- port_path_or_id: opts.path || opts.port || '6379',
118
- database_name: db
80
+ return {
81
+ name: command || 'other',
82
+ parameters,
83
+ callback: function bindCallback(shim, _f, _n, segment) {
84
+ const last = args[args.length - 1]
85
+ if (shim.isFunction(last)) {
86
+ shim.bindCallbackSegment(args, shim.LAST, segment)
87
+ } else if (shim.isArray(last) && shim.isFunction(last[last.length - 1])) {
88
+ shim.bindCallbackSegment(last, shim.LAST, segment)
89
+ }
119
90
  }
120
91
  }
92
+ })
93
+ }
94
+
95
+ function stringifyKeys(shim, keys) {
96
+ let key = null
97
+ if (keys && keys.length && !shim.isFunction(keys)) {
98
+ try {
99
+ key = stringify(keys[0])
100
+ } catch (err) {
101
+ shim.logger.debug(err, 'Failed to stringify redis key for send command')
102
+ key = '<unknown>'
103
+ }
104
+ }
105
+
106
+ return key
107
+ }
108
+
109
+ /**
110
+ * Captures the necessary datastore parameters based on the specific version of redis
111
+ *
112
+ * @param {Shim} shim instance of shim
113
+ * @param {object} client instance of redis client
114
+ * @returns {object} datastore parameters
115
+ */
116
+ function getInstanceParameters(shim, client) {
117
+ if (hasOwnProperty(client, 'connection_options')) {
118
+ // for redis 2.4.0 - 2.6.2
119
+ return doCapture(client, client.connection_options)
120
+ } else if (hasOwnProperty(client, 'connectionOption')) {
121
+ // for redis 0.12 - 2.2.5
122
+ return doCapture(client, client.connectionOption)
123
+ } else if (hasOwnProperty(client, 'options')) {
124
+ // for redis 2.3.0 - 2.3.1
125
+ return doCapture(client, client.options)
126
+ }
127
+ shim.logger.debug('Could not access instance attributes on connection.')
128
+ return doCapture()
129
+ }
130
+
131
+ /**
132
+ * Extracts the relevant datastore parameters
133
+ *
134
+ * @param {object} client instance of redis client
135
+ * @param {object} opts options for the client instance
136
+ * @returns {object} datastore parameters
137
+ */
138
+ function doCapture(client = {}, opts = {}) {
139
+ return {
140
+ host: opts.host || 'localhost',
141
+ port_path_or_id: opts.path || opts.port || '6379',
142
+ database_name: client.selected_db || opts.db || 0
121
143
  }
122
144
  }