newrelic 11.13.0 → 11.14.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,39 +1,60 @@
1
- ### v11.13.0 (2024-03-13)
2
-
3
- #### Features
4
-
5
- * Added AI Monitoring(AIM) for OpenAI, Amazon Bedrock, and Langchain.
6
- * To enable set `config.ai_monitoring.enabled` or `NEW_RELIC_AI_MONITORING_ENABLED` to `true`.
7
- * It is recommended for a full experience to set `config.span_events.max_samples_stored` to `10000`, `config.custom_insights_events.max_samples_stored` to `100000`, and `config.distributed_tracing.enabled` should not be set to false.
8
- * **Note**: Enabling [high-security mode](https://docs.newrelic.com/docs/apm/agents/manage-apm-agents/configuration/high-security-mode/) will disable the AIM instrumentation and events.
9
- * Added instrumentation for VectorStore.similaritySearch for langchain.js ([#2049](https://github.com/newrelic/node-newrelic/pull/2049)) ([3c50606](https://github.com/newrelic/node-newrelic/commit/3c50606a57a8dc18c91fe36e3b180bafd15a4cbc))
10
- * Added setLlmTokenCountCallback API endpoint to register a callback for calculating token count when none is provided ([#2065](https://github.com/newrelic/node-newrelic/pull/2065)) ([d2faf1a](https://github.com/newrelic/node-newrelic/commit/d2faf1a4942aacefd8b92e0bc2dc11bd8b687328))
11
- * Added token_count to LlmEmbedding and LlmChatCompletionMessage for openai ([#2061](https://github.com/newrelic/node-newrelic/pull/2061)) ([47a925e](https://github.com/newrelic/node-newrelic/commit/47a925eccce8e5b5d682b46277bbef5191f8b553))
12
- * Added tool_id to langchain error event ([#2064](https://github.com/newrelic/node-newrelic/pull/2064)) ([793abe8](https://github.com/newrelic/node-newrelic/commit/793abe8c9854b80fb1cf42f839a7db6a646cdc0b))
13
-
14
- #### Miscellaneous chores
15
-
16
- * Added flag for verbosity during docs publishing ([#2060](https://github.com/newrelic/node-newrelic/pull/2060)) ([fe0d41a](https://github.com/newrelic/node-newrelic/commit/fe0d41a86cba8da6173f5935e61c62b7dca75344))
17
- * Added some delays to docs publishing ([#2062](https://github.com/newrelic/node-newrelic/pull/2062)) ([a602fd4](https://github.com/newrelic/node-newrelic/commit/a602fd48e929437e4bc933c4ce276989013c53f1))
18
- * Changed casing of OpenAI in vendor property ([#2068](https://github.com/newrelic/node-newrelic/pull/2068)) ([64b4ca2](https://github.com/newrelic/node-newrelic/commit/64b4ca2750c9de8d9028fef515c15019e358a5d9))
19
- * Changed token_count to only use tokenCountCallback ([#2070](https://github.com/newrelic/node-newrelic/pull/2070)) ([66f94b0](https://github.com/newrelic/node-newrelic/commit/66f94b05aa7018af6fa1db1d2d87de9013f23a5b))
20
- * Removed langchain_instrumentation feature flag as the instrumentation is feature complete ([#2073](https://github.com/newrelic/node-newrelic/pull/2073)) ([925a463](https://github.com/newrelic/node-newrelic/commit/925a463c708b29c78567aecf78ac89ac226b7995))
21
- * updated @newrelic/aws-sdk and @newrelic/koa to latest ([#2074](https://github.com/newrelic/node-newrelic/pull/2074)) ([0747ca4](https://github.com/newrelic/node-newrelic/commit/0747ca4f0f6615e1fbfcc734a36059960facf23e))
22
-
23
- ### v11.12.0 (2024-03-04)
24
-
25
- #### Features
26
-
27
- * Added instrumentation for `chain.stream` for langchain js. ([#2052](https://github.com/newrelic/node-newrelic/pull/2052)) ([03abfce](https://github.com/newrelic/node-newrelic/commit/03abfce666d3c48abd0994c44817bb5cbe8655a9))
28
-
29
- #### Miscellaneous chores
30
-
31
- * Added @azure/openai and @langchain/community/llms/bedrock as tracking packages so we can measure usage with angler ([#2053](https://github.com/newrelic/node-newrelic/pull/2053)) ([4830ea3](https://github.com/newrelic/node-newrelic/commit/4830ea3285726f8cc8fb60820c4cf2d3790e5250))
32
-
33
- #### Tests
34
-
35
- * Updated aws-sdk branch to main as the necessary code was merged. ([#2051](https://github.com/newrelic/node-newrelic/pull/2051)) ([1e52a5c](https://github.com/newrelic/node-newrelic/commit/1e52a5cd7a8acc8970dbdc6b1bf774c06aa6f5fb))
36
-
1
+ ### v11.14.0 (2024-03-20)
2
+
3
+ #### Features
4
+
5
+ * Added `guid` attribute to `TransactionError` events ([#2079](https://github.com/newrelic/node-newrelic/pull/2079)) ([3468fb1](https://github.com/newrelic/node-newrelic/commit/3468fb14635b10c30a9a814dc0d084aeacfd9e10))
6
+ * Added instrumentation for `initializeUnorderedBulkOp`, and `initializeOrderedBulkOp` in mongodb 4+ ([#2086](https://github.com/newrelic/node-newrelic/pull/2086)) ([666ae6e](https://github.com/newrelic/node-newrelic/commit/666ae6ea4be8167748e31fe9b1d086aee5d0135d))
7
+ * Added support for Mongo v5+ ([#2085](https://github.com/newrelic/node-newrelic/pull/2085)) ([00f6feb](https://github.com/newrelic/node-newrelic/commit/00f6febb4893ed2abe902644514af3ab629cdbc1))
8
+
9
+ #### Security improvements
10
+
11
+ * **deps:** bump follow-redirects from 1.15.5 to 1.15.6 ([#2078](https://github.com/newrelic/node-newrelic/pull/2078)) ([aa86482](https://github.com/newrelic/node-newrelic/commit/aa86482738ba6b17bf6c2c7362931a3ad57736dd))
12
+
13
+ #### Documentation
14
+
15
+ * Changed bug bounty URL ([#2081](https://github.com/newrelic/node-newrelic/pull/2081)) ([f4586d7](https://github.com/newrelic/node-newrelic/commit/f4586d71cbe9c886181fbfe4b414cf0f9d630873))
16
+
17
+ #### Miscellaneous chores
18
+
19
+ * Added instrumentation tracking class ([#2084](https://github.com/newrelic/node-newrelic/pull/2084)) ([07d7e7d](https://github.com/newrelic/node-newrelic/commit/07d7e7d50edf9cd8a1cd1b465b24bc589409c5dc))
20
+ * **deps:** pin @newrelic/security-agent to 1.0.1 ([#2089](https://github.com/newrelic/node-newrelic/pull/2089)) ([e133c76](https://github.com/newrelic/node-newrelic/commit/e133c76a34f7d2af16610f00958651a6cff8824d))
21
+
22
+ ### v11.13.0 (2024-03-13)
23
+
24
+ #### Features
25
+
26
+ * Added AI Monitoring(AIM) for OpenAI, Amazon Bedrock, and Langchain.
27
+ * To enable set `config.ai_monitoring.enabled` or `NEW_RELIC_AI_MONITORING_ENABLED` to `true`.
28
+ * It is recommended for a full experience to set `config.span_events.max_samples_stored` to `10000`, `config.custom_insights_events.max_samples_stored` to `100000`, and `config.distributed_tracing.enabled` should not be set to false.
29
+ * **Note**: Enabling [high-security mode](https://docs.newrelic.com/docs/apm/agents/manage-apm-agents/configuration/high-security-mode/) will disable the AIM instrumentation and events.
30
+ * Added instrumentation for VectorStore.similaritySearch for langchain.js ([#2049](https://github.com/newrelic/node-newrelic/pull/2049)) ([3c50606](https://github.com/newrelic/node-newrelic/commit/3c50606a57a8dc18c91fe36e3b180bafd15a4cbc))
31
+ * Added setLlmTokenCountCallback API endpoint to register a callback for calculating token count when none is provided ([#2065](https://github.com/newrelic/node-newrelic/pull/2065)) ([d2faf1a](https://github.com/newrelic/node-newrelic/commit/d2faf1a4942aacefd8b92e0bc2dc11bd8b687328))
32
+ * Added token_count to LlmEmbedding and LlmChatCompletionMessage for openai ([#2061](https://github.com/newrelic/node-newrelic/pull/2061)) ([47a925e](https://github.com/newrelic/node-newrelic/commit/47a925eccce8e5b5d682b46277bbef5191f8b553))
33
+ * Added tool_id to langchain error event ([#2064](https://github.com/newrelic/node-newrelic/pull/2064)) ([793abe8](https://github.com/newrelic/node-newrelic/commit/793abe8c9854b80fb1cf42f839a7db6a646cdc0b))
34
+
35
+ #### Miscellaneous chores
36
+
37
+ * Added flag for verbosity during docs publishing ([#2060](https://github.com/newrelic/node-newrelic/pull/2060)) ([fe0d41a](https://github.com/newrelic/node-newrelic/commit/fe0d41a86cba8da6173f5935e61c62b7dca75344))
38
+ * Added some delays to docs publishing ([#2062](https://github.com/newrelic/node-newrelic/pull/2062)) ([a602fd4](https://github.com/newrelic/node-newrelic/commit/a602fd48e929437e4bc933c4ce276989013c53f1))
39
+ * Changed casing of OpenAI in vendor property ([#2068](https://github.com/newrelic/node-newrelic/pull/2068)) ([64b4ca2](https://github.com/newrelic/node-newrelic/commit/64b4ca2750c9de8d9028fef515c15019e358a5d9))
40
+ * Changed token_count to only use tokenCountCallback ([#2070](https://github.com/newrelic/node-newrelic/pull/2070)) ([66f94b0](https://github.com/newrelic/node-newrelic/commit/66f94b05aa7018af6fa1db1d2d87de9013f23a5b))
41
+ * Removed langchain_instrumentation feature flag as the instrumentation is feature complete ([#2073](https://github.com/newrelic/node-newrelic/pull/2073)) ([925a463](https://github.com/newrelic/node-newrelic/commit/925a463c708b29c78567aecf78ac89ac226b7995))
42
+ * updated @newrelic/aws-sdk and @newrelic/koa to latest ([#2074](https://github.com/newrelic/node-newrelic/pull/2074)) ([0747ca4](https://github.com/newrelic/node-newrelic/commit/0747ca4f0f6615e1fbfcc734a36059960facf23e))
43
+
44
+ ### v11.12.0 (2024-03-04)
45
+
46
+ #### Features
47
+
48
+ * Added instrumentation for `chain.stream` for langchain js. ([#2052](https://github.com/newrelic/node-newrelic/pull/2052)) ([03abfce](https://github.com/newrelic/node-newrelic/commit/03abfce666d3c48abd0994c44817bb5cbe8655a9))
49
+
50
+ #### Miscellaneous chores
51
+
52
+ * Added @azure/openai and @langchain/community/llms/bedrock as tracking packages so we can measure usage with angler ([#2053](https://github.com/newrelic/node-newrelic/pull/2053)) ([4830ea3](https://github.com/newrelic/node-newrelic/commit/4830ea3285726f8cc8fb60820c4cf2d3790e5250))
53
+
54
+ #### Tests
55
+
56
+ * Updated aws-sdk branch to main as the necessary code was merged. ([#2051](https://github.com/newrelic/node-newrelic/pull/2051)) ([1e52a5c](https://github.com/newrelic/node-newrelic/commit/1e52a5cd7a8acc8970dbdc6b1bf774c06aa6f5fb))
57
+
37
58
  ### v11.11.0 (2024-02-27)
38
59
 
39
60
  #### Features
package/README.md CHANGED
@@ -234,7 +234,7 @@ If you have any questions, or to execute our corporate CLA, required if your con
234
234
 
235
235
  As noted in our [security policy](../../security/policy), New Relic is committed to the privacy and security of our customers and their data. We believe that providing coordinated disclosure by security researchers and engaging with the security community are important means to achieve our security goals.
236
236
 
237
- If you believe you have found a security vulnerability in this project or any of New Relic's products or websites, we welcome and greatly appreciate you reporting it to New Relic through [HackerOne](https://hackerone.com/newrelic).
237
+ If you believe you have found a security vulnerability in this project or any of New Relic's products or websites, we welcome and greatly appreciate you reporting it to New Relic through [our bug bounty program](https://docs.newrelic.com/docs/security/security-privacy/information-security/report-security-vulnerabilities/).
238
238
 
239
239
  If you would like to contribute to this project, review [these guidelines](./CONTRIBUTING.md).
240
240
 
@@ -240,6 +240,7 @@ function _getErrorEventIntrinsicAttrs(transaction, errorClass, message, expected
240
240
  }
241
241
 
242
242
  attributes['nr.transactionGuid'] = transaction.id
243
+ attributes.guid = transaction.id
243
244
 
244
245
  if (transaction.port) {
245
246
  attributes.port = transaction.port
@@ -72,9 +72,13 @@ common.instrumentBulkOperation = function instrumentBulkOperation(shim, BulkOper
72
72
  common.instrumentDb = function instrumentDb(shim, Db) {
73
73
  if (Db && Db.prototype) {
74
74
  const proto = Db.prototype
75
- shim.recordOperation(proto, DB_OPS, new OperationSpec({ callback: shim.LAST, opaque: true }))
75
+ shim.recordOperation(
76
+ proto,
77
+ DB_OPS,
78
+ new OperationSpec({ callback: shim.LAST, opaque: true, promise: true })
79
+ )
76
80
  // link to client.connect(removed in v4.0)
77
- shim.recordOperation(Db, 'connect', new OperationSpec({ callback: shim.LAST }))
81
+ shim.recordOperation(Db, 'connect', new OperationSpec({ callback: shim.LAST, promise: true }))
78
82
  }
79
83
  }
80
84
 
@@ -133,7 +137,7 @@ common.makeBulkDescFunc = function makeBulkDescFunc(shim) {
133
137
  *
134
138
  * @param {Shim} shim instance of shim
135
139
  * @param {object} instrumenter instance of mongo APM class
136
- * @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
140
+ * @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
137
141
  */
138
142
  common.captureAttributesOnStarted = function captureAttributesOnStarted(
139
143
  shim,
@@ -214,7 +218,6 @@ function setHostPort(shim, connStr, db, client) {
214
218
  */
215
219
  function getInstanceAttributeParameters(shim, mongo) {
216
220
  let params
217
-
218
221
  if (mongo?.s?.topology) {
219
222
  shim.logger.trace('Adding datastore instance attributes from mongo.s.db + mongo.s.topology')
220
223
  const databaseName = mongo?.s?.db?.databaseName || mongo?.s?.namespace?.db || null
@@ -224,6 +227,10 @@ function getInstanceAttributeParameters(shim, mongo) {
224
227
  const databaseName = mongo?.s?.db?.databaseName || null
225
228
  const hosts = mongo.s.db.s.client.s.options.hosts
226
229
  params = getParametersFromHosts(hosts, databaseName)
230
+ } else if (mongo?.s?.db?.client?.topology) {
231
+ const databaseName = mongo?.s?.namespace?.db
232
+ const topology = mongo.s.db.client.topology
233
+ params = getParametersFromTopology(topology, databaseName)
227
234
  } else {
228
235
  shim.logger.trace('Could not find datastore instance attributes.')
229
236
  params = new DatastoreParameters()
@@ -272,6 +279,11 @@ function getParametersFromTopology(conf, database) {
272
279
  ;[{ host, port }] = conf.s.options.servers
273
280
  }
274
281
 
282
+ // hosts is an array but we will always pull the first for consistency
283
+ if (conf?.s?.options?.hosts?.length) {
284
+ ;[{ host, port }] = conf.s.options.hosts
285
+ }
286
+
275
287
  // host is a domain socket. set host as localhost and use the domain
276
288
  // socket host as the port
277
289
  if (host && host.endsWith('.sock')) {
@@ -280,7 +292,7 @@ function getParametersFromTopology(conf, database) {
280
292
  }
281
293
 
282
294
  return new DatastoreParameters({
283
- host: host,
295
+ host,
284
296
  port_path_or_id: port,
285
297
  database_name: database
286
298
  })
@@ -6,7 +6,13 @@
6
6
  'use strict'
7
7
 
8
8
  const { OperationSpec } = require('../../shim/specs')
9
- const { instrumentCollection, instrumentCursor, instrumentDb, parseAddress } = require('./common')
9
+ const {
10
+ instrumentBulkOperation,
11
+ instrumentCollection,
12
+ instrumentCursor,
13
+ instrumentDb,
14
+ parseAddress
15
+ } = require('./common')
10
16
 
11
17
  /**
12
18
  * parser used to grab the collection and operation
@@ -18,8 +24,11 @@ function queryParser(operation) {
18
24
  let collection = this.collectionName || 'unknown'
19
25
 
20
26
  // cursor methods have collection on namespace.collection
21
- if (this.namespace && this.namespace.collection) {
27
+ if (this?.namespace?.collection) {
22
28
  collection = this.namespace.collection
29
+ // (un)ordered bulk operations have collection on different key
30
+ } else if (this?.s?.collection?.collectionName) {
31
+ collection = this.s.collection.collectionName
23
32
  }
24
33
 
25
34
  return { operation, collection }
@@ -88,4 +97,5 @@ module.exports = function instrument(shim, mongodb) {
88
97
  instrumentCursor(shim, mongodb.AggregationCursor)
89
98
  instrumentCollection(shim, mongodb.Collection)
90
99
  instrumentDb(shim, mongodb.Db)
100
+ instrumentBulkOperation(shim, shim.require('./lib/bulk/common'))
91
101
  }
@@ -0,0 +1,186 @@
1
+ /*
2
+ * Copyright 2024 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const IdGen = require('./util/idgen')
9
+ const idGen = new IdGen()
10
+
11
+ /**
12
+ * @typedef {function} InstrumentationOnRequire
13
+ * @param {Shim} shim The shim instance to use for the instrumentation.
14
+ * @param {object} resolvedNodule The module being instrumented as returned by
15
+ * Node's `require` function.
16
+ * @param {string} moduleName The simple name of the module, i.e. the value
17
+ * passed to the `require` function.
18
+ * @throws {Error|object}
19
+ */
20
+
21
+ /**
22
+ * @typedef {function} InstrumentationOnError
23
+ * @param {Error|object} error The error thrown by `onRequire` when there was
24
+ * an issue registering the instrumentation.
25
+ */
26
+
27
+ /* eslint-disable jsdoc/require-property-description */
28
+ /**
29
+ * @typedef {object} InstrumentationDescriptorParams
30
+ * @property {string} absolutePath
31
+ * @property {string} module
32
+ * @property {string} moduleName
33
+ * @property {InstrumentationOnError} onError
34
+ * @property {InstrumentationOnRequire} onRequire
35
+ * @property {string} resolvedName
36
+ * @property {string} type
37
+ */
38
+
39
+ /**
40
+ * Describes the configuration for an instrumentation. An instrumentation
41
+ * is what `newrelic` uses to wrap Node.js modules. In particular, a description
42
+ * details the name of the module, the path on disk to the module, and the
43
+ * hooks (`onRequire` and `onError`) to apply to the module.
44
+ */
45
+ class InstrumentationDescriptor {
46
+ /**
47
+ * Utility/generic module.
48
+ * @type {string}
49
+ */
50
+ static TYPE_GENERIC = 'generic'
51
+
52
+ /**
53
+ * @private
54
+ * @type {string}
55
+ */
56
+ static TYPE_CONGLOMERATE = 'conglomerate'
57
+
58
+ /**
59
+ * Database module, such as the MongoDB or MySQL drivers.
60
+ * @type {string}
61
+ */
62
+ static TYPE_DATASTORE = 'datastore'
63
+
64
+ /**
65
+ * Messaging module, such as AMQP.
66
+ * @type {string}
67
+ */
68
+ static TYPE_MESSAGE = 'message'
69
+
70
+ /**
71
+ * Promise module, such as Bluebird.
72
+ * @type {string}
73
+ */
74
+ static TYPE_PROMISE = 'promise'
75
+
76
+ /**
77
+ * @private
78
+ * @type {string}
79
+ */
80
+ static TYPE_TRANSACTION = 'transaction'
81
+
82
+ /**
83
+ * Web server framework module, such as Express or Fastify.
84
+ * @type {string}
85
+ */
86
+ static TYPE_WEB_FRAMEWORK = 'web-framework'
87
+
88
+ /**
89
+ * Used to load supportability metrics on installed versions of packages
90
+ * that the Node.js agent does not instrument (e.g. OTEL instrumentation or
91
+ * top logging libraries).
92
+ * @type {string}
93
+ */
94
+ static TYPE_TRACKING = 'tracking'
95
+
96
+ /**
97
+ * The type of the module being instrumented. See the static `TYPE_` fields.
98
+ * @type {string|null}
99
+ */
100
+ type
101
+
102
+ /**
103
+ * The name of the module being instrumented, i.e. the string used to require
104
+ * the module. This must map to a directory in `lib/instrumentations` which
105
+ * contains an `nr-hooks.js` file.
106
+ *
107
+ * This takes precedence over `moduleName`.
108
+ * @type {string}
109
+ */
110
+ module
111
+
112
+ /**
113
+ * The name of the module being instrumented, i.e. the string used to require
114
+ * the module. This must map to a JavaScript file of the same name in the
115
+ * `lib/instrumentations` directory.
116
+ * @type {string}
117
+ */
118
+ moduleName
119
+
120
+ /**
121
+ * The absolute path to the module to instrument. This should only be set
122
+ * when the module being instrumented does not reside in a `node_modules`
123
+ * directory; for example, when someone is instrumenting a module of their
124
+ * own through the public API.
125
+ *
126
+ * The `moduleName` property still needs to be set to the simple name, i.e.
127
+ * the string passed to `require`, for instrumentation tracking purposes.
128
+ *
129
+ * Note: this value takes precedence over `moduleName`.
130
+ */
131
+ absolutePath
132
+
133
+ /**
134
+ * The fully resolved path to the module, e.g. `/opt/app/node_modules/foo`.
135
+ * If the module is a core module, the special value `.` should be used.
136
+ * @type {string}
137
+ */
138
+ resolvedName
139
+
140
+ /**
141
+ * Hook to invoke when the module is required. This is the actual
142
+ * implementation of the instrumentation.
143
+ * @type {InstrumentationOnRequire}
144
+ */
145
+ onRequire
146
+
147
+ /**
148
+ * Hook to invoke when the `onRequire` hook throws an error.
149
+ * @type {InstrumentationOnError}
150
+ */
151
+ onError
152
+
153
+ /**
154
+ * @type {number}
155
+ */
156
+ #id
157
+
158
+ /* eslint-disable jsdoc/require-param-description */
159
+ /**
160
+ * @param {InstrumentationDescriptorParams} params
161
+ */
162
+ constructor(params) {
163
+ this.absolutePath = params.absolutePath
164
+ this.module = params.module
165
+ this.moduleName = params.moduleName
166
+ this.onError = params.onError
167
+ this.onRequire = params.onRequire
168
+ this.resolvedName = params.resolvedName
169
+ this.type = params.type
170
+
171
+ this.#id = idGen.idFor(this.moduleName)
172
+ }
173
+
174
+ /**
175
+ * Identifier for the instrumentation. Used by the internal instrumentation
176
+ * tracker to distinguish between different instrumentations targeting the
177
+ * same module.
178
+ *
179
+ * @returns {number} The identifier.
180
+ */
181
+ get instrumentationId() {
182
+ return this.#id
183
+ }
184
+ }
185
+
186
+ module.exports = InstrumentationDescriptor
@@ -0,0 +1,222 @@
1
+ /*
2
+ * Copyright 2024 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const InstrumentationDescriptor = require('./instrumentation-descriptor')
9
+
10
+ /**
11
+ * @typedef {object} TrackedItemMeta
12
+ * @property {boolean} instrumented Indicates if the instrumentation
13
+ * has been successfully applied.
14
+ * @property {boolean|undefined} didError Indicates if the instrumentation
15
+ * application resulted in an error or not.
16
+ */
17
+
18
+ /**
19
+ * Represents tracked instrumentations in the {@link InstrumentationTracker}.
20
+ *
21
+ * @private
22
+ */
23
+ class TrackedItem {
24
+ /**
25
+ * @type {InstrumentationDescriptor}
26
+ */
27
+ instrumentation
28
+
29
+ /**
30
+ * @type {TrackedItemMeta}
31
+ */
32
+ meta
33
+
34
+ constructor(params) {
35
+ this.instrumentation = params.instrumentation
36
+ this.meta = params.meta
37
+ }
38
+ }
39
+
40
+ /**
41
+ * The instrumentation tracker is used to keep track of
42
+ * {@link InstrumentationDescriptor} instances in relation to modules that
43
+ * are being instrumented. The general process looks like:
44
+ *
45
+ * 1. We register an instrumentation with a simple name like `pino`. This
46
+ * "instrumentation" includes things like the `onRequire` and `onError`
47
+ * callbacks. In this context, "instrumentation" and "hook" are interchangeable
48
+ * terms.
49
+ *
50
+ * 2. Upon `require(<simple_name>)`, we hit `shimmer._postLoad` which will give
51
+ * us the fully resolved path to the module being loaded.
52
+ *
53
+ * 3. `_postLoad` will utilize the previously registered instrumentation
54
+ * information to determine if there are any callbacks for the module being
55
+ * loaded. If so, it will attempt to run the callbacks.
56
+ *
57
+ * 4. At this point we need to keep track of which simple name + fully resolved
58
+ * path has callbacks associated with it, and if the error callback was invoked.
59
+ * When a subsequent load of the same simple name + fully resolved path
60
+ * combination is encountered, we need to append that to the tracked hooks.
61
+ * Or, if the previous hook failed, provide a way for the loading algorithm
62
+ * to learn about that so that it can skip doing unnecessary work.
63
+ *
64
+ * The `InstrumentationTracker` object provides utility methods to facilitate
65
+ * that process.
66
+ *
67
+ * @private
68
+ */
69
+ class InstrumentationTracker {
70
+ #tracked = new Map()
71
+
72
+ get [Symbol.toStringTag]() {
73
+ return 'InstrumentationTracker'
74
+ }
75
+
76
+ /**
77
+ * Get all tracked instrumentations for the named module.
78
+ *
79
+ * @param {string} moduleName The simple name for the module, e.g. "pino".
80
+ *
81
+ * @returns {TrackedItem[]} All tracked items for the module.
82
+ */
83
+ getAllByName(moduleName) {
84
+ return this.#tracked.get(moduleName)
85
+ }
86
+
87
+ /**
88
+ * Get a specific tracked item for a module. This allows the
89
+ * {@link setHookSuccess} and {@link setHookFailure} methods to be used.
90
+ *
91
+ * @param {string} moduleName The simple name for the module, e.g. "pino".
92
+ * @param {InstrumentationDescriptor} instrumentation The instrumentation
93
+ * descriptor that is contained within the tracked item.
94
+ *
95
+ * @returns {TrackedItem|undefined} The full tracked item that includes the
96
+ * passed in descriptor along with the metadata about the instrumentation.
97
+ */
98
+ getTrackedItem(moduleName, instrumentation) {
99
+ const items = this.getAllByName(moduleName)
100
+ for (const item of items) {
101
+ /* istanbul ignore else */
102
+ if (item.instrumentation === instrumentation) {
103
+ return item
104
+ }
105
+ }
106
+ }
107
+
108
+ /**
109
+ * The primary entrypoint to the tracker. It registers the basic information
110
+ * about an instrumentation prior to the to-be-instrumented module being
111
+ * loaded. If this method is not used first, other methods will throw because
112
+ * they will not be able to find any tracked items.
113
+ *
114
+ * @param {string} moduleName The simple name of the module being
115
+ * instrumented, e.g. "pino". That is, whatever is passed to the `require`
116
+ * function.
117
+ * @param {InstrumentationDescriptor} instrumentation The initial descriptor
118
+ * for the module being instrumented.
119
+ */
120
+ track(moduleName, instrumentation) {
121
+ const tracked = this.#tracked.get(moduleName)
122
+ if (tracked === undefined) {
123
+ this.#tracked.set(moduleName, [
124
+ new TrackedItem({ instrumentation, meta: { instrumented: false, didError: undefined } })
125
+ ])
126
+ return
127
+ }
128
+
129
+ let found
130
+ for (const t of tracked) {
131
+ if (t.instrumentation.instrumentationId === instrumentation.instrumentationId) {
132
+ return
133
+ }
134
+ }
135
+ /* istanbul ignore else */
136
+ if (found === undefined) {
137
+ tracked.push(
138
+ new TrackedItem({ instrumentation, meta: { instrumented: false, didError: undefined } })
139
+ )
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Update the metadata for a tracked item to indicate that the hook failed.
145
+ *
146
+ * @param {TrackedItem} trackedItem The item to update.
147
+ */
148
+ setHookFailure(trackedItem) {
149
+ trackedItem.meta.instrumented = false
150
+ trackedItem.meta.didError = true
151
+ }
152
+
153
+ /**
154
+ * Update the metadata for a tracked item to indicate that the hook succeeded.
155
+ *
156
+ * @param {TrackedItem} trackedItem The item to update.
157
+ */
158
+ setHookSuccess(trackedItem) {
159
+ trackedItem.meta.instrumented = true
160
+ trackedItem.meta.didError = false
161
+ }
162
+
163
+ /**
164
+ * After a module has been loaded, via `require` or `import`, the tracked
165
+ * instrumentation for that module must have its `resolvedName` property
166
+ * updated. The `resolvedName` is used to uniquely identify instances of the
167
+ * module. Being able to uniquely identify instances is crucial to being able
168
+ * to instrument all loaded instances.
169
+ *
170
+ * @param {string} moduleName The simple name of the module, e.g. "pino".
171
+ * @param {string} resolvedName The fully resolved file system path to the
172
+ * module instance, e.g. "/opt/app/node_modules/pino".
173
+ *
174
+ * @throws {Error} If the provided `moduleName` is not present in the tracker.
175
+ */
176
+ setResolvedName(moduleName, resolvedName) {
177
+ const items = this.#tracked.get(moduleName)
178
+ if (items === undefined) {
179
+ throw Error(`module not tracked: ${moduleName}`)
180
+ }
181
+
182
+ const missingResolvedName = []
183
+ for (const item of items) {
184
+ if (item.instrumentation.resolvedName === undefined) {
185
+ missingResolvedName.push(item)
186
+ } else if (item.instrumentation.resolvedName === resolvedName) {
187
+ // We already have this specific instance of the module tracked.
188
+ // So we don't need to do anything.
189
+ return
190
+ }
191
+ }
192
+
193
+ // eslint-disable-next-line sonarjs/no-small-switch
194
+ switch (missingResolvedName.length) {
195
+ case 0: {
196
+ // We have encountered a new instance of the module. Therefore, we
197
+ // need to clone an existing instrumentation, but apply a different
198
+ // `resolvedName` to it.
199
+ //
200
+ // This happens when there is a dependency tree like:
201
+ // + `/opt/app/node_modules/foo`
202
+ // + `/opt/app/node_modules/transitive-dep/node_modules/foo`
203
+ const item = items[0]
204
+ this.track(
205
+ moduleName,
206
+ new InstrumentationDescriptor({ ...item.instrumentation, resolvedName })
207
+ )
208
+ break
209
+ }
210
+
211
+ default: {
212
+ // Add the same name to all found instrumentations. This definitely
213
+ // happens when the security agent is enabled.
214
+ missingResolvedName.forEach((item) => {
215
+ item.instrumentation.resolvedName = resolvedName
216
+ })
217
+ }
218
+ }
219
+ }
220
+ }
221
+
222
+ module.exports = InstrumentationTracker
@@ -0,0 +1,31 @@
1
+ /*
2
+ * Copyright 2024 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ class IdGenerator {
9
+ #tracked = new Map()
10
+
11
+ /**
12
+ * Get the next available identifier for the provided `name`.
13
+ *
14
+ * @param {string} name The label to get an id for.
15
+ *
16
+ * @returns {number} The id.
17
+ */
18
+ idFor(name) {
19
+ const key = Symbol.for(name)
20
+ let val = this.#tracked.get(key)
21
+ if (val === undefined) {
22
+ val = 0
23
+ } else {
24
+ val += 1
25
+ }
26
+ this.#tracked.set(key, val)
27
+ return val
28
+ }
29
+ }
30
+
31
+ module.exports = IdGenerator
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "11.13.0",
3
+ "version": "11.14.0",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "license": "Apache-2.0",
6
6
  "contributors": [
@@ -196,7 +196,7 @@
196
196
  "@newrelic/aws-sdk": "^7.3.0",
197
197
  "@newrelic/koa": "^9.0.0",
198
198
  "@newrelic/ritm": "^7.2.0",
199
- "@newrelic/security-agent": "^1.0.1",
199
+ "@newrelic/security-agent": "1.0.1",
200
200
  "@newrelic/superagent": "^7.0.1",
201
201
  "@tyriar/fibonacci-heap": "^2.0.7",
202
202
  "concat-stream": "^2.0.0",