newrelic 11.12.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 -14
- package/README.md +1 -1
- package/THIRD_PARTY_NOTICES.md +2 -2
- package/api.js +34 -1
- package/lib/errors/index.js +1 -0
- package/lib/feature_flags.js +3 -3
- package/lib/instrumentation/langchain/common.js +1 -3
- package/lib/instrumentation/langchain/nr-hooks.js +6 -0
- package/lib/instrumentation/langchain/tools.js +1 -1
- package/lib/instrumentation/langchain/vectorstore.js +108 -0
- package/lib/instrumentation/mongodb/common.js +17 -5
- package/lib/instrumentation/mongodb/v4-mongo.js +12 -2
- package/lib/instrumentation/restify.js +1 -1
- package/lib/instrumentation-descriptor.js +186 -0
- package/lib/instrumentation-tracker.js +222 -0
- package/lib/llm-events/error-message.js +6 -1
- package/lib/llm-events/langchain/vector-search-result.js +2 -3
- package/lib/llm-events/openai/chat-completion-message.js +11 -0
- package/lib/llm-events/openai/embedding.js +4 -0
- package/lib/llm-events/openai/event.js +1 -1
- package/lib/metrics/names.js +4 -2
- package/lib/shim/shim.js +15 -0
- package/lib/util/idgen.js +31 -0
- package/package.json +4 -4
package/NEWS.md
CHANGED
|
@@ -1,17 +1,60 @@
|
|
|
1
|
-
### v11.
|
|
2
|
-
|
|
3
|
-
#### Features
|
|
4
|
-
|
|
5
|
-
* Added
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
+
|
|
15
58
|
### v11.11.0 (2024-02-27)
|
|
16
59
|
|
|
17
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/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -504,7 +504,7 @@ This product includes source derived from [@grpc/proto-loader](https://github.co
|
|
|
504
504
|
|
|
505
505
|
### @newrelic/aws-sdk
|
|
506
506
|
|
|
507
|
-
This product includes source derived from [@newrelic/aws-sdk](https://github.com/newrelic/node-newrelic-aws-sdk) ([v7.
|
|
507
|
+
This product includes source derived from [@newrelic/aws-sdk](https://github.com/newrelic/node-newrelic-aws-sdk) ([v7.3.0](https://github.com/newrelic/node-newrelic-aws-sdk/tree/v7.3.0)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-newrelic-aws-sdk/blob/v7.3.0/LICENSE):
|
|
508
508
|
|
|
509
509
|
```
|
|
510
510
|
Apache License
|
|
@@ -712,7 +712,7 @@ This product includes source derived from [@newrelic/aws-sdk](https://github.com
|
|
|
712
712
|
|
|
713
713
|
### @newrelic/koa
|
|
714
714
|
|
|
715
|
-
This product includes source derived from [@newrelic/koa](https://github.com/newrelic/node-newrelic-koa) ([
|
|
715
|
+
This product includes source derived from [@newrelic/koa](https://github.com/newrelic/node-newrelic-koa) ([v9.0.0](https://github.com/newrelic/node-newrelic-koa/tree/v9.0.0)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-newrelic-koa/blob/v9.0.0/LICENSE):
|
|
716
716
|
|
|
717
717
|
```
|
|
718
718
|
Apache License
|
package/api.js
CHANGED
|
@@ -1839,7 +1839,7 @@ API.prototype.setErrorGroupCallback = function setErrorGroupCallback(callback) {
|
|
|
1839
1839
|
)
|
|
1840
1840
|
metric.incrementCallCount()
|
|
1841
1841
|
|
|
1842
|
-
if (!this.shim.isFunction(callback) || this.shim.
|
|
1842
|
+
if (!this.shim.isFunction(callback) || this.shim.isAsyncFunction(callback)) {
|
|
1843
1843
|
logger.warn(
|
|
1844
1844
|
'Error Group callback must be a synchronous function, Error Group attribute will not be added'
|
|
1845
1845
|
)
|
|
@@ -1849,4 +1849,37 @@ API.prototype.setErrorGroupCallback = function setErrorGroupCallback(callback) {
|
|
|
1849
1849
|
this.agent.errors.errorGroupCallback = callback
|
|
1850
1850
|
}
|
|
1851
1851
|
|
|
1852
|
+
/**
|
|
1853
|
+
* Registers a callback which will be used for calculating token counts on Llm events when they are not
|
|
1854
|
+
* available. This function will typically only be used if `ai_monitoring.record_content.enabled` is false
|
|
1855
|
+
* and you want to still capture token counts for Llm events.
|
|
1856
|
+
*
|
|
1857
|
+
* Provided callbacks must return an integer value for the token count for a given piece of content.
|
|
1858
|
+
*
|
|
1859
|
+
* @param {Function} callback - synchronous function called to calculate token count for content.
|
|
1860
|
+
* @example
|
|
1861
|
+
* // @param {string} model - name of model (i.e. gpt-3.5-turbo)
|
|
1862
|
+
* // @param {string} content - prompt or completion response
|
|
1863
|
+
* function tokenCallback(model, content) {
|
|
1864
|
+
* // calculate tokens based on model and content
|
|
1865
|
+
* // return token count
|
|
1866
|
+
* return 40
|
|
1867
|
+
* }
|
|
1868
|
+
*/
|
|
1869
|
+
API.prototype.setLlmTokenCountCallback = function setLlmTokenCountCallback(callback) {
|
|
1870
|
+
const metric = this.agent.metrics.getOrCreateMetric(
|
|
1871
|
+
NAMES.SUPPORTABILITY.API + '/setLlmTokenCountCallback'
|
|
1872
|
+
)
|
|
1873
|
+
metric.incrementCallCount()
|
|
1874
|
+
|
|
1875
|
+
if (!this.shim.isFunction(callback) || this.shim.isAsyncFunction(callback)) {
|
|
1876
|
+
logger.warn(
|
|
1877
|
+
'Llm token count callback must be a synchronous function, callback will not be registered.'
|
|
1878
|
+
)
|
|
1879
|
+
return
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
this.agent.llm.tokenCountCallback = callback
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1852
1885
|
module.exports = API
|
package/lib/errors/index.js
CHANGED
package/lib/feature_flags.js
CHANGED
|
@@ -12,8 +12,7 @@ exports.prerelease = {
|
|
|
12
12
|
reverse_naming_rules: false,
|
|
13
13
|
undici_async_tracking: true,
|
|
14
14
|
unresolved_promise_cleanup: true,
|
|
15
|
-
legacy_context_manager: false
|
|
16
|
-
langchain_instrumentation: false
|
|
15
|
+
legacy_context_manager: false
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
// flags that are no longer used for released features
|
|
@@ -35,7 +34,8 @@ exports.released = [
|
|
|
35
34
|
'certificate_bundle',
|
|
36
35
|
'async_local_context',
|
|
37
36
|
'undici_instrumentation',
|
|
38
|
-
'aws_bedrock_instrumentation'
|
|
37
|
+
'aws_bedrock_instrumentation',
|
|
38
|
+
'langchain_instrumentation'
|
|
39
39
|
]
|
|
40
40
|
|
|
41
41
|
// flags that are no longer used for unreleased features
|
|
@@ -59,7 +59,5 @@ common.recordEvent = function recordEvent({ agent, type, msg, pkgVersion }) {
|
|
|
59
59
|
* @returns {boolean} flag if we should skip instrumentation
|
|
60
60
|
*/
|
|
61
61
|
common.shouldSkipInstrumentation = function shouldSkipInstrumentation(config) {
|
|
62
|
-
return
|
|
63
|
-
config.ai_monitoring.enabled === true && config.feature_flag.langchain_instrumentation === true
|
|
64
|
-
)
|
|
62
|
+
return config.ai_monitoring.enabled !== true
|
|
65
63
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
const toolsInstrumentation = require('./tools')
|
|
8
8
|
const cbManagerInstrumentation = require('./callback-manager')
|
|
9
9
|
const runnableInstrumentation = require('./runnable')
|
|
10
|
+
const vectorstoreInstrumentation = require('./vectorstore')
|
|
10
11
|
|
|
11
12
|
module.exports = [
|
|
12
13
|
{
|
|
@@ -23,5 +24,10 @@ module.exports = [
|
|
|
23
24
|
type: 'generic',
|
|
24
25
|
moduleName: '@langchain/core/dist/runnables/base',
|
|
25
26
|
onRequire: runnableInstrumentation
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
type: 'generic',
|
|
30
|
+
moduleName: '@langchain/core/vectorstores',
|
|
31
|
+
onRequire: vectorstoreInstrumentation
|
|
26
32
|
}
|
|
27
33
|
]
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
const {
|
|
8
|
+
AI: { LANGCHAIN }
|
|
9
|
+
} = require('../../metrics/names')
|
|
10
|
+
const { LangChainVectorSearch, LangChainVectorSearchResult } = require('../../llm-events/langchain')
|
|
11
|
+
const { recordEvent, shouldSkipInstrumentation } = require('./common')
|
|
12
|
+
const { DESTINATIONS } = require('../../config/attribute-filter')
|
|
13
|
+
const { RecorderSpec } = require('../../shim/specs')
|
|
14
|
+
const LlmErrorMessage = require('../../llm-events/error-message')
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generates a LangChainVectorSearch for entire search request.
|
|
18
|
+
* Also iterates over documents in output and generates a
|
|
19
|
+
* LangChainVectorSearchResult for each document.
|
|
20
|
+
*
|
|
21
|
+
* @param {object} params input params
|
|
22
|
+
* @param {string} params.request vector search query
|
|
23
|
+
* @param {number} params.k vector search top k
|
|
24
|
+
* @param {object} params.output vector search documents
|
|
25
|
+
* @param {Agent} params.agent NR agent instance
|
|
26
|
+
* @param {TraceSegment} params.segment active segment from vector search
|
|
27
|
+
* @param {string} params.pkgVersion langchain version
|
|
28
|
+
* @param {err} params.err if it exists
|
|
29
|
+
*/
|
|
30
|
+
function recordVectorSearch({ request, k, output, agent, segment, pkgVersion, err }) {
|
|
31
|
+
const vectorSearch = new LangChainVectorSearch({
|
|
32
|
+
agent,
|
|
33
|
+
segment,
|
|
34
|
+
query: request,
|
|
35
|
+
k,
|
|
36
|
+
documents: output,
|
|
37
|
+
error: err !== null
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
recordEvent({ agent, type: 'LlmVectorSearch', pkgVersion, msg: vectorSearch })
|
|
41
|
+
|
|
42
|
+
output.forEach((document, sequence) => {
|
|
43
|
+
const vectorSearchResult = new LangChainVectorSearchResult({
|
|
44
|
+
agent,
|
|
45
|
+
segment,
|
|
46
|
+
metadata: document.metadata,
|
|
47
|
+
pageContent: document.pageContent,
|
|
48
|
+
sequence,
|
|
49
|
+
search_id: vectorSearch.id
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
recordEvent({
|
|
53
|
+
agent,
|
|
54
|
+
type: 'LlmVectorSearchResult',
|
|
55
|
+
pkgVersion,
|
|
56
|
+
msg: vectorSearchResult
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
if (err) {
|
|
61
|
+
agent.errors.add(
|
|
62
|
+
segment.transaction,
|
|
63
|
+
err,
|
|
64
|
+
new LlmErrorMessage({
|
|
65
|
+
response: output,
|
|
66
|
+
cause: err,
|
|
67
|
+
vectorsearch: vectorSearch
|
|
68
|
+
})
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = function initialize(shim, vectorstores) {
|
|
74
|
+
const { agent, pkgVersion } = shim
|
|
75
|
+
|
|
76
|
+
if (shouldSkipInstrumentation(agent.config)) {
|
|
77
|
+
shim.logger.debug(
|
|
78
|
+
'langchain instrumentation is disabled. To enable set `config.ai_monitoring.enabled` to true'
|
|
79
|
+
)
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
shim.record(
|
|
84
|
+
vectorstores.VectorStore.prototype,
|
|
85
|
+
'similaritySearch',
|
|
86
|
+
function wrapCall(shim, similaritySearch, fnName, args) {
|
|
87
|
+
const [request, k] = args
|
|
88
|
+
|
|
89
|
+
return new RecorderSpec({
|
|
90
|
+
name: `${LANGCHAIN.VECTORSTORE}/${fnName}`,
|
|
91
|
+
promise: true,
|
|
92
|
+
// eslint-disable-next-line max-params
|
|
93
|
+
after(_shim, _fn, _name, err, output, segment) {
|
|
94
|
+
if (!output) {
|
|
95
|
+
// If we get an error, it is possible that `output = null`.
|
|
96
|
+
// In that case, we define it to be an empty array.
|
|
97
|
+
output = []
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
segment.end()
|
|
101
|
+
recordVectorSearch({ request, k, output, agent, segment, pkgVersion, err })
|
|
102
|
+
|
|
103
|
+
segment.transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_EVENT, 'llm', true)
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
}
|
|
@@ -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
|
}
|
|
@@ -93,7 +93,7 @@ function wrapMiddleware(shim, middleware, _name, route) {
|
|
|
93
93
|
})
|
|
94
94
|
|
|
95
95
|
const wrappedMw = shim.recordMiddleware(middleware, spec)
|
|
96
|
-
if (middleware
|
|
96
|
+
if (shim.isAsyncFunction(middleware)) {
|
|
97
97
|
return async function asyncShim() {
|
|
98
98
|
return wrappedMw.apply(this, arguments)
|
|
99
99
|
}
|
|
@@ -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
|
|
@@ -18,14 +18,19 @@ module.exports = class LlmErrorMessage {
|
|
|
18
18
|
* conversation if it was a chat completion conversation.
|
|
19
19
|
* @param {LlmEmbedding} [params.embedding] Details about the conversation
|
|
20
20
|
* if it was an embedding conversation.
|
|
21
|
+
* @param {LlmVectorStoreSearch} [params.vectorsearch] Details about the vector
|
|
22
|
+
* search if it was a vector search event.
|
|
23
|
+
* @param {LlmTool} [params.tool] Details about the tool event if it was a tool event.
|
|
21
24
|
*/
|
|
22
|
-
constructor({ response, cause, summary, embedding } = {}) {
|
|
25
|
+
constructor({ response, cause, summary, embedding, vectorsearch, tool } = {}) {
|
|
23
26
|
this['http.statusCode'] = response?.status ?? cause?.status
|
|
24
27
|
this['error.message'] = cause?.message
|
|
25
28
|
this['error.code'] = response?.code ?? cause?.error?.code
|
|
26
29
|
this['error.param'] = response?.param ?? cause?.error?.param
|
|
27
30
|
this.completion_id = summary?.id
|
|
28
31
|
this.embedding_id = embedding?.id
|
|
32
|
+
this.vector_store_id = vectorsearch?.id
|
|
33
|
+
this.tool_id = tool?.id
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
get [Symbol.toStringTag]() {
|
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
'use strict'
|
|
7
7
|
|
|
8
8
|
const LangChainEvent = require('./event')
|
|
9
|
-
const crypto = require('crypto')
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* @typedef {object} LangChainVectorSearchResultParams
|
|
13
12
|
* @augments LangChainEventParams
|
|
14
13
|
* @property {string} pageContent The stringified contents of the pageContent attribute on each returned search result document.
|
|
15
14
|
* @property {number} [sequence=0] The index of the document in the search result documents list.
|
|
15
|
+
* @property {string} search_id The identifier from the LangChainVectorSearch event.
|
|
16
16
|
*/
|
|
17
17
|
/**
|
|
18
18
|
* @type {LangChainVectorSearchResultParams}
|
|
@@ -23,13 +23,12 @@ const defaultParams = {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
class LangChainVectorSearchResult extends LangChainEvent {
|
|
26
|
-
search_id = crypto.randomUUID()
|
|
27
|
-
|
|
28
26
|
constructor(params) {
|
|
29
27
|
params = Object.assign({}, defaultParams, params)
|
|
30
28
|
super(params)
|
|
31
29
|
const { agent } = params
|
|
32
30
|
|
|
31
|
+
this.search_id = params.search_id
|
|
33
32
|
this.sequence = params.sequence
|
|
34
33
|
|
|
35
34
|
if (agent.config.ai_monitoring.record_content.enabled === true) {
|
|
@@ -18,5 +18,16 @@ module.exports = class LlmChatCompletionMessage extends LlmEvent {
|
|
|
18
18
|
if (agent.config.ai_monitoring.record_content.enabled === true) {
|
|
19
19
|
this.content = message?.content
|
|
20
20
|
}
|
|
21
|
+
|
|
22
|
+
const tokenCB = agent.llm?.tokenCountCallback
|
|
23
|
+
if (typeof tokenCB !== 'function') {
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (this.is_response) {
|
|
28
|
+
this.token_count = tokenCB(this['response.model'], message?.content)
|
|
29
|
+
} else {
|
|
30
|
+
this.token_count = tokenCB(request.model || request.engine, message?.content)
|
|
31
|
+
}
|
|
21
32
|
}
|
|
22
33
|
}
|
|
@@ -14,5 +14,9 @@ module.exports = class LlmEmbedding extends LlmEvent {
|
|
|
14
14
|
if (agent.config.ai_monitoring.record_content.enabled === true) {
|
|
15
15
|
this.input = request.input?.toString()
|
|
16
16
|
}
|
|
17
|
+
this.token_count = agent.llm?.tokenCountCallback?.(
|
|
18
|
+
this['request.model'],
|
|
19
|
+
request.input?.toString()
|
|
20
|
+
)
|
|
17
21
|
}
|
|
18
22
|
}
|
|
@@ -19,7 +19,7 @@ module.exports = class LlmEvent extends BaseEvent {
|
|
|
19
19
|
this.span_id = segment?.id
|
|
20
20
|
this.transaction_id = segment?.transaction?.id
|
|
21
21
|
this['response.model'] = response.model
|
|
22
|
-
this.vendor = '
|
|
22
|
+
this.vendor = 'openai'
|
|
23
23
|
this.ingest_source = 'Node'
|
|
24
24
|
this.metadata = agent
|
|
25
25
|
|
package/lib/metrics/names.js
CHANGED
|
@@ -170,7 +170,8 @@ const AI = {
|
|
|
170
170
|
EMBEDDING: 'Llm/embedding',
|
|
171
171
|
COMPLETION: 'Llm/completion',
|
|
172
172
|
TOOL: 'Llm/tool',
|
|
173
|
-
CHAIN: 'Llm/chain'
|
|
173
|
+
CHAIN: 'Llm/chain',
|
|
174
|
+
VECTORSTORE: 'Llm/vectorstore'
|
|
174
175
|
}
|
|
175
176
|
|
|
176
177
|
AI.OPENAI = {
|
|
@@ -184,7 +185,8 @@ AI.LANGCHAIN = {
|
|
|
184
185
|
EMBEDDING: `${AI.EMBEDDING}/Langchain`,
|
|
185
186
|
COMPLETION: `${AI.COMPLETION}/Langchain`,
|
|
186
187
|
TOOL: `${AI.TOOL}/Langchain`,
|
|
187
|
-
CHAIN: `${AI.CHAIN}/Langchain
|
|
188
|
+
CHAIN: `${AI.CHAIN}/Langchain`,
|
|
189
|
+
VECTORSTORE: `${AI.VECTORSTORE}/Langchain`
|
|
188
190
|
}
|
|
189
191
|
|
|
190
192
|
const RESTIFY = {
|
package/lib/shim/shim.js
CHANGED
|
@@ -125,6 +125,7 @@ Shim.prototype.getName = getName
|
|
|
125
125
|
Shim.prototype.isObject = isObject
|
|
126
126
|
Shim.prototype.isFunction = isFunction
|
|
127
127
|
Shim.prototype.isPromise = isPromise
|
|
128
|
+
Shim.prototype.isAsyncFunction = isAsyncFunction
|
|
128
129
|
Shim.prototype.isString = isString
|
|
129
130
|
Shim.prototype.isNumber = isNumber
|
|
130
131
|
Shim.prototype.isBoolean = isBoolean
|
|
@@ -1345,6 +1346,20 @@ function isPromise(obj) {
|
|
|
1345
1346
|
return obj && typeof obj.then === 'function'
|
|
1346
1347
|
}
|
|
1347
1348
|
|
|
1349
|
+
/**
|
|
1350
|
+
* Determines if function is an async function.
|
|
1351
|
+
* Note it does not test if the return value of function is a
|
|
1352
|
+
* promise or async function
|
|
1353
|
+
*
|
|
1354
|
+
* @memberof Shim.prototype
|
|
1355
|
+
* @param fn
|
|
1356
|
+
* @param (function) function to test if async
|
|
1357
|
+
* @returns {boolean} True if the function is an async function
|
|
1358
|
+
*/
|
|
1359
|
+
function isAsyncFunction(fn) {
|
|
1360
|
+
return fn.constructor.name === 'AsyncFunction'
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1348
1363
|
/**
|
|
1349
1364
|
* Determines if the given value is null.
|
|
1350
1365
|
*
|
|
@@ -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": [
|
|
@@ -193,10 +193,10 @@
|
|
|
193
193
|
"dependencies": {
|
|
194
194
|
"@grpc/grpc-js": "^1.9.4",
|
|
195
195
|
"@grpc/proto-loader": "^0.7.5",
|
|
196
|
-
"@newrelic/aws-sdk": "^7.
|
|
197
|
-
"@newrelic/koa": "^
|
|
196
|
+
"@newrelic/aws-sdk": "^7.3.0",
|
|
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",
|