newrelic 13.9.2 → 13.11.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 +66 -0
- package/lib/instrumentation/aws-sdk/v3/bedrock.js +2 -1
- package/lib/instrumentation/when/index.js +0 -1
- package/lib/llm-events/aws-bedrock/chat-completion-message.js +5 -1
- package/lib/llm-events/google-genai/chat-completion-message.js +5 -0
- package/lib/llm-events/langchain/chat-completion-message.js +16 -10
- package/lib/llm-events/openai/chat-completion-message.js +6 -1
- package/lib/llm-events/openai/chat-completion-summary.js +4 -2
- package/lib/metrics/names.js +5 -2
- package/lib/otel/setup.js +14 -10
- package/lib/otel/traces/span-processor.js +9 -2
- package/lib/shimmer.js +1 -1
- package/lib/spans/span-event.js +2 -0
- package/lib/subscribers/ai-monitoring/base.js +113 -0
- package/lib/subscribers/ai-monitoring/chat.js +117 -0
- package/lib/subscribers/ai-monitoring/embedding.js +72 -0
- package/lib/subscribers/ai-monitoring/index.js +14 -0
- package/lib/subscribers/base.js +32 -14
- package/lib/subscribers/dc-base.js +47 -6
- package/lib/subscribers/fastify/index.js +5 -2
- package/lib/subscribers/google-genai/embed-content.js +18 -44
- package/lib/subscribers/google-genai/generate-content-stream.js +16 -39
- package/lib/subscribers/google-genai/generate-content.js +40 -29
- package/lib/subscribers/ioredis/index.js +2 -12
- package/lib/subscribers/langchain/runnable-stream.js +33 -66
- package/lib/subscribers/langchain/runnable.js +73 -32
- package/lib/subscribers/langchain/tool-callback-manager.js +4 -3
- package/lib/subscribers/langchain/tool.js +9 -19
- package/lib/subscribers/langchain/vectorstore.js +6 -26
- package/lib/subscribers/mcp-sdk/client-request.js +6 -12
- package/lib/subscribers/middleware-wrapper.js +5 -1
- package/lib/subscribers/openai/chat.js +204 -44
- package/lib/subscribers/openai/client.js +20 -7
- package/lib/subscribers/openai/embeddings.js +22 -34
- package/lib/subscribers/pg/connect.js +6 -1
- package/lib/subscribers/pg/native-connect.js +1 -1
- package/lib/subscribers/pg/native-query.js +1 -1
- package/lib/subscribers/pg/query.js +6 -1
- package/lib/subscribers/record-supportability-metric.js +39 -0
- package/lib/subscribers/resolve-package-version.js +84 -0
- package/lib/subscribers/utils.js +38 -0
- package/lib/tracking-packages.js +1 -9
- package/lib/transaction/trace/partial-trace.js +62 -4
- package/lib/transaction/trace/segment.js +19 -1
- package/package.json +1 -1
- package/lib/subscribers/google-genai/base.js +0 -123
- package/lib/subscribers/langchain/base.js +0 -164
- package/lib/subscribers/openai/base.js +0 -46
- package/lib/subscribers/openai/utils.js +0 -358
package/NEWS.md
CHANGED
|
@@ -1,3 +1,68 @@
|
|
|
1
|
+
### v13.11.0 (2026-01-29)
|
|
2
|
+
|
|
3
|
+
#### Features
|
|
4
|
+
|
|
5
|
+
* Added `timestamp` to AIM `LlmChatCompletionMessage` messages
|
|
6
|
+
* Added `timestamp` to AWS Bedrock `LlmChatCompletionMessage` ([#3685](https://github.com/newrelic/node-newrelic/pull/3685)) ([ea297c8](https://github.com/newrelic/node-newrelic/commit/ea297c86772c1ae377a16e636db8bbcf53ef1547))
|
|
7
|
+
* Added `timestamp` to Google Gen AI `LlmChatCompletionMessage` ([#3686](https://github.com/newrelic/node-newrelic/pull/3686)) ([f4dd09e](https://github.com/newrelic/node-newrelic/commit/f4dd09ec332c47396277f6aaeff2f6f15daad97a))
|
|
8
|
+
* Added `timestamp` to LangChain input `LlmChatCompletionMessage` ([#3658](https://github.com/newrelic/node-newrelic/pull/3658)) ([0bce235](https://github.com/newrelic/node-newrelic/commit/0bce235ce02f772bc089bf947afadf312c735c1b))
|
|
9
|
+
* Added timestamp to OpenAI `LlmChatCompletionSummary` ([#3689](https://github.com/newrelic/node-newrelic/pull/3689)) ([55b0310](https://github.com/newrelic/node-newrelic/commit/55b03100323eefb3578816cff69f2b919e4b7bdd))
|
|
10
|
+
* Additional support for partial granularity traces (Not available for production use)
|
|
11
|
+
* Added essential tracing mode for span links ([#3670](https://github.com/newrelic/node-newrelic/pull/3670)) ([83dfb84](https://github.com/newrelic/node-newrelic/commit/83dfb84b842e0245392425ce61e92a2789fcaf06))
|
|
12
|
+
* Updated `Supportability/Nodejs/PartialGranularity/NrIds/Dropped` metric to only keep track of spans that got dropped when `nr.ids` execeeded 63 spans. ([#3684](https://github.com/newrelic/node-newrelic/pull/3684)) ([9797cf3](https://github.com/newrelic/node-newrelic/commit/9797cf38aa2808b29da9623158edb221b1819548))
|
|
13
|
+
|
|
14
|
+
#### Bug fixes
|
|
15
|
+
|
|
16
|
+
* Added defensive code in `lib/subscribers/middleware-wrapper.js` to prevent crash when attempting to associate an error on an incoming http request ([#3697](https://github.com/newrelic/node-newrelic/pull/3697)) ([658e78b](https://github.com/newrelic/node-newrelic/commit/658e78ba564eae996d790bb3334bda6847c7e2bd))
|
|
17
|
+
* Updated `pg` instrumentation to properly capture the TraceSegment duration for promise based queries ([#3695](https://github.com/newrelic/node-newrelic/pull/3695)) ([0051697](https://github.com/newrelic/node-newrelic/commit/005169776a3f3fe9d988174857b3c96e6131aea3))
|
|
18
|
+
|
|
19
|
+
#### Code refactoring
|
|
20
|
+
|
|
21
|
+
* Added AIM abstract subscribers and updated all AIM subscribers to use them ([#3676](https://github.com/newrelic/node-newrelic/pull/3676)) ([08cd38b](https://github.com/newrelic/node-newrelic/commit/08cd38b5b34b4f1a2636ccc12d359484af6b3e83))
|
|
22
|
+
|
|
23
|
+
#### Documentation
|
|
24
|
+
|
|
25
|
+
* Updated compatibility report ([#3675](https://github.com/newrelic/node-newrelic/pull/3675)) ([cc898f4](https://github.com/newrelic/node-newrelic/commit/cc898f4d8b0f520e120f330ff9e90fe7baaebedc))
|
|
26
|
+
|
|
27
|
+
#### Miscellaneous chores
|
|
28
|
+
|
|
29
|
+
* Added TimedEvent limit of 100 ([#3683](https://github.com/newrelic/node-newrelic/pull/3683)) ([0d595d8](https://github.com/newrelic/node-newrelic/commit/0d595d8579b5a58e49908bc24321ce7b526ddff2))
|
|
30
|
+
* Added supportability metrics `Supportability/Nodejs/SpanEvent/<Links|Events>/Dropped` when limit of 100 is exceeded ([#3688](https://github.com/newrelic/node-newrelic/pull/3688)) ([a247d31](https://github.com/newrelic/node-newrelic/commit/a247d31fc9c24752be50a73d4ddfc7a088a68ce6))
|
|
31
|
+
* Removed eslint disable next line for `sonarjs/no-dead-store` ([#3692](https://github.com/newrelic/node-newrelic/pull/3692)) ([593fa15](https://github.com/newrelic/node-newrelic/commit/593fa15a033437b95fe375cf6034b8453c7d2b36))
|
|
32
|
+
|
|
33
|
+
#### Continuous Integration
|
|
34
|
+
* Fixed CI issue when sec-agent present ([#3677](https://github.com/newrelic/node-newrelic/pull/3677)) ([23da654](https://github.com/newrelic/node-newrelic/commit/23da6547e72f49bf798046330e8cfa74a0b4d2af))
|
|
35
|
+
|
|
36
|
+
#### Tests
|
|
37
|
+
|
|
38
|
+
* Assert that span events drop in partial traces ([#3680](https://github.com/newrelic/node-newrelic/pull/3680)) ([25e3781](https://github.com/newrelic/node-newrelic/commit/25e3781098343b9ca9e4aed0cd5c9e0f94266926))
|
|
39
|
+
|
|
40
|
+
### v13.10.0 (2026-01-20)
|
|
41
|
+
|
|
42
|
+
#### Features
|
|
43
|
+
|
|
44
|
+
* Add `timestamp` to OpenAI input `LlmChatCompletionMessage`s ([#3657](https://github.com/newrelic/node-newrelic/pull/3657)) ([099e20e](https://github.com/newrelic/node-newrelic/commit/099e20eaa380615b7e8aae7eaa12e2754a931041))
|
|
45
|
+
* Added reduced tracing mode for span links ([#3649](https://github.com/newrelic/node-newrelic/pull/3649)) ([2414331](https://github.com/newrelic/node-newrelic/commit/24143313e2d894a5e11e42116f33ef4be18f2fb4))
|
|
46
|
+
|
|
47
|
+
#### Bug fixes
|
|
48
|
+
|
|
49
|
+
* Updated OpenAI instrumentation to properly parse headers when a call fails ([#3665](https://github.com/newrelic/node-newrelic/pull/3665)) ([641b7a8](https://github.com/newrelic/node-newrelic/commit/641b7a8c8015855d82574e8173cb6e9fd082b4a0))
|
|
50
|
+
|
|
51
|
+
#### Documentation
|
|
52
|
+
|
|
53
|
+
* Updated compatibility report ([#3669](https://github.com/newrelic/node-newrelic/pull/3669)) ([ec922ae](https://github.com/newrelic/node-newrelic/commit/ec922ae6b0cecfa4ecbce86a789c308d6876ecd7)) ([#3668](https://github.com/newrelic/node-newrelic/pull/3668)) ([85c601f](https://github.com/newrelic/node-newrelic/commit/85c601fe2eaaacd82c0abac81eba96ce6718f2f1)) ([#3663](https://github.com/newrelic/node-newrelic/pull/3663)) ([82b3ac8](https://github.com/newrelic/node-newrelic/commit/82b3ac8cf6f69d0609b7fbaaf8be2a35f6552485))
|
|
54
|
+
|
|
55
|
+
#### Miscellaneous chores
|
|
56
|
+
|
|
57
|
+
* Lazy load OTEL dependencies ([#3667](https://github.com/newrelic/node-newrelic/pull/3667)) ([0b0c764](https://github.com/newrelic/node-newrelic/commit/0b0c7647f3f0bc3093b9ffbf7596c1654ba6113d))
|
|
58
|
+
* Record subscriber usage metric ([#3626](https://github.com/newrelic/node-newrelic/pull/3626)) ([5796574](https://github.com/newrelic/node-newrelic/commit/57965740acd053e6c99a88d861a876bcf2601c07))
|
|
59
|
+
* Update `ai-support.json` ([#3666](https://github.com/newrelic/node-newrelic/pull/3666)) ([562b403](https://github.com/newrelic/node-newrelic/commit/562b403a3414430cdd0afab261d3d119d3d9b4db))
|
|
60
|
+
* Updated OpenAI instrumentation to skip creating a `LlmChatCompletionMessage` for an outgoing tool call response ([#3655](https://github.com/newrelic/node-newrelic/pull/3655)) ([572c3a1](https://github.com/newrelic/node-newrelic/commit/572c3a1741ebd13d77d857c262b050aeb0c801fc))
|
|
61
|
+
|
|
62
|
+
#### Tests
|
|
63
|
+
|
|
64
|
+
* Updated mock response to avoid retries on OpenAI LangChain vectorstore calls ([#3664](https://github.com/newrelic/node-newrelic/pull/3664)) ([bc1faf3](https://github.com/newrelic/node-newrelic/commit/bc1faf30654d920e31ad4ddc205560e56b6c796c))
|
|
65
|
+
|
|
1
66
|
### v13.9.2 (2026-01-14)
|
|
2
67
|
|
|
3
68
|
#### Bug fixes
|
|
@@ -8233,3 +8298,4 @@ Special thanks to Ryan Copley (@RyanCopley) for the contribution.
|
|
|
8233
8298
|
|
|
8234
8299
|
|
|
8235
8300
|
|
|
8301
|
+
|
|
@@ -75,9 +75,10 @@ function isStreamingEnabled({ commandName, config }) {
|
|
|
75
75
|
function recordEvent({ agent, type, msg }) {
|
|
76
76
|
msg.serialize()
|
|
77
77
|
const llmContext = extractLlmContext(agent)
|
|
78
|
+
const timestamp = msg?.timestamp ?? Date.now()
|
|
78
79
|
|
|
79
80
|
agent.customEventAggregator.add([
|
|
80
|
-
{ type, timestamp
|
|
81
|
+
{ type, timestamp },
|
|
81
82
|
Object.assign({}, msg, llmContext)
|
|
82
83
|
])
|
|
83
84
|
}
|
|
@@ -296,7 +296,6 @@ module.exports = function initialize(shim, when) {
|
|
|
296
296
|
ret = agent.tracer.bindFunction(fn, context, true).apply(this, arguments)
|
|
297
297
|
} finally {
|
|
298
298
|
if (ret && typeof ret.then === 'function') {
|
|
299
|
-
// eslint-disable-next-line sonarjs/no-dead-store
|
|
300
299
|
ret = ctx.next[symbols.context].continue(ret)
|
|
301
300
|
}
|
|
302
301
|
}
|
|
@@ -38,7 +38,7 @@ class LlmChatCompletionMessage extends LlmEvent {
|
|
|
38
38
|
params = Object.assign({}, defaultParams, params)
|
|
39
39
|
super(params)
|
|
40
40
|
|
|
41
|
-
const { agent, content, isResponse, index, completionId, role } = params
|
|
41
|
+
const { agent, content, isResponse, index, completionId, role, segment } = params
|
|
42
42
|
const recordContent = agent.config?.ai_monitoring?.record_content?.enabled
|
|
43
43
|
|
|
44
44
|
this.is_response = isResponse
|
|
@@ -46,6 +46,10 @@ class LlmChatCompletionMessage extends LlmEvent {
|
|
|
46
46
|
this.sequence = index
|
|
47
47
|
this.content = recordContent === true ? content : undefined
|
|
48
48
|
this.role = role
|
|
49
|
+
if (this.is_response === false) {
|
|
50
|
+
// Only record for request/input messages
|
|
51
|
+
this.timestamp = segment.timer.start
|
|
52
|
+
}
|
|
49
53
|
|
|
50
54
|
this.#setId(index)
|
|
51
55
|
this.setTokenCount(agent)
|
|
@@ -33,6 +33,11 @@ module.exports = class LlmChatCompletionMessage extends LlmEvent {
|
|
|
33
33
|
this.content = this.is_response ? message?.parts?.[0]?.text : message
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// only add timestamp for request/input messages
|
|
37
|
+
if (this.is_response === false) {
|
|
38
|
+
this.timestamp = segment.timer.start
|
|
39
|
+
}
|
|
40
|
+
|
|
36
41
|
this.setTokenCount(agent, request, response)
|
|
37
42
|
}
|
|
38
43
|
|
|
@@ -38,19 +38,25 @@ class LangChainCompletionMessage extends LangChainEvent {
|
|
|
38
38
|
constructor(params = defaultParams) {
|
|
39
39
|
params = Object.assign({}, defaultParams, params)
|
|
40
40
|
super(params)
|
|
41
|
-
const { agent } = params
|
|
41
|
+
const { agent, runId, segment, sequence, role, content, completionId, isResponse } = params
|
|
42
42
|
|
|
43
|
-
if (
|
|
44
|
-
this.id = `${
|
|
43
|
+
if (runId) {
|
|
44
|
+
this.id = `${runId}-${sequence}`
|
|
45
45
|
} else {
|
|
46
|
-
this.id = `${this.id}-${
|
|
46
|
+
this.id = `${this.id}-${sequence}`
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
this.sequence =
|
|
50
|
-
this.completion_id =
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
this.sequence = sequence
|
|
50
|
+
this.completion_id = completionId
|
|
51
|
+
|
|
52
|
+
this.is_response = isResponse ?? false
|
|
53
|
+
if (this.is_response === false) {
|
|
54
|
+
// `timestamp` is only required for input/request messages.
|
|
55
|
+
this.timestamp = segment.timer.start
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (role) {
|
|
59
|
+
this.role = role
|
|
54
60
|
} else {
|
|
55
61
|
// As a backup, we can infer the role based on if it
|
|
56
62
|
// is a response or not.
|
|
@@ -58,7 +64,7 @@ class LangChainCompletionMessage extends LangChainEvent {
|
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
if (agent.config.ai_monitoring.record_content.enabled === true) {
|
|
61
|
-
this.content =
|
|
67
|
+
this.content = content
|
|
62
68
|
}
|
|
63
69
|
}
|
|
64
70
|
}
|
|
@@ -26,13 +26,18 @@ module.exports = class LlmChatCompletionMessage extends LlmEvent {
|
|
|
26
26
|
// Check if the given message is from the response.
|
|
27
27
|
// The response object differs based on the API called.
|
|
28
28
|
// If it's `responses.create`, we check against `response.output`.
|
|
29
|
-
// If it's `chat.completions.create
|
|
29
|
+
// If it's `chat.completions.create`, we check against `response.choices`.
|
|
30
30
|
if (response?.object === 'response') {
|
|
31
31
|
this.is_response = message.content === response?.output?.[0]?.content?.[0]?.text
|
|
32
32
|
} else {
|
|
33
33
|
this.is_response = message.content === response?.choices?.[0]?.message?.content
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// Spec 771: `timestamp` is only required for input/request messages.
|
|
37
|
+
if (this.is_response === false) {
|
|
38
|
+
this.timestamp = segment.timer.start
|
|
39
|
+
}
|
|
40
|
+
|
|
36
41
|
// Assign content to the event.
|
|
37
42
|
const content = message?.content
|
|
38
43
|
if (agent.config.ai_monitoring.record_content.enabled === true) {
|
|
@@ -14,17 +14,19 @@ module.exports = class LlmChatCompletionSummary extends LlmEvent {
|
|
|
14
14
|
this['request.temperature'] = request.temperature
|
|
15
15
|
|
|
16
16
|
if (request?.input) {
|
|
17
|
-
// `responses.create` logic
|
|
17
|
+
// `responses.create` api logic
|
|
18
18
|
// `request.input` can be an array or a string.
|
|
19
19
|
const requestLength = Array.isArray(request?.input) ? request.input.length : 1
|
|
20
20
|
this['response.number_of_messages'] = requestLength + (response?.output?.length ?? 0)
|
|
21
21
|
this['response.choices.finish_reason'] = response?.status
|
|
22
22
|
} else {
|
|
23
|
-
// `chat.completions.create` logic
|
|
23
|
+
// `chat.completions.create` api logic
|
|
24
24
|
this['response.number_of_messages'] = request?.messages?.length + response?.choices?.length
|
|
25
25
|
this['response.choices.finish_reason'] = response?.choices?.[0]?.finish_reason
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
this.timestamp = segment.timer.start
|
|
29
|
+
|
|
28
30
|
this.setTokens(agent, request, response)
|
|
29
31
|
}
|
|
30
32
|
|
package/lib/metrics/names.js
CHANGED
|
@@ -325,7 +325,9 @@ const SPAN_EVENTS = {
|
|
|
325
325
|
SEEN: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'TotalEventsSeen',
|
|
326
326
|
SENT: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'TotalEventsSent',
|
|
327
327
|
DROPPED: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'Discarded',
|
|
328
|
-
LIMIT: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'Limit'
|
|
328
|
+
LIMIT: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'Limit',
|
|
329
|
+
LINKS_DROPPED: SUPPORTABILITY.NODEJS + '/' + SPAN_EVENT_PREFIX + 'Links/Dropped',
|
|
330
|
+
EVENTS_DROPPED: SUPPORTABILITY.NODEJS + '/' + SPAN_EVENT_PREFIX + 'Events/Dropped'
|
|
329
331
|
}
|
|
330
332
|
|
|
331
333
|
const INFINITE_TRACING = {
|
|
@@ -353,7 +355,8 @@ const FEATURES = {
|
|
|
353
355
|
SOURCE_MAPS: `${SUPPORTABILITY.FEATURES}/EnableSourceMaps`,
|
|
354
356
|
CERTIFICATES: SUPPORTABILITY.FEATURES + '/Certificates',
|
|
355
357
|
INSTRUMENTATION: {
|
|
356
|
-
ON_REQUIRE: SUPPORTABILITY.FEATURES + '/Instrumentation/OnRequire'
|
|
358
|
+
ON_REQUIRE: SUPPORTABILITY.FEATURES + '/Instrumentation/OnRequire',
|
|
359
|
+
SUBSCRIBER_USED: SUPPORTABILITY.FEATURES + '/Instrumentation/SubscriberUsed'
|
|
357
360
|
}
|
|
358
361
|
}
|
|
359
362
|
|
package/lib/otel/setup.js
CHANGED
|
@@ -5,16 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict'
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const SetupTraces = require('./traces/index.js')
|
|
13
|
-
const ContextManager = require('./context-manager')
|
|
14
|
-
const TracePropagator = require('./trace-propagator')
|
|
8
|
+
// The majority of dependencies in this module are lazy loaded in order to
|
|
9
|
+
// limit the impact on memory usage. In general, the OTEL dependencies are
|
|
10
|
+
// only loaded when they are needed, because they can have a significant
|
|
11
|
+
// impact once loaded.
|
|
15
12
|
const defaultLogger = require('../logger').child({ component: 'opentelemetry-bridge' })
|
|
16
|
-
const createOtelLogger = require('./logger')
|
|
17
|
-
const interceptSpanKey = require('./span-key-interceptor')
|
|
18
13
|
|
|
19
14
|
const signals = []
|
|
20
15
|
|
|
@@ -26,6 +21,12 @@ function setupOtel(agent, logger = defaultLogger) {
|
|
|
26
21
|
return
|
|
27
22
|
}
|
|
28
23
|
|
|
24
|
+
const opentelemetry = require('@opentelemetry/api')
|
|
25
|
+
const ContextManager = require('./context-manager')
|
|
26
|
+
const TracePropagator = require('./trace-propagator')
|
|
27
|
+
const createOtelLogger = require('./logger')
|
|
28
|
+
const interceptSpanKey = require('./span-key-interceptor')
|
|
29
|
+
|
|
29
30
|
// When bridge mode is enabled, our context manager must utilize the OTEL
|
|
30
31
|
// context manager. Otherwise, traces within, e.g. logs, will not be
|
|
31
32
|
// captured correctly. The internal OTEL logger also must be configured
|
|
@@ -37,6 +38,7 @@ function setupOtel(agent, logger = defaultLogger) {
|
|
|
37
38
|
opentelemetry.propagation.setGlobalPropagator(new TracePropagator(agent))
|
|
38
39
|
|
|
39
40
|
if (agent.config.opentelemetry.traces.enabled === true) {
|
|
41
|
+
const SetupTraces = require('./traces/index.js')
|
|
40
42
|
const signal = new SetupTraces({ agent })
|
|
41
43
|
signals.push(signal)
|
|
42
44
|
} else {
|
|
@@ -47,6 +49,7 @@ function setupOtel(agent, logger = defaultLogger) {
|
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
if (agent.config.opentelemetry.metrics.enabled === true) {
|
|
52
|
+
const SetupMetrics = require('./metrics/index.js')
|
|
50
53
|
const signal = new SetupMetrics({ agent })
|
|
51
54
|
signals.push(signal)
|
|
52
55
|
} else {
|
|
@@ -57,6 +60,7 @@ function setupOtel(agent, logger = defaultLogger) {
|
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
if (agent.config.opentelemetry.logs.enabled === true) {
|
|
63
|
+
const SetupLogs = require('./logs/index.js')
|
|
60
64
|
const signal = new SetupLogs({ agent })
|
|
61
65
|
signals.push(signal)
|
|
62
66
|
} else {
|
|
@@ -68,7 +72,7 @@ function setupOtel(agent, logger = defaultLogger) {
|
|
|
68
72
|
.incrementCallCount()
|
|
69
73
|
}
|
|
70
74
|
|
|
71
|
-
function teardownOtel(agent) {
|
|
75
|
+
function teardownOtel(agent, { opentelemetry = require('@opentelemetry/api') } = {}) {
|
|
72
76
|
if (agent?.config?.opentelemetry?.enabled !== true) {
|
|
73
77
|
return
|
|
74
78
|
}
|
|
@@ -23,6 +23,7 @@ const {
|
|
|
23
23
|
transformTemplate
|
|
24
24
|
} = require('./utils.js')
|
|
25
25
|
const defaultLogger = require('#agentlib/logger.js').child({ component: 'span-processor' })
|
|
26
|
+
const { SPAN_EVENTS } = require('#agentlib/metrics/names.js')
|
|
26
27
|
|
|
27
28
|
const {
|
|
28
29
|
SPAN_STATUS_CODE
|
|
@@ -155,7 +156,10 @@ module.exports = class NrSpanProcessor {
|
|
|
155
156
|
event: span.events[i],
|
|
156
157
|
spanContext
|
|
157
158
|
})
|
|
158
|
-
segment.addTimedEvent(event)
|
|
159
|
+
const added = segment.addTimedEvent(event)
|
|
160
|
+
if (!added) {
|
|
161
|
+
this.agent.metrics.getOrCreateMetric(SPAN_EVENTS.EVENTS_DROPPED).incrementCallCount()
|
|
162
|
+
}
|
|
159
163
|
}
|
|
160
164
|
}
|
|
161
165
|
|
|
@@ -181,7 +185,10 @@ module.exports = class NrSpanProcessor {
|
|
|
181
185
|
spanContext,
|
|
182
186
|
timestamp
|
|
183
187
|
})
|
|
184
|
-
segment.addSpanLink(link)
|
|
188
|
+
const added = segment.addSpanLink(link)
|
|
189
|
+
if (!added) {
|
|
190
|
+
this.agent.metrics.getOrCreateMetric(SPAN_EVENTS.LINKS_DROPPED).incrementCallCount()
|
|
191
|
+
}
|
|
185
192
|
}
|
|
186
193
|
}
|
|
187
194
|
|
package/lib/shimmer.js
CHANGED
|
@@ -306,7 +306,7 @@ const shimmer = (module.exports = {
|
|
|
306
306
|
registerHooks(agent) {
|
|
307
307
|
// add the packages from the subscriber based instrumentation
|
|
308
308
|
// this is only added to add tracking metrics
|
|
309
|
-
|
|
309
|
+
Array.prototype.push.apply(pkgsToHook, trackingPkgs)
|
|
310
310
|
this._ritm = new Hook(pkgsToHook, function onHook(exports, name, basedir) {
|
|
311
311
|
return _postLoad(agent, exports, name, basedir)
|
|
312
312
|
})
|
package/lib/spans/span-event.js
CHANGED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
const Subscriber = require('../base')
|
|
8
|
+
const { DESTINATIONS } = require('#agentlib/config/attribute-filter.js')
|
|
9
|
+
const { extractLlmContext } = require('#agentlib/util/llm-utils.js')
|
|
10
|
+
|
|
11
|
+
class AiMonitoringSubscriber extends Subscriber {
|
|
12
|
+
/**
|
|
13
|
+
* @param {object} params constructor params object
|
|
14
|
+
* @param {object} params.agent A New Relic Node.js agent instance.
|
|
15
|
+
* @param {object} params.logger An agent logger instance.
|
|
16
|
+
* @param {string} params.packageName The package name being instrumented.
|
|
17
|
+
* This is what a developer would provide to the `require` function.
|
|
18
|
+
* @param {string} params.channelName A unique name for the diagnostics channel
|
|
19
|
+
* that will be created and monitored.
|
|
20
|
+
* @param {string} params.name name of segment for a given subscriber
|
|
21
|
+
* @param {string} params.trackingPrefix prefix for the tracking metric for a given subscriber
|
|
22
|
+
*/
|
|
23
|
+
constructor({ agent, logger, packageName, channelName, name, trackingPrefix }) {
|
|
24
|
+
super({ agent, logger, packageName, channelName })
|
|
25
|
+
this.name = name
|
|
26
|
+
this.trackingPrefix = trackingPrefix
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get enabled() {
|
|
30
|
+
return super.enabled && this.agent.config.ai_monitoring.enabled === true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
set name(name) {
|
|
34
|
+
if (!name) {
|
|
35
|
+
throw new Error('subscriber must pass in `name`')
|
|
36
|
+
}
|
|
37
|
+
this._name = name
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get name() {
|
|
41
|
+
return this._name
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
set trackingPrefix(prefix) {
|
|
45
|
+
if (!prefix) {
|
|
46
|
+
throw new Error('subscriber must pass in `trackingPrefix`')
|
|
47
|
+
}
|
|
48
|
+
this._prefix = prefix
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get trackingPrefix() {
|
|
52
|
+
return this._prefix
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Increments the tracking metric and sets the llm attribute on transactions
|
|
57
|
+
*
|
|
58
|
+
* @param {object} params input params
|
|
59
|
+
* @param {Transaction} params.ctx active context
|
|
60
|
+
* @param {string} params.version package version
|
|
61
|
+
*/
|
|
62
|
+
addLlmMeta({ ctx, version }) {
|
|
63
|
+
if (!ctx?.transaction) {
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const transaction = ctx.transaction
|
|
68
|
+
const trackingMetric = `${this.trackingPrefix}/${version}`
|
|
69
|
+
this.agent.metrics.getOrCreateMetric(trackingMetric).incrementCallCount()
|
|
70
|
+
transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_EVENT, 'llm', true)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Enqueues a LLM event to the custom event aggregator
|
|
75
|
+
*
|
|
76
|
+
* @param {object} params input params
|
|
77
|
+
* @param {string} params.type LLM event type
|
|
78
|
+
* @param {object} params.msg LLM event
|
|
79
|
+
*/
|
|
80
|
+
recordEvent({ type, msg }) {
|
|
81
|
+
const llmContext = extractLlmContext(this.agent)
|
|
82
|
+
// prefer timestamp on event if present, otherwise use `Date.now`
|
|
83
|
+
const timestamp = msg?.timestamp ?? Date.now()
|
|
84
|
+
this.agent.customEventAggregator.add([
|
|
85
|
+
{ type, timestamp },
|
|
86
|
+
Object.assign({}, msg, llmContext)
|
|
87
|
+
])
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Defines the common handler for all AIM calls.
|
|
92
|
+
* All of these calls simply create a segment with a name
|
|
93
|
+
* The `this.name` must be defined in the inherited subscriber
|
|
94
|
+
* @param {object} data event passed to handler
|
|
95
|
+
* @param {Context} ctx context passed to handler
|
|
96
|
+
* @returns {Context} either new context or existing
|
|
97
|
+
*/
|
|
98
|
+
handler(data, ctx) {
|
|
99
|
+
if (!this.enabled) {
|
|
100
|
+
this.logger.debug('`ai_monitoring.enabled` is set to false, not creating segment.')
|
|
101
|
+
return ctx
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.addLlmMeta({ ctx, version: data.moduleVersion })
|
|
105
|
+
|
|
106
|
+
return this.createSegment({
|
|
107
|
+
name: this.name,
|
|
108
|
+
ctx
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = AiMonitoringSubscriber
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
const AiMonitoringSubscriber = require('./base')
|
|
8
|
+
const LlmErrorMessage = require('#agentlib/llm-events/error-message.js')
|
|
9
|
+
|
|
10
|
+
class AiMonitoringChatSubscriber extends AiMonitoringSubscriber {
|
|
11
|
+
/**
|
|
12
|
+
* @param {object} params constructor params object
|
|
13
|
+
* @param {object} params.agent A New Relic Node.js agent instance.
|
|
14
|
+
* @param {object} params.logger An agent logger instance.
|
|
15
|
+
* @param {string} params.packageName The package name being instrumented.
|
|
16
|
+
* This is what a developer would provide to the `require` function.
|
|
17
|
+
* @param {string} params.channelName A unique name for the diagnostics channel
|
|
18
|
+
* that will be created and monitored.
|
|
19
|
+
* @param {string} params.name name of segment for a given subscriber
|
|
20
|
+
* @param {string} params.trackingPrefix prefix for the tracking metric for a given subscriber
|
|
21
|
+
*/
|
|
22
|
+
constructor({ agent, logger, packageName, channelName, name, trackingPrefix }) {
|
|
23
|
+
super({ agent, logger, packageName, channelName, name, trackingPrefix })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get streamingEnabled() {
|
|
27
|
+
return this.agent.config.ai_monitoring.streaming.enabled === true
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Function that must be implemented by inherited subscriber to create an llm completion message.
|
|
32
|
+
*
|
|
33
|
+
* @param {object} params to function
|
|
34
|
+
* @param {Context} params.ctx active context
|
|
35
|
+
* @param {object} params.request request made to method on a given llm library
|
|
36
|
+
* @param {object} params.response response from method on a given llm library
|
|
37
|
+
* @param {object|string} params.message the message object/string used to create llm completion message
|
|
38
|
+
* @param {string} params.completionId the id of the llm completion summary for a given conversation
|
|
39
|
+
* @param {number} params.index index of message for a given conversation
|
|
40
|
+
* returns {object} a llm completion message instance for the given LLM
|
|
41
|
+
*/
|
|
42
|
+
createCompletionMessage({ ctx, request, response, message, completionId, index }) {
|
|
43
|
+
throw new Error('createCompletionMessage must be implemented by your subscriber')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Function that must be implemented by inherited subscriber to create the llm completion summary.
|
|
48
|
+
*
|
|
49
|
+
* @param {object} params to function
|
|
50
|
+
* @param {Context} params.ctx active context
|
|
51
|
+
* @param {object} params.request request made to method on a given llm library
|
|
52
|
+
* @param {object} params.response response from method on a given llm library
|
|
53
|
+
* @param {object} params.err error if present
|
|
54
|
+
* @param {object} params.metadata used only for langchain events at the moment
|
|
55
|
+
* @param {Array} params.tags used only for langchain events at the moment
|
|
56
|
+
* returns {object} a llm completion summary instance for the given LLM
|
|
57
|
+
*/
|
|
58
|
+
createCompletionSummary({ ctx, request, response, err, metadata, tags }) {
|
|
59
|
+
throw new Error('createCompletionSummary must be implemented by your subscriber')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Function that must be implemented by inherited subscriber to retrieve the relevant
|
|
64
|
+
* messages for a given chat conversation.
|
|
65
|
+
*
|
|
66
|
+
* @param {object} params to function
|
|
67
|
+
* @param {object} params.request request to instrumented function
|
|
68
|
+
* @param {object} params.response response to instrumented function
|
|
69
|
+
* returns {Array} an array of relevant messages used to construct llm completion messages
|
|
70
|
+
*/
|
|
71
|
+
getMessages({ request, response }) {
|
|
72
|
+
throw new Error('getMessages must be implemented by your subscriber')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Function to create both llm completion summary and messages
|
|
77
|
+
* as well as assign errors to transaction if present
|
|
78
|
+
* @param {object} params to function
|
|
79
|
+
* @param {Context} params.ctx active context
|
|
80
|
+
* @param {object} params.request request made to method on a given llm library
|
|
81
|
+
* @param {object} params.response response from method on a given llm library
|
|
82
|
+
* @param {object} params.err error if present
|
|
83
|
+
* @param {object} params.metadata used only for langchain events at the moment
|
|
84
|
+
* @param {Array} params.tags used only for langchain events at the moment
|
|
85
|
+
*/
|
|
86
|
+
recordChatCompletionEvents({ ctx, request, response, err, metadata = {}, tags = [] }) {
|
|
87
|
+
if (!this.enabled) {
|
|
88
|
+
this.logger.debug('config.ai_monitoring.enabled is set to false, not creating chat completion events.')
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!(ctx?.segment || ctx?.transaction)) {
|
|
93
|
+
this.logger.debug('Empty context, not creating completion events.')
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Explicitly end segment to provide consistent duration
|
|
98
|
+
// for both LLM events and the segment
|
|
99
|
+
ctx.segment.end()
|
|
100
|
+
const completionSummary = this.createCompletionSummary({ ctx, request, response, err, metadata, tags })
|
|
101
|
+
this.recordEvent({ type: 'LlmChatCompletionSummary', msg: completionSummary })
|
|
102
|
+
|
|
103
|
+
const messages = this.getMessages({ request, response })
|
|
104
|
+
for (let i = 0; i < messages.length; i++) {
|
|
105
|
+
const message = messages[i]
|
|
106
|
+
const completionMessage = this.createCompletionMessage({ ctx, completionId: completionSummary.id, index: i, message, request, response })
|
|
107
|
+
this.recordEvent({ type: 'LlmChatCompletionMessage', msg: completionMessage })
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (err) {
|
|
111
|
+
const llmError = new LlmErrorMessage({ cause: err, summary: completionSummary, response })
|
|
112
|
+
this.agent.errors.add(ctx.transaction, err, llmError)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = AiMonitoringChatSubscriber
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
const AiMonitoringSubscriber = require('./base')
|
|
8
|
+
const LlmErrorMessage = require('#agentlib/llm-events/error-message.js')
|
|
9
|
+
|
|
10
|
+
class AiMonitoringEmbeddingSubscriber extends AiMonitoringSubscriber {
|
|
11
|
+
/**
|
|
12
|
+
* @param {object} params constructor params object
|
|
13
|
+
* @param {object} params.agent A New Relic Node.js agent instance.
|
|
14
|
+
* @param {object} params.logger An agent logger instance.
|
|
15
|
+
* @param {string} params.packageName The package name being instrumented.
|
|
16
|
+
* This is what a developer would provide to the `require` function.
|
|
17
|
+
* @param {string} params.channelName A unique name for the diagnostics channel
|
|
18
|
+
* that will be created and monitored.
|
|
19
|
+
* @param {string} params.name name of segment for a given subscriber
|
|
20
|
+
* @param {string} params.trackingPrefix prefix for the tracking metric for a given subscriber
|
|
21
|
+
*/
|
|
22
|
+
constructor({ agent, logger, packageName, channelName, name, trackingPrefix }) {
|
|
23
|
+
super({ agent, logger, packageName, channelName, name, trackingPrefix })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Function that must be implemented by inherited subscriber to create an llm embedding.
|
|
28
|
+
*
|
|
29
|
+
* @param {object} params to function
|
|
30
|
+
* @param {Context} params.ctx active context
|
|
31
|
+
* @param {object} params.request request made to method on a given llm library
|
|
32
|
+
* @param {object} params.response response from method on a given llm library
|
|
33
|
+
* @param {object} params.err error if present
|
|
34
|
+
* returns {object} a llm embedding instance for the given LLM
|
|
35
|
+
*/
|
|
36
|
+
createEmbedding({ ctx, request, response, err }) {
|
|
37
|
+
throw new Error('createEmbedding must be implemented by your subscriber')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Function to create embedding as well as assign errors to transaction if present
|
|
42
|
+
* @param {object} params to function
|
|
43
|
+
* @param {Context} params.ctx active context
|
|
44
|
+
* @param {object} params.request request made to method on a given llm library
|
|
45
|
+
* @param {object} params.response response from method on a given llm library
|
|
46
|
+
* @param {object} params.err error if present
|
|
47
|
+
*/
|
|
48
|
+
recordEmbedding({ ctx, request, response, err }) {
|
|
49
|
+
if (!this.enabled) {
|
|
50
|
+
this.logger.debug('config.ai_monitoring.enabled is set to false, not creating embedding event.')
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!(ctx?.segment || ctx?.transaction)) {
|
|
55
|
+
this.logger.debug('Empty context, not creating embedding event.')
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Explicitly end segment to provide consistent duration
|
|
60
|
+
// for both LLM events and the segment
|
|
61
|
+
ctx.segment.end()
|
|
62
|
+
const embedding = this.createEmbedding({ ctx, request, response, err })
|
|
63
|
+
this.recordEvent({ type: 'LlmEmbedding', msg: embedding })
|
|
64
|
+
|
|
65
|
+
if (err) {
|
|
66
|
+
const llmError = new LlmErrorMessage({ cause: err, embedding, response })
|
|
67
|
+
this.agent.errors.add(ctx.transaction, err, llmError)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = AiMonitoringEmbeddingSubscriber
|