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 +57 -36
- package/README.md +1 -1
- package/lib/errors/index.js +1 -0
- package/lib/instrumentation/mongodb/common.js +17 -5
- package/lib/instrumentation/mongodb/v4-mongo.js +12 -2
- package/lib/instrumentation-descriptor.js +186 -0
- package/lib/instrumentation-tracker.js +222 -0
- package/lib/util/idgen.js +31 -0
- package/package.json +2 -2
package/NEWS.md
CHANGED
|
@@ -1,39 +1,60 @@
|
|
|
1
|
-
### v11.
|
|
2
|
-
|
|
3
|
-
#### Features
|
|
4
|
-
|
|
5
|
-
* Added
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
*
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
* Added
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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 [
|
|
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
|
|
package/lib/errors/index.js
CHANGED
|
@@ -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(
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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.
|
|
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": "
|
|
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",
|