newrelic 13.14.0 → 13.15.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 +33 -0
- package/THIRD_PARTY_NOTICES.md +11 -11
- package/lib/instrumentation/aws-sdk/v3/bedrock.js +5 -1
- package/lib/instrumentation/aws-sdk/v3/converse-stream-handler.js +12 -0
- package/lib/instrumentation/aws-sdk/v3/stream-handler.js +13 -0
- package/lib/instrumentation/redis.js +1 -0
- package/lib/instrumentations.js +0 -5
- package/lib/llm-events/aws-bedrock/chat-completion-summary.js +4 -2
- package/lib/llm-events/chat-completion-summary.js +6 -1
- package/lib/llm-events/google-genai/chat-completion-summary.js +3 -1
- package/lib/llm-events/openai/chat-completion-summary.js +4 -2
- package/lib/subscriber-configs.js +3 -0
- package/lib/subscribers/ai-monitoring/chat.js +9 -7
- package/lib/subscribers/google-genai/generate-content-stream.js +3 -0
- package/lib/subscribers/google-genai/generate-content.js +3 -1
- package/lib/subscribers/nestjs/config.js +27 -0
- package/lib/subscribers/nestjs/instrumentation.js +39 -0
- package/lib/subscribers/node-redis-client/add-command.js +13 -0
- package/lib/subscribers/node-redis-client/config.js +63 -0
- package/lib/subscribers/node-redis-client/multi.js +13 -0
- package/lib/subscribers/node-redis-client/send-command.js +12 -0
- package/lib/subscribers/openai/chat.js +11 -1
- package/lib/subscribers/redis-client/add-command.js +46 -0
- package/lib/subscribers/redis-client/client-propagation.js +108 -0
- package/lib/subscribers/redis-client/config.js +83 -0
- package/lib/subscribers/redis-client/multi.js +17 -0
- package/lib/subscribers/redis-client/send-command.js +17 -0
- package/package.json +2 -2
- package/lib/instrumentation/@nestjs/core.js +0 -35
- package/lib/instrumentation/@node-redis/client.js +0 -114
- package/lib/instrumentation/@redis/client.js +0 -7
- package/lib/instrumentation/generic-pool.js +0 -33
- package/lib/instrumentation/superagent.js +0 -68
package/NEWS.md
CHANGED
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
### v13.15.0 (2026-03-09)
|
|
2
|
+
|
|
3
|
+
#### Features
|
|
4
|
+
|
|
5
|
+
* Added `time_to_first_token` for `LlmChatCompletionSummary` ([#3781](https://github.com/newrelic/node-newrelic/pull/3781)) ([737f374](https://github.com/newrelic/node-newrelic/commit/737f3740d43908be348a7795dd2c85e7a4e88cb8))
|
|
6
|
+
* Added `time_to_first_token` for AWS Bedrock ([#3804](https://github.com/newrelic/node-newrelic/pull/3804)) ([a993b00](https://github.com/newrelic/node-newrelic/commit/a993b00b4fa55fb2e7ba0b7748c4c21d8660b911))
|
|
7
|
+
* Added `time_to_first_token` for Google Gen AI ([#3785](https://github.com/newrelic/node-newrelic/pull/3785)) ([d90ee70](https://github.com/newrelic/node-newrelic/commit/d90ee709538e430395c0fb5863c344fe37009ce5))
|
|
8
|
+
* Added `time_to_first_token` for OpenAI ([#3782](https://github.com/newrelic/node-newrelic/pull/3782)) ([07cfd57](https://github.com/newrelic/node-newrelic/commit/07cfd570d5cc3dd77a5ae23e88587d47af3d195a))
|
|
9
|
+
|
|
10
|
+
#### Code refactoring
|
|
11
|
+
|
|
12
|
+
* Migrated `redis` 4+ instrumentation to subscribe to events emitted ([#3773](https://github.com/newrelic/node-newrelic/pull/3773)) ([add6827](https://github.com/newrelic/node-newrelic/commit/add68278d29f5f415134426d33986a59c089d71f))
|
|
13
|
+
|
|
14
|
+
#### Documentation
|
|
15
|
+
|
|
16
|
+
* Updated compatibility report ([#3800](https://github.com/newrelic/node-newrelic/pull/3800)) ([5228cdc](https://github.com/newrelic/node-newrelic/commit/5228cdc0508217fa139d8b4f8232391fd063a5fa)) ([#3795](https://github.com/newrelic/node-newrelic/pull/3795)) ([1d6fe52](https://github.com/newrelic/node-newrelic/commit/1d6fe522f0e707729a4a01f5c5e5599670136917)) ([#3791](https://github.com/newrelic/node-newrelic/pull/3791)) ([e79db5b](https://github.com/newrelic/node-newrelic/commit/e79db5bbac1500bfa49f8ac3e9dc64c409396d79)) ([#3777](https://github.com/newrelic/node-newrelic/pull/3777)) ([12fe4c6](https://github.com/newrelic/node-newrelic/commit/12fe4c6f2a7a3f08efd874ba949150c7805a8888))
|
|
17
|
+
|
|
18
|
+
#### Miscellaneous chores
|
|
19
|
+
|
|
20
|
+
* Added version comment for agent-metadata.yml ([#3779](https://github.com/newrelic/node-newrelic/pull/3779)) ([1267293](https://github.com/newrelic/node-newrelic/commit/1267293cc36c79106b7cdd84dedf2c18b8a010d9))
|
|
21
|
+
* Refactored nestjs instrumentation to subscriber type ([#3792](https://github.com/newrelic/node-newrelic/pull/3792)) ([f7feb25](https://github.com/newrelic/node-newrelic/commit/f7feb25a5c7349433e7de26a0f6aebb820c034f4))
|
|
22
|
+
* Removed `@langchain/core` peer dep from langchain openai and aws tests ([#3788](https://github.com/newrelic/node-newrelic/pull/3788)) ([e670bb1](https://github.com/newrelic/node-newrelic/commit/e670bb1f47720c61509157cf45cd200234505733))
|
|
23
|
+
* Removed unneccessary `generic-pool` context propagation ([#3786](https://github.com/newrelic/node-newrelic/pull/3786)) ([bdd35fb](https://github.com/newrelic/node-newrelic/commit/bdd35fbf6b57d374de84f716488498a40ece41fd))
|
|
24
|
+
* Removed unnecessary `superagent` context propagation ([#3798](https://github.com/newrelic/node-newrelic/pull/3798)) ([81650e1](https://github.com/newrelic/node-newrelic/commit/81650e18d7da3897f9c82098def2f34c05c78d9e))
|
|
25
|
+
* Updated `@apm-js-collab/tracing-hooks` to `0.4.0` ([#3787](https://github.com/newrelic/node-newrelic/pull/3787)) ([d85fb2b](https://github.com/newrelic/node-newrelic/commit/d85fb2b832bc7202e5acafa19ff59094c6e386ee))
|
|
26
|
+
* Updated version format in agent-metadata.yml ([#3778](https://github.com/newrelic/node-newrelic/pull/3778)) ([61759e9](https://github.com/newrelic/node-newrelic/commit/61759e9c4e9ad2796a6462b967e30bad35b3fbf7))
|
|
27
|
+
|
|
28
|
+
#### Tests
|
|
29
|
+
|
|
30
|
+
* Updated langchain vectorstore tests to use a custom vectorstore to simplify the testing process ([#3793](https://github.com/newrelic/node-newrelic/pull/3793)) ([0fd9b54](https://github.com/newrelic/node-newrelic/commit/0fd9b54c0b4782162dae769453169ad4b05d7918))
|
|
31
|
+
* Updated version range for langgraph tests ([#3794](https://github.com/newrelic/node-newrelic/pull/3794)) ([e02ce99](https://github.com/newrelic/node-newrelic/commit/e02ce99083d584d8b61a223c67c4509f3fa9f4ce))
|
|
32
|
+
|
|
1
33
|
### v13.14.0 (2026-02-24)
|
|
2
34
|
|
|
3
35
|
#### Features
|
|
@@ -8399,5 +8431,6 @@ Special thanks to Ryan Copley (@RyanCopley) for the contribution.
|
|
|
8399
8431
|
|
|
8400
8432
|
|
|
8401
8433
|
|
|
8434
|
+
|
|
8402
8435
|
|
|
8403
8436
|
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -94,7 +94,7 @@ code, the source code can be found at [https://github.com/newrelic/node-newrelic
|
|
|
94
94
|
|
|
95
95
|
### @apm-js-collab/tracing-hooks
|
|
96
96
|
|
|
97
|
-
This product includes source derived from [@apm-js-collab/tracing-hooks](https://github.com/apm-js-collab/tracing-hooks) ([v0.
|
|
97
|
+
This product includes source derived from [@apm-js-collab/tracing-hooks](https://github.com/apm-js-collab/tracing-hooks) ([v0.4.0](https://github.com/apm-js-collab/tracing-hooks/tree/v0.4.0)), distributed under the [Apache-2.0 License](https://github.com/apm-js-collab/tracing-hooks/blob/v0.4.0/LICENSE):
|
|
98
98
|
|
|
99
99
|
```
|
|
100
100
|
|
|
@@ -722,7 +722,7 @@ This product includes source derived from [@grpc/proto-loader](https://github.co
|
|
|
722
722
|
|
|
723
723
|
### @newrelic/security-agent
|
|
724
724
|
|
|
725
|
-
This product includes source derived from [@newrelic/security-agent](https://github.com/newrelic/csec-node-agent) ([v3.0.
|
|
725
|
+
This product includes source derived from [@newrelic/security-agent](https://github.com/newrelic/csec-node-agent) ([v3.0.2](https://github.com/newrelic/csec-node-agent/tree/v3.0.2)), distributed under the [UNKNOWN License](https://github.com/newrelic/csec-node-agent/blob/v3.0.2/LICENSE):
|
|
726
726
|
|
|
727
727
|
```
|
|
728
728
|
## New Relic Software License v1.0
|
|
@@ -1186,7 +1186,7 @@ This product includes source derived from [@opentelemetry/api](https://github.co
|
|
|
1186
1186
|
|
|
1187
1187
|
### @opentelemetry/core
|
|
1188
1188
|
|
|
1189
|
-
This product includes source derived from [@opentelemetry/core](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.
|
|
1189
|
+
This product includes source derived from [@opentelemetry/core](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.1](https://github.com/open-telemetry/opentelemetry-js/tree/v2.5.1)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.5.1/LICENSE):
|
|
1190
1190
|
|
|
1191
1191
|
```
|
|
1192
1192
|
Apache License
|
|
@@ -1604,7 +1604,7 @@ This product includes source derived from [@opentelemetry/exporter-metrics-otlp-
|
|
|
1604
1604
|
|
|
1605
1605
|
### @opentelemetry/resources
|
|
1606
1606
|
|
|
1607
|
-
This product includes source derived from [@opentelemetry/resources](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.
|
|
1607
|
+
This product includes source derived from [@opentelemetry/resources](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.1](https://github.com/open-telemetry/opentelemetry-js/tree/v2.5.1)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.5.1/LICENSE):
|
|
1608
1608
|
|
|
1609
1609
|
```
|
|
1610
1610
|
Apache License
|
|
@@ -2022,7 +2022,7 @@ This product includes source derived from [@opentelemetry/sdk-logs](https://gith
|
|
|
2022
2022
|
|
|
2023
2023
|
### @opentelemetry/sdk-metrics
|
|
2024
2024
|
|
|
2025
|
-
This product includes source derived from [@opentelemetry/sdk-metrics](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.
|
|
2025
|
+
This product includes source derived from [@opentelemetry/sdk-metrics](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.1](https://github.com/open-telemetry/opentelemetry-js/tree/v2.5.1)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.5.1/LICENSE):
|
|
2026
2026
|
|
|
2027
2027
|
```
|
|
2028
2028
|
Apache License
|
|
@@ -2231,7 +2231,7 @@ This product includes source derived from [@opentelemetry/sdk-metrics](https://g
|
|
|
2231
2231
|
|
|
2232
2232
|
### @opentelemetry/sdk-trace-base
|
|
2233
2233
|
|
|
2234
|
-
This product includes source derived from [@opentelemetry/sdk-trace-base](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.
|
|
2234
|
+
This product includes source derived from [@opentelemetry/sdk-trace-base](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.1](https://github.com/open-telemetry/opentelemetry-js/tree/v2.5.1)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.5.1/LICENSE):
|
|
2235
2235
|
|
|
2236
2236
|
```
|
|
2237
2237
|
Apache License
|
|
@@ -2904,7 +2904,7 @@ SOFTWARE.
|
|
|
2904
2904
|
|
|
2905
2905
|
### semver
|
|
2906
2906
|
|
|
2907
|
-
This product includes source derived from [semver](https://github.com/npm/node-semver) ([v7.7.
|
|
2907
|
+
This product includes source derived from [semver](https://github.com/npm/node-semver) ([v7.7.4](https://github.com/npm/node-semver/tree/v7.7.4)), distributed under the [ISC License](https://github.com/npm/node-semver/blob/v7.7.4/LICENSE):
|
|
2908
2908
|
|
|
2909
2909
|
```
|
|
2910
2910
|
The ISC License
|
|
@@ -2960,7 +2960,7 @@ SOFTWARE.
|
|
|
2960
2960
|
|
|
2961
2961
|
### @aws-sdk/client-s3
|
|
2962
2962
|
|
|
2963
|
-
This product includes source derived from [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3) ([v3.
|
|
2963
|
+
This product includes source derived from [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3) ([v3.996.0](https://github.com/aws/aws-sdk-js-v3/tree/v3.996.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.996.0/LICENSE):
|
|
2964
2964
|
|
|
2965
2965
|
```
|
|
2966
2966
|
Apache License
|
|
@@ -3169,7 +3169,7 @@ This product includes source derived from [@aws-sdk/client-s3](https://github.co
|
|
|
3169
3169
|
|
|
3170
3170
|
### @aws-sdk/s3-request-presigner
|
|
3171
3171
|
|
|
3172
|
-
This product includes source derived from [@aws-sdk/s3-request-presigner](https://github.com/aws/aws-sdk-js-v3) ([v3.
|
|
3172
|
+
This product includes source derived from [@aws-sdk/s3-request-presigner](https://github.com/aws/aws-sdk-js-v3) ([v3.996.0](https://github.com/aws/aws-sdk-js-v3/tree/v3.996.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.996.0/LICENSE):
|
|
3173
3173
|
|
|
3174
3174
|
```
|
|
3175
3175
|
Apache License
|
|
@@ -4510,7 +4510,7 @@ Apache License
|
|
|
4510
4510
|
|
|
4511
4511
|
### ajv
|
|
4512
4512
|
|
|
4513
|
-
This product includes source derived from [ajv](https://github.com/ajv-validator/ajv) ([v6.
|
|
4513
|
+
This product includes source derived from [ajv](https://github.com/ajv-validator/ajv) ([v6.14.0](https://github.com/ajv-validator/ajv/tree/v6.14.0)), distributed under the [MIT License](https://github.com/ajv-validator/ajv/blob/v6.14.0/LICENSE):
|
|
4514
4514
|
|
|
4515
4515
|
```
|
|
4516
4516
|
The MIT License (MIT)
|
|
@@ -5000,7 +5000,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
5000
5000
|
|
|
5001
5001
|
### eslint
|
|
5002
5002
|
|
|
5003
|
-
This product includes source derived from [eslint](https://github.com/eslint/eslint) ([v9.39.
|
|
5003
|
+
This product includes source derived from [eslint](https://github.com/eslint/eslint) ([v9.39.3](https://github.com/eslint/eslint/tree/v9.39.3)), distributed under the [MIT License](https://github.com/eslint/eslint/blob/v9.39.3/LICENSE):
|
|
5004
5004
|
|
|
5005
5005
|
```
|
|
5006
5006
|
Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
|
|
@@ -112,6 +112,7 @@ function addLlmMeta({ agent, segment, transaction }) {
|
|
|
112
112
|
* @param {BedrockResponse} params.bedrockResponse parsed response
|
|
113
113
|
* @param {Shim} params.shim shim instance
|
|
114
114
|
* @param {Transaction} params.transaction active transaction
|
|
115
|
+
* @param {number} [params.timeOfFirstToken] For streaming requests only, timestamp of when first token was sent.
|
|
115
116
|
*/
|
|
116
117
|
function recordChatCompletionMessages({
|
|
117
118
|
agent,
|
|
@@ -120,7 +121,8 @@ function recordChatCompletionMessages({
|
|
|
120
121
|
transaction,
|
|
121
122
|
bedrockCommand,
|
|
122
123
|
bedrockResponse,
|
|
123
|
-
err
|
|
124
|
+
err,
|
|
125
|
+
timeOfFirstToken
|
|
124
126
|
}) {
|
|
125
127
|
if (shouldSkipInstrumentation(agent.config) === true) {
|
|
126
128
|
shim.logger.debug('skipping sending of ai data')
|
|
@@ -133,6 +135,7 @@ function recordChatCompletionMessages({
|
|
|
133
135
|
bedrockCommand,
|
|
134
136
|
transaction,
|
|
135
137
|
segment,
|
|
138
|
+
timeOfFirstToken,
|
|
136
139
|
error: err !== null
|
|
137
140
|
})
|
|
138
141
|
|
|
@@ -341,6 +344,7 @@ function handleResponse({ shim, err, response, segment, transaction, bedrockComm
|
|
|
341
344
|
transaction,
|
|
342
345
|
bedrockCommand,
|
|
343
346
|
bedrockResponse,
|
|
347
|
+
timeOfFirstToken: this?.timeOfFirstToken,
|
|
344
348
|
err
|
|
345
349
|
})
|
|
346
350
|
} else if (modelType === 'embedding') {
|
|
@@ -80,12 +80,19 @@ class ConverseStreamHandler {
|
|
|
80
80
|
*/
|
|
81
81
|
onComplete
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Timestamp of when first token was sent; needed for construction of the
|
|
85
|
+
* `LlmChatCompletionSummary` event.
|
|
86
|
+
*/
|
|
87
|
+
timeOfFirstToken
|
|
88
|
+
|
|
83
89
|
constructor({ stream, passThroughParams, onComplete }) {
|
|
84
90
|
this.passThroughParams = passThroughParams
|
|
85
91
|
this.stream = stream
|
|
86
92
|
this.onComplete = onComplete
|
|
87
93
|
this.segment = passThroughParams.segment
|
|
88
94
|
this.generator = handleConverse
|
|
95
|
+
this.timeOfFirstToken = null
|
|
89
96
|
}
|
|
90
97
|
|
|
91
98
|
/**
|
|
@@ -97,6 +104,7 @@ class ConverseStreamHandler {
|
|
|
97
104
|
*/
|
|
98
105
|
finish() {
|
|
99
106
|
this.passThroughParams.response = this.response
|
|
107
|
+
this.passThroughParams.timeOfFirstToken = this.timeOfFirstToken
|
|
100
108
|
this.onComplete(this.passThroughParams)
|
|
101
109
|
|
|
102
110
|
this.segment.touch()
|
|
@@ -136,6 +144,10 @@ async function * handleConverse() {
|
|
|
136
144
|
activeChunk = null
|
|
137
145
|
}
|
|
138
146
|
} else if (event.contentBlockDelta?.delta) {
|
|
147
|
+
// Delta represents a letter/word/token
|
|
148
|
+
if (!this.timeOfFirstToken) {
|
|
149
|
+
this.timeOfFirstToken = Date.now()
|
|
150
|
+
}
|
|
139
151
|
// There are also deltas for tool use (stringified inputs) but we don't currently record them so we just ignore for now
|
|
140
152
|
if (event.contentBlockDelta.delta.text) {
|
|
141
153
|
// It seems like the first streamed chunk does not always start with a contentBlockStart message
|
|
@@ -83,11 +83,18 @@ class StreamHandler {
|
|
|
83
83
|
*/
|
|
84
84
|
onComplete
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Timestamp of when first token was sent; needed for construction of the
|
|
88
|
+
* `LlmChatCompletionSummary` event.
|
|
89
|
+
*/
|
|
90
|
+
timeOfFirstToken
|
|
91
|
+
|
|
86
92
|
constructor({ stream, passThroughParams, onComplete }) {
|
|
87
93
|
this.passThroughParams = passThroughParams
|
|
88
94
|
this.stream = stream
|
|
89
95
|
this.onComplete = onComplete
|
|
90
96
|
this.segment = passThroughParams.segment
|
|
97
|
+
this.timeOfFirstToken = null
|
|
91
98
|
|
|
92
99
|
const { bedrockCommand } = passThroughParams
|
|
93
100
|
|
|
@@ -204,6 +211,7 @@ async function * handleClaude() {
|
|
|
204
211
|
|
|
205
212
|
try {
|
|
206
213
|
for await (const event of this.stream) {
|
|
214
|
+
if (!this.timeOfFirstToken) this.timeOfFirstToken = Date.now()
|
|
207
215
|
yield event
|
|
208
216
|
const parsed = this.parseEvent(event)
|
|
209
217
|
this.updateHeaders(parsed)
|
|
@@ -224,6 +232,7 @@ async function * handleClaude3() {
|
|
|
224
232
|
|
|
225
233
|
try {
|
|
226
234
|
for await (const event of this.stream) {
|
|
235
|
+
if (!this.timeOfFirstToken) this.timeOfFirstToken = Date.now()
|
|
227
236
|
yield event
|
|
228
237
|
const parsed = this.parseEvent(event)
|
|
229
238
|
this.updateHeaders(parsed)
|
|
@@ -247,6 +256,7 @@ async function * handleCohere() {
|
|
|
247
256
|
const generations = []
|
|
248
257
|
try {
|
|
249
258
|
for await (const event of this.stream) {
|
|
259
|
+
if (!this.timeOfFirstToken) this.timeOfFirstToken = Date.now()
|
|
250
260
|
yield event
|
|
251
261
|
const parsed = this.parseEvent(event)
|
|
252
262
|
this.updateHeaders(parsed)
|
|
@@ -265,6 +275,7 @@ async function * handleCohereEmbed() {
|
|
|
265
275
|
const embeddings = []
|
|
266
276
|
try {
|
|
267
277
|
for await (const event of this.stream) {
|
|
278
|
+
if (!this.timeOfFirstToken) this.timeOfFirstToken = Date.now()
|
|
268
279
|
yield event
|
|
269
280
|
const parsed = this.parseEvent(event)
|
|
270
281
|
this.updateHeaders(parsed)
|
|
@@ -284,6 +295,7 @@ async function * handleLlama() {
|
|
|
284
295
|
|
|
285
296
|
try {
|
|
286
297
|
for await (const event of this.stream) {
|
|
298
|
+
if (!this.timeOfFirstToken) this.timeOfFirstToken = Date.now()
|
|
287
299
|
yield event
|
|
288
300
|
const parsed = this.parseEvent(event)
|
|
289
301
|
this.updateHeaders(parsed)
|
|
@@ -308,6 +320,7 @@ async function * handleTitan() {
|
|
|
308
320
|
|
|
309
321
|
try {
|
|
310
322
|
for await (const event of this.stream) {
|
|
323
|
+
if (!this.timeOfFirstToken) this.timeOfFirstToken = Date.now()
|
|
311
324
|
yield event // Pass it up to the real consumer of the stream.
|
|
312
325
|
const parsed = this.parseEvent(event)
|
|
313
326
|
this.updateHeaders(parsed)
|
|
@@ -14,6 +14,7 @@ const {
|
|
|
14
14
|
module.exports = function initialize(_agent, redis, _moduleName, shim) {
|
|
15
15
|
const proto = redis?.RedisClient?.prototype
|
|
16
16
|
if (!proto) {
|
|
17
|
+
// This will happen when redis >= 4.0.0
|
|
17
18
|
shim.logger.warn('Skipping redis instrumentation due to unrecognized module shape')
|
|
18
19
|
return false
|
|
19
20
|
}
|
package/lib/instrumentations.js
CHANGED
|
@@ -14,15 +14,11 @@ module.exports = function instrumentations() {
|
|
|
14
14
|
'@grpc/grpc-js': { module: './instrumentation/grpc-js' },
|
|
15
15
|
'@hapi/hapi': { type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK },
|
|
16
16
|
'@hapi/vision': { type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK },
|
|
17
|
-
'@nestjs/core': { type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK },
|
|
18
|
-
'@node-redis/client': { type: InstrumentationDescriptor.TYPE_DATASTORE },
|
|
19
17
|
'@prisma/client': { type: InstrumentationDescriptor.TYPE_DATASTORE },
|
|
20
|
-
'@redis/client': { type: InstrumentationDescriptor.TYPE_DATASTORE },
|
|
21
18
|
'aws-sdk': { module: './instrumentation/aws-sdk' },
|
|
22
19
|
bluebird: { type: InstrumentationDescriptor.TYPE_PROMISE },
|
|
23
20
|
connect: { type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK },
|
|
24
21
|
fastify: { type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK },
|
|
25
|
-
'generic-pool': { type: InstrumentationDescriptor.TYPE_GENERIC },
|
|
26
22
|
kafkajs: { type: InstrumentationDescriptor.TYPE_MESSAGE },
|
|
27
23
|
koa: { module: './instrumentation/koa' },
|
|
28
24
|
memcached: { type: InstrumentationDescriptor.TYPE_DATASTORE },
|
|
@@ -31,7 +27,6 @@ module.exports = function instrumentations() {
|
|
|
31
27
|
q: { type: null },
|
|
32
28
|
redis: { type: InstrumentationDescriptor.TYPE_DATASTORE },
|
|
33
29
|
restify: { type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK },
|
|
34
|
-
superagent: { type: InstrumentationDescriptor.TYPE_GENERIC },
|
|
35
30
|
when: { module: './instrumentation/when' },
|
|
36
31
|
winston: { type: InstrumentationDescriptor.TYPE_GENERIC }
|
|
37
32
|
}
|
|
@@ -16,9 +16,10 @@ module.exports = class AwsBedrockLlmChatCompletionSummary extends LlmChatComplet
|
|
|
16
16
|
* @param {object} params.transaction Current and active transaction
|
|
17
17
|
* @param {object} params.bedrockCommand AWS Bedrock Command object, represents the request
|
|
18
18
|
* @param {object} params.bedrockResponse AWS Bedrock Response object
|
|
19
|
+
* @param {number} [params.timeOfFirstToken] For streaming requests only, timestamp of when first token was sent.
|
|
19
20
|
* @param {boolean} [params.error] Set to `true` if an error occurred during creation call, omitted if no error occurred
|
|
20
21
|
*/
|
|
21
|
-
constructor({ agent, segment, transaction, bedrockCommand, bedrockResponse, error }) {
|
|
22
|
+
constructor({ agent, segment, transaction, bedrockCommand, bedrockResponse, timeOfFirstToken, error }) {
|
|
22
23
|
super({ agent,
|
|
23
24
|
segment,
|
|
24
25
|
transaction,
|
|
@@ -30,7 +31,8 @@ module.exports = class AwsBedrockLlmChatCompletionSummary extends LlmChatComplet
|
|
|
30
31
|
temperature: bedrockCommand.temperature,
|
|
31
32
|
maxTokens: bedrockCommand.maxTokens,
|
|
32
33
|
numMsgs: (bedrockCommand.prompt.length ?? 0) + (bedrockResponse.completions.length ?? 0),
|
|
33
|
-
finishReason: bedrockResponse?.finishReason
|
|
34
|
+
finishReason: bedrockResponse?.finishReason,
|
|
35
|
+
timeOfFirstToken })
|
|
34
36
|
|
|
35
37
|
this.setTokens(agent, bedrockCommand, bedrockResponse)
|
|
36
38
|
}
|
|
@@ -28,6 +28,9 @@ const LlmEvent = require('./event-base')
|
|
|
28
28
|
* a chat completion including system, user, and assistant messages
|
|
29
29
|
* @property {string} response.organization Organization ID returned in the
|
|
30
30
|
* response or response headers
|
|
31
|
+
* @property {number} time_to_first_token Duration in milliseconds between when
|
|
32
|
+
* the request was issued and the first token was received. This attribute **MUST**
|
|
33
|
+
* only be captured for streaming requests.
|
|
31
34
|
*/
|
|
32
35
|
module.exports = class LlmChatCompletionSummary extends LlmEvent {
|
|
33
36
|
/**
|
|
@@ -47,10 +50,11 @@ module.exports = class LlmChatCompletionSummary extends LlmEvent {
|
|
|
47
50
|
* @param {number} params.numMsgs Number of messages comprising a
|
|
48
51
|
* chat completion including system, user, and assistant messages
|
|
49
52
|
* @param {string} params.finishReason Reason the model stopped generating tokens (e.g. "stop")
|
|
53
|
+
* @param {number} [params.timeOfFirstToken] Timestamp of when the first token was received. Only supply for streaming requests.
|
|
50
54
|
* @param {boolean} [params.error] Set to `true` if an error occurred during creation call, omitted if no error occurred
|
|
51
55
|
*/
|
|
52
56
|
constructor({ agent, segment, transaction, vendor, responseModel, requestModel, requestId,
|
|
53
|
-
responseOrg, temperature, maxTokens, numMsgs = 0, finishReason, error }) {
|
|
57
|
+
responseOrg, temperature, maxTokens, numMsgs = 0, finishReason, timeOfFirstToken, error }) {
|
|
54
58
|
super({ agent, segment, transaction, vendor, responseModel, requestId, error })
|
|
55
59
|
|
|
56
60
|
if (requestModel) this['request.model'] = requestModel
|
|
@@ -61,6 +65,7 @@ module.exports = class LlmChatCompletionSummary extends LlmEvent {
|
|
|
61
65
|
|
|
62
66
|
this['response.number_of_messages'] = numMsgs
|
|
63
67
|
this.timestamp = segment.timer.start
|
|
68
|
+
if (timeOfFirstToken) this['time_to_first_token'] = timeOfFirstToken - this.timestamp
|
|
64
69
|
this.duration = segment.getDurationInMillis()
|
|
65
70
|
}
|
|
66
71
|
|
|
@@ -20,9 +20,10 @@ module.exports = class GoogleGenAiLlmChatCompletionSummary extends LlmChatComple
|
|
|
20
20
|
* @param {Transaction} params.transaction Current and active transaction
|
|
21
21
|
* @param {object} params.request Google Gen AI request object
|
|
22
22
|
* @param {object} params.response Google Gen AI response object
|
|
23
|
+
* @param {number} [params.timeOfFirstToken] Timestamp of when the first token was sent, for streaming only.
|
|
23
24
|
* @param {boolean} [params.error] Set to `true` if an error occurred
|
|
24
25
|
*/
|
|
25
|
-
constructor({ agent, segment, transaction, request, response, error }) {
|
|
26
|
+
constructor({ agent, segment, transaction, request, response, timeOfFirstToken, error }) {
|
|
26
27
|
super({ agent,
|
|
27
28
|
segment,
|
|
28
29
|
transaction,
|
|
@@ -32,6 +33,7 @@ module.exports = class GoogleGenAiLlmChatCompletionSummary extends LlmChatComple
|
|
|
32
33
|
maxTokens: request.config?.maxOutputTokens,
|
|
33
34
|
temperature: request.config?.temperature,
|
|
34
35
|
vendor: 'gemini',
|
|
36
|
+
timeOfFirstToken,
|
|
35
37
|
error })
|
|
36
38
|
|
|
37
39
|
let requestMessagesLength = 0
|
|
@@ -20,9 +20,10 @@ module.exports = class OpenAiLlmChatCompletionSummary extends LlmChatCompletionS
|
|
|
20
20
|
* @param {Transaction} params.transaction Current and active transaction.
|
|
21
21
|
* @param {object} params.request OpenAI request object.
|
|
22
22
|
* @param {object} params.response OpenAI response object.
|
|
23
|
+
* @param {number} [params.timeOfFirstToken] Timestamp of when the first token was sent, for streaming only.
|
|
23
24
|
* @param {boolean} [params.error] Set to `true` if an error occurred, can be omitted in false cases.
|
|
24
25
|
*/
|
|
25
|
-
constructor({ agent, segment, transaction, request, response, error }) {
|
|
26
|
+
constructor({ agent, segment, transaction, request, response, timeOfFirstToken, error }) {
|
|
26
27
|
super({
|
|
27
28
|
agent,
|
|
28
29
|
segment,
|
|
@@ -34,7 +35,8 @@ module.exports = class OpenAiLlmChatCompletionSummary extends LlmChatCompletionS
|
|
|
34
35
|
requestModel: request?.model,
|
|
35
36
|
requestId: response?.headers?.['x-request-id'],
|
|
36
37
|
temperature: request?.temperature,
|
|
37
|
-
maxTokens: request?.max_tokens ?? request?.max_output_tokens
|
|
38
|
+
maxTokens: request?.max_tokens ?? request?.max_output_tokens,
|
|
39
|
+
timeOfFirstToken
|
|
38
40
|
})
|
|
39
41
|
|
|
40
42
|
if (request?.input) {
|
|
@@ -22,9 +22,12 @@ const subscribers = {
|
|
|
22
22
|
...require('./subscribers/mcp-sdk/config'),
|
|
23
23
|
...require('./subscribers/mysql/config'),
|
|
24
24
|
...require('./subscribers/mysql2/config'),
|
|
25
|
+
...require('./subscribers/nestjs/config'),
|
|
26
|
+
...require('./subscribers/node-redis-client/config'),
|
|
25
27
|
...require('./subscribers/openai/config'),
|
|
26
28
|
...require('./subscribers/pino/config'),
|
|
27
29
|
...require('./subscribers/pg/config'),
|
|
30
|
+
...require('./subscribers/redis-client/config'),
|
|
28
31
|
...require('./subscribers/undici/config')
|
|
29
32
|
}
|
|
30
33
|
|
|
@@ -50,12 +50,13 @@ class AiMonitoringChatSubscriber extends AiMonitoringSubscriber {
|
|
|
50
50
|
* @param {Context} params.ctx active context
|
|
51
51
|
* @param {object} params.request request made to method on a given llm library
|
|
52
52
|
* @param {object} params.response response from method on a given llm library
|
|
53
|
-
* @param {object} params.err error if present
|
|
54
|
-
* @param {
|
|
55
|
-
* @param {
|
|
53
|
+
* @param {object} [params.err] error if present
|
|
54
|
+
* @param {number} [params.timeOfFirstToken] Timestamp of when the first streaming token was sent.
|
|
55
|
+
* @param {object} [params.metadata] used only for langchain events at the moment
|
|
56
|
+
* @param {Array} [params.tags] used only for langchain events at the moment
|
|
56
57
|
* returns {object} a llm completion summary instance for the given LLM
|
|
57
58
|
*/
|
|
58
|
-
createCompletionSummary({ ctx, request, response, err, metadata, tags }) {
|
|
59
|
+
createCompletionSummary({ ctx, request, response, err, timeOfFirstToken, metadata, tags }) {
|
|
59
60
|
throw new Error('createCompletionSummary must be implemented by your subscriber')
|
|
60
61
|
}
|
|
61
62
|
|
|
@@ -79,11 +80,12 @@ class AiMonitoringChatSubscriber extends AiMonitoringSubscriber {
|
|
|
79
80
|
* @param {Context} params.ctx active context
|
|
80
81
|
* @param {object} params.request request made to method on a given llm library
|
|
81
82
|
* @param {object} params.response response from method on a given llm library
|
|
82
|
-
* @param {object} params.err error if present
|
|
83
|
+
* @param {object} [params.err] error if present
|
|
84
|
+
* @param {number} [params.timeOfFirstToken] Timestamp of when the first streaming token was sent.
|
|
83
85
|
* @param {object} params.metadata used only for langchain events at the moment
|
|
84
86
|
* @param {Array} params.tags used only for langchain events at the moment
|
|
85
87
|
*/
|
|
86
|
-
recordChatCompletionEvents({ ctx, request, response, err, metadata = {}, tags = [] }) {
|
|
88
|
+
recordChatCompletionEvents({ ctx, request, response, err, timeOfFirstToken, metadata = {}, tags = [] }) {
|
|
87
89
|
if (!this.enabled) {
|
|
88
90
|
this.logger.debug('config.ai_monitoring.enabled is set to false, not creating chat completion events.')
|
|
89
91
|
return
|
|
@@ -97,7 +99,7 @@ class AiMonitoringChatSubscriber extends AiMonitoringSubscriber {
|
|
|
97
99
|
// Explicitly end segment to provide consistent duration
|
|
98
100
|
// for both LLM events and the segment
|
|
99
101
|
ctx.segment.end()
|
|
100
|
-
const completionSummary = this.createCompletionSummary({ ctx, request, response, err, metadata, tags })
|
|
102
|
+
const completionSummary = this.createCompletionSummary({ ctx, request, response, timeOfFirstToken, err, metadata, tags })
|
|
101
103
|
this.recordEvent({ type: 'LlmChatCompletionSummary', msg: completionSummary })
|
|
102
104
|
|
|
103
105
|
const messages = this.getMessages({ request, response })
|
|
@@ -30,6 +30,7 @@ class GoogleGenAIGenerateContentStreamSubscriber extends GoogleGenAIGenerateCont
|
|
|
30
30
|
let cachedResult = {}
|
|
31
31
|
let err
|
|
32
32
|
let entireMessage = ''
|
|
33
|
+
let timeOfFirstToken
|
|
33
34
|
response.next = async function wrappedNext(...nextArgs) {
|
|
34
35
|
let result = {}
|
|
35
36
|
try {
|
|
@@ -44,6 +45,7 @@ class GoogleGenAIGenerateContentStreamSubscriber extends GoogleGenAIGenerateCont
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
if (result?.value?.text) {
|
|
48
|
+
if (!timeOfFirstToken) timeOfFirstToken = Date.now()
|
|
47
49
|
entireMessage += result.value.text // readonly variable that equates to result.value.candidates[0].content.parts[0].text
|
|
48
50
|
}
|
|
49
51
|
} catch (streamErr) {
|
|
@@ -64,6 +66,7 @@ class GoogleGenAIGenerateContentStreamSubscriber extends GoogleGenAIGenerateCont
|
|
|
64
66
|
ctx,
|
|
65
67
|
request,
|
|
66
68
|
response: cachedResult,
|
|
69
|
+
timeOfFirstToken,
|
|
67
70
|
err
|
|
68
71
|
})
|
|
69
72
|
}
|
|
@@ -56,10 +56,11 @@ class GoogleGenAIGenerateContentSubscriber extends AiMonitoringChatSubscriber {
|
|
|
56
56
|
* @param {Context} params.ctx active context
|
|
57
57
|
* @param {object} params.request request made to method
|
|
58
58
|
* @param {object} params.response response from method
|
|
59
|
+
* @param {number} [params.timeOfFirstToken] Timestamp of when the first streaming token was sent.
|
|
59
60
|
* @param {object} [params.err] error object if present
|
|
60
61
|
* @returns {object} a llm completion summary instance for Google Gen AI
|
|
61
62
|
*/
|
|
62
|
-
createCompletionSummary({ ctx, request, response = {}, err }) {
|
|
63
|
+
createCompletionSummary({ ctx, request, response = {}, timeOfFirstToken, err }) {
|
|
63
64
|
const { transaction, segment } = ctx
|
|
64
65
|
return new LlmChatCompletionSummary({
|
|
65
66
|
agent: this.agent,
|
|
@@ -67,6 +68,7 @@ class GoogleGenAIGenerateContentSubscriber extends AiMonitoringChatSubscriber {
|
|
|
67
68
|
transaction,
|
|
68
69
|
request,
|
|
69
70
|
response,
|
|
71
|
+
timeOfFirstToken,
|
|
70
72
|
error: !!err
|
|
71
73
|
})
|
|
72
74
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
'@nestjs/core': [
|
|
8
|
+
{
|
|
9
|
+
path: './nestjs/instrumentation.js',
|
|
10
|
+
instrumentations: [
|
|
11
|
+
{
|
|
12
|
+
channelName: 'nr_unknown_error',
|
|
13
|
+
module: {
|
|
14
|
+
name: '@nestjs/core',
|
|
15
|
+
versionRange: '>=8.0.0',
|
|
16
|
+
filePath: 'exceptions/base-exception-filter.js'
|
|
17
|
+
},
|
|
18
|
+
functionQuery: {
|
|
19
|
+
className: 'BaseExceptionFilter',
|
|
20
|
+
methodName: 'handleUnknownError',
|
|
21
|
+
kind: 'Sync'
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
|
|
8
|
+
const Subscriber = require('../base.js')
|
|
9
|
+
|
|
10
|
+
module.exports = class BaseExceptionFilterSubscriber extends Subscriber {
|
|
11
|
+
constructor({ agent, logger, ...rest }) {
|
|
12
|
+
super({
|
|
13
|
+
agent,
|
|
14
|
+
logger,
|
|
15
|
+
packageName: '@nestjs/core',
|
|
16
|
+
channelName: 'nr_unknown_error',
|
|
17
|
+
...rest
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// eslint-disable-next-line sonarjs/no-invariant-returns
|
|
22
|
+
handler(data, ctx) {
|
|
23
|
+
const { arguments: args } = data
|
|
24
|
+
const { transaction } = ctx
|
|
25
|
+
const exception = args[0]
|
|
26
|
+
|
|
27
|
+
if (!transaction) {
|
|
28
|
+
this.logger.trace(
|
|
29
|
+
exception,
|
|
30
|
+
'Ignoring error handled by Nest.js exception filter: not in a transaction'
|
|
31
|
+
)
|
|
32
|
+
return ctx
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.logger.trace(exception, 'Captured error handled by Nest.js exception filter.')
|
|
36
|
+
this.agent.errors.add(transaction, exception)
|
|
37
|
+
return ctx
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
const BaseCmdQueueAddCmdSubsriber = require('../redis-client/add-command')
|
|
8
|
+
|
|
9
|
+
module.exports = class CmdQueueAddCmdSubscriber extends BaseCmdQueueAddCmdSubsriber {
|
|
10
|
+
constructor({ agent, logger }) {
|
|
11
|
+
super({ agent, logger, packageName: '@node-redis/client' })
|
|
12
|
+
}
|
|
13
|
+
}
|