newrelic 13.7.0 → 13.8.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 +28 -0
- package/lib/instrumentation/aws-sdk/v3/bedrock.js +16 -13
- package/lib/instrumentation/langchain/runnable.js +6 -1
- package/lib/instrumentations.js +0 -2
- package/lib/llm-events/aws-bedrock/index.js +1 -1
- package/lib/llm-events/error-message.js +32 -20
- package/lib/metrics/names.js +6 -1
- package/lib/samplers/always-on-sampler.js +1 -1
- package/lib/samplers/index.js +2 -1
- package/lib/samplers/ratio-based-sampler.js +0 -1
- package/lib/spans/span-event-aggregator.js +29 -8
- package/lib/spans/span-event.js +8 -2
- package/lib/spans/streaming-span-event-aggregator.js +4 -0
- package/lib/subscriber-configs.js +2 -0
- package/lib/subscribers/application-logs.js +5 -1
- package/lib/subscribers/bunyan/base.js +24 -0
- package/lib/subscribers/bunyan/config.js +35 -0
- package/lib/subscribers/bunyan/emit.js +31 -0
- package/lib/subscribers/bunyan/index.js +63 -0
- package/lib/subscribers/google-genai/base.js +123 -0
- package/lib/subscribers/google-genai/config.js +84 -0
- package/lib/subscribers/google-genai/embed-content.js +70 -0
- package/lib/subscribers/google-genai/generate-content-stream.js +125 -0
- package/lib/subscribers/google-genai/generate-content.js +55 -0
- package/lib/transaction/index.js +7 -0
- package/lib/transaction/trace/index.js +79 -2
- package/package.json +1 -1
- package/lib/instrumentation/@google/genai.js +0 -282
- package/lib/instrumentation/bunyan.js +0 -96
- package/lib/llm-events/aws-bedrock/error.js +0 -29
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
LlmChatCompletionMessage,
|
|
8
|
+
LlmChatCompletionSummary,
|
|
9
|
+
LlmErrorMessage
|
|
10
|
+
} = require('../../../lib/llm-events/google-genai')
|
|
11
|
+
const { extractLlmContext } = require('../../util/llm-utils')
|
|
12
|
+
const { AI } = require('../../../lib/metrics/names')
|
|
13
|
+
const { GEMINI } = AI
|
|
14
|
+
const { DESTINATIONS } = require('../../config/attribute-filter')
|
|
15
|
+
|
|
16
|
+
const Subscriber = require('../base')
|
|
17
|
+
|
|
18
|
+
class GoogleGenAISubscriber extends Subscriber {
|
|
19
|
+
constructor({ agent, logger, channelName, packageName = '@google/genai' }) {
|
|
20
|
+
super({ agent, logger, channelName, packageName })
|
|
21
|
+
this.events = ['asyncEnd']
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get enabled() {
|
|
25
|
+
return super.enabled && this.agent.config.ai_monitoring.enabled
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Enqueues a LLM event to the custom event aggregator
|
|
30
|
+
*
|
|
31
|
+
* @param {object} params input params
|
|
32
|
+
* @param {string} params.type LLM event type
|
|
33
|
+
* @param {object} params.msg LLM event
|
|
34
|
+
*/
|
|
35
|
+
recordEvent({ type, msg }) {
|
|
36
|
+
const llmContext = extractLlmContext(this.agent)
|
|
37
|
+
|
|
38
|
+
this.agent.customEventAggregator.add([
|
|
39
|
+
{ type, timestamp: Date.now() },
|
|
40
|
+
Object.assign({}, msg, llmContext)
|
|
41
|
+
])
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Generates LlmChatCompletionSummary for a chat completion creation.
|
|
46
|
+
* Also iterates over both input messages and the first response message
|
|
47
|
+
* and creates LlmChatCompletionMessage.
|
|
48
|
+
*
|
|
49
|
+
* Also assigns relevant ids by response id for LlmFeedbackEvent creation
|
|
50
|
+
*
|
|
51
|
+
* @param {object} params input params
|
|
52
|
+
* @param {TraceSegment} params.segment active segment from chat completion
|
|
53
|
+
* @param {object} params.request chat completion params
|
|
54
|
+
* @param {object} [params.response] chat completion response
|
|
55
|
+
* @param {boolean} [params.err] err if it exists
|
|
56
|
+
* @param {Transaction} params.transaction active transaction
|
|
57
|
+
*/
|
|
58
|
+
recordChatCompletionMessages({
|
|
59
|
+
segment,
|
|
60
|
+
request,
|
|
61
|
+
response = {},
|
|
62
|
+
err,
|
|
63
|
+
transaction
|
|
64
|
+
}) {
|
|
65
|
+
const agent = this.agent
|
|
66
|
+
|
|
67
|
+
// Explicitly end segment to provide consistent duration
|
|
68
|
+
// for both LLM events and the segment
|
|
69
|
+
segment.end()
|
|
70
|
+
const completionSummary = new LlmChatCompletionSummary({
|
|
71
|
+
agent,
|
|
72
|
+
segment,
|
|
73
|
+
transaction,
|
|
74
|
+
request,
|
|
75
|
+
response,
|
|
76
|
+
withError: err != null
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// Only take the first response message and append to input messages
|
|
80
|
+
// request.contents can be a string or an array of strings
|
|
81
|
+
// response.candidates is an array of candidates (choices); we only take the first one
|
|
82
|
+
const inputMessages = Array.isArray(request.contents) ? request.contents : [request.contents]
|
|
83
|
+
const responseMessage = response?.candidates?.[0]?.content
|
|
84
|
+
const messages = responseMessage !== undefined ? [...inputMessages, responseMessage] : inputMessages
|
|
85
|
+
for (let i = 0; i < messages.length; i++) {
|
|
86
|
+
const message = messages[i]
|
|
87
|
+
const completionMsg = new LlmChatCompletionMessage({
|
|
88
|
+
agent,
|
|
89
|
+
segment,
|
|
90
|
+
transaction,
|
|
91
|
+
request,
|
|
92
|
+
response,
|
|
93
|
+
index: i,
|
|
94
|
+
completionId: completionSummary.id,
|
|
95
|
+
message
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
this.recordEvent({ type: 'LlmChatCompletionMessage', msg: completionMsg })
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
this.recordEvent({ type: 'LlmChatCompletionSummary', msg: completionSummary })
|
|
102
|
+
|
|
103
|
+
if (err) {
|
|
104
|
+
const llmError = new LlmErrorMessage({ cause: err, summary: completionSummary, response })
|
|
105
|
+
agent.errors.add(transaction, err, llmError)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Increments the tracking metric and sets the llm attribute on transactions
|
|
111
|
+
*
|
|
112
|
+
* @param {object} params input params
|
|
113
|
+
* @param {Transaction} params.transaction active transaction
|
|
114
|
+
* @param {string} params.version package version
|
|
115
|
+
*/
|
|
116
|
+
addLlmMeta({ transaction, version }) {
|
|
117
|
+
const TRACKING_METRIC = `${GEMINI.TRACKING_PREFIX}/${version}`
|
|
118
|
+
this.agent.metrics.getOrCreateMetric(TRACKING_METRIC).incrementCallCount()
|
|
119
|
+
transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_EVENT, 'llm', true)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = GoogleGenAISubscriber
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const generateContentInternal = {
|
|
7
|
+
path: './google-genai/generate-content.js',
|
|
8
|
+
instrumentations: [
|
|
9
|
+
{
|
|
10
|
+
channelName: 'nr_generateContentInternal',
|
|
11
|
+
module: { name: '@google/genai', versionRange: '>=1.1.0', filePath: 'dist/node/index.cjs' },
|
|
12
|
+
functionQuery: {
|
|
13
|
+
className: 'Models',
|
|
14
|
+
methodName: 'generateContentInternal',
|
|
15
|
+
kind: 'Async'
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
channelName: 'nr_generateContentInternal',
|
|
20
|
+
module: { name: '@google/genai', versionRange: '>=1.1.0', filePath: 'dist/node/index.mjs' },
|
|
21
|
+
functionQuery: {
|
|
22
|
+
className: 'Models',
|
|
23
|
+
methodName: 'generateContentInternal',
|
|
24
|
+
kind: 'Async'
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const generateContentStreamInternal = {
|
|
31
|
+
path: './google-genai/generate-content-stream.js',
|
|
32
|
+
instrumentations: [
|
|
33
|
+
{
|
|
34
|
+
channelName: 'nr_generateContentStreamInternal',
|
|
35
|
+
module: { name: '@google/genai', versionRange: '>=1.1.0', filePath: 'dist/node/index.cjs' },
|
|
36
|
+
functionQuery: {
|
|
37
|
+
className: 'Models',
|
|
38
|
+
methodName: 'generateContentStreamInternal',
|
|
39
|
+
kind: 'Async'
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
channelName: 'nr_generateContentStreamInternal',
|
|
44
|
+
module: { name: '@google/genai', versionRange: '>=1.1.0', filePath: 'dist/node/index.mjs' },
|
|
45
|
+
functionQuery: {
|
|
46
|
+
className: 'Models',
|
|
47
|
+
methodName: 'generateContentStreamInternal',
|
|
48
|
+
kind: 'Async'
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const embedContent = {
|
|
55
|
+
path: './google-genai/embed-content.js',
|
|
56
|
+
instrumentations: [
|
|
57
|
+
{
|
|
58
|
+
channelName: 'nr_embedContent',
|
|
59
|
+
module: { name: '@google/genai', versionRange: '>=1.1.0', filePath: 'dist/node/index.cjs' },
|
|
60
|
+
functionQuery: {
|
|
61
|
+
className: 'Models',
|
|
62
|
+
methodName: 'embedContent',
|
|
63
|
+
kind: 'Async'
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
channelName: 'nr_embedContent',
|
|
68
|
+
module: { name: '@google/genai', versionRange: '>=1.1.0', filePath: 'dist/node/index.mjs' },
|
|
69
|
+
functionQuery: {
|
|
70
|
+
className: 'Models',
|
|
71
|
+
methodName: 'embedContent',
|
|
72
|
+
kind: 'Async'
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
'@google/genai': [
|
|
80
|
+
generateContentInternal,
|
|
81
|
+
generateContentStreamInternal,
|
|
82
|
+
embedContent
|
|
83
|
+
]
|
|
84
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const GoogleGenAISubscriber = require('./base')
|
|
7
|
+
const { AI } = require('../../../lib/metrics/names')
|
|
8
|
+
const { GEMINI } = AI
|
|
9
|
+
const {
|
|
10
|
+
LlmErrorMessage,
|
|
11
|
+
LlmEmbedding
|
|
12
|
+
} = require('../../../lib/llm-events/google-genai')
|
|
13
|
+
|
|
14
|
+
class GoogleGenAIEmbedContentSubscriber extends GoogleGenAISubscriber {
|
|
15
|
+
constructor ({ agent, logger }) {
|
|
16
|
+
super({ agent, logger, channelName: 'nr_embedContent' })
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
handler(data, ctx) {
|
|
20
|
+
if (!this.enabled) {
|
|
21
|
+
this.logger.debug('`ai_monitoring.enabled` is set to false, not creating segment.')
|
|
22
|
+
return ctx
|
|
23
|
+
}
|
|
24
|
+
const segment = this.agent.tracer.createSegment({
|
|
25
|
+
name: GEMINI.EMBEDDING,
|
|
26
|
+
parent: ctx.segment,
|
|
27
|
+
transaction: ctx.transaction
|
|
28
|
+
})
|
|
29
|
+
return ctx.enterSegment({ segment })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
asyncEnd(data) {
|
|
33
|
+
const agent = this.agent
|
|
34
|
+
if (!this.enabled) {
|
|
35
|
+
this.logger.debug('`ai_monitoring.enabled` is set to false, not recording Llm events.')
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
const ctx = agent.tracer.getContext()
|
|
39
|
+
if (!ctx?.segment || !ctx?.transaction) {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
this.addLlmMeta({ transaction: ctx.transaction, version: data.moduleVersion })
|
|
43
|
+
// If we get an error, it is possible that `response = null`.
|
|
44
|
+
// In that case, we define it to be an empty object.
|
|
45
|
+
const { result: response = {}, arguments: args, error: err } = data
|
|
46
|
+
const [request] = args
|
|
47
|
+
|
|
48
|
+
// Explicitly end segment to get consistent duration
|
|
49
|
+
// for both LLM events and the segment
|
|
50
|
+
ctx.segment.end()
|
|
51
|
+
|
|
52
|
+
const embedding = new LlmEmbedding({
|
|
53
|
+
agent,
|
|
54
|
+
segment: ctx.segment,
|
|
55
|
+
transaction: ctx.transaction,
|
|
56
|
+
request,
|
|
57
|
+
response,
|
|
58
|
+
withError: err != null
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
this.recordEvent({ type: 'LlmEmbedding', msg: embedding })
|
|
62
|
+
|
|
63
|
+
if (err) {
|
|
64
|
+
const llmError = new LlmErrorMessage({ cause: err, embedding, response })
|
|
65
|
+
agent.errors.add(ctx.transaction, err, llmError)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = GoogleGenAIEmbedContentSubscriber
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const GoogleGenAIGenerateContentSubscriber = require('./generate-content')
|
|
7
|
+
const { AI } = require('../../../lib/metrics/names')
|
|
8
|
+
|
|
9
|
+
class GoogleGenAIGenerateContentStreamSubscriber extends GoogleGenAIGenerateContentSubscriber {
|
|
10
|
+
constructor({ agent, logger }) {
|
|
11
|
+
super({ agent, logger, channelName: 'nr_generateContentStreamInternal' })
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get enabled() {
|
|
15
|
+
return super.enabled && this.streamingEnabled
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get streamingEnabled() {
|
|
19
|
+
return this.agent.config.ai_monitoring.streaming.enabled
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
handler(data, ctx) {
|
|
23
|
+
if (!this.enabled) {
|
|
24
|
+
this.logger.debug('`ai_monitoring.enabled` or `ai_monitoring.streaming.enabled` is set to false, not creating segment.')
|
|
25
|
+
return ctx
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return super.handler(data, ctx)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Instruments the streaming response by wrapping the next function.
|
|
33
|
+
* @param {object} params function params
|
|
34
|
+
* @param {object} params.request the original request object
|
|
35
|
+
* @param {object} params.response the original response object
|
|
36
|
+
* @param {TraceSegment} params.segment the active trace segment
|
|
37
|
+
* @param {Transaction} params.transaction the active transaction
|
|
38
|
+
*/
|
|
39
|
+
instrumentStream({ request, response, segment, transaction }) {
|
|
40
|
+
const self = this
|
|
41
|
+
const originalNext = response.next
|
|
42
|
+
let isDone = false
|
|
43
|
+
let cachedResult = {}
|
|
44
|
+
let err
|
|
45
|
+
let entireMessage = ''
|
|
46
|
+
response.next = async function wrappedNext(...nextArgs) {
|
|
47
|
+
let result = {}
|
|
48
|
+
try {
|
|
49
|
+
result = await originalNext.apply(response, nextArgs)
|
|
50
|
+
// When the stream is done we get {value: undefined, done: true}
|
|
51
|
+
// we need to cache the composed value and add the entire message
|
|
52
|
+
// back in later
|
|
53
|
+
if (result.done === true) {
|
|
54
|
+
isDone = true
|
|
55
|
+
} else {
|
|
56
|
+
cachedResult = result.value
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (result?.value?.text) {
|
|
60
|
+
entireMessage += result.value.text // readonly variable that equates to result.value.candidates[0].content.parts[0].text
|
|
61
|
+
}
|
|
62
|
+
} catch (streamErr) {
|
|
63
|
+
err = streamErr
|
|
64
|
+
throw err
|
|
65
|
+
} finally {
|
|
66
|
+
// Update segment duration since we want to extend the
|
|
67
|
+
// time it took to handle the stream
|
|
68
|
+
segment.touch()
|
|
69
|
+
|
|
70
|
+
// also need to enter this block if there was an
|
|
71
|
+
// error, so we can record it
|
|
72
|
+
if (isDone || err) {
|
|
73
|
+
if (cachedResult?.candidates?.[0]?.content?.parts) {
|
|
74
|
+
cachedResult.candidates[0].content.parts[0].text = entireMessage
|
|
75
|
+
}
|
|
76
|
+
self.recordChatCompletionMessages({
|
|
77
|
+
segment,
|
|
78
|
+
transaction,
|
|
79
|
+
request,
|
|
80
|
+
response: cachedResult,
|
|
81
|
+
err
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return result
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
asyncEnd(data) {
|
|
90
|
+
// Check config is ai_monitoring is still enabled
|
|
91
|
+
if (!this.streamingEnabled) {
|
|
92
|
+
this.logger.warn(
|
|
93
|
+
'`ai_monitoring.streaming.enabled` is set to `false`, stream will not be instrumented.'
|
|
94
|
+
)
|
|
95
|
+
this.agent.metrics.getOrCreateMetric(AI.STREAMING_DISABLED).incrementCallCount()
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
if (!this.enabled) {
|
|
99
|
+
this.logger.debug('`ai_monitoring.enabled` is set to false, not recording Llm events.')
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Instrument the stream
|
|
104
|
+
const ctx = this.agent.tracer.getContext()
|
|
105
|
+
if (!ctx?.segment || !ctx?.transaction) {
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
const { result: response, arguments: args } = data
|
|
109
|
+
const [request] = args
|
|
110
|
+
|
|
111
|
+
this.instrumentStream({
|
|
112
|
+
request,
|
|
113
|
+
response,
|
|
114
|
+
segment: ctx.segment,
|
|
115
|
+
transaction: ctx.transaction,
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
this.addLlmMeta({
|
|
119
|
+
transaction: ctx.transaction,
|
|
120
|
+
version: data.moduleVersion,
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = GoogleGenAIGenerateContentStreamSubscriber
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const GoogleGenAISubscriber = require('./base')
|
|
7
|
+
const { AI } = require('../../metrics/names')
|
|
8
|
+
const { GEMINI } = AI
|
|
9
|
+
|
|
10
|
+
class GoogleGenAIGenerateContentSubscriber extends GoogleGenAISubscriber {
|
|
11
|
+
constructor({ agent, logger, channelName = 'nr_generateContentInternal' }) {
|
|
12
|
+
super({ agent, logger, channelName })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
handler(data, ctx) {
|
|
16
|
+
if (!this.enabled) {
|
|
17
|
+
this.logger.debug('`ai_monitoring.enabled` is set to false, not creating segment.')
|
|
18
|
+
return ctx
|
|
19
|
+
}
|
|
20
|
+
const segment = this.agent.tracer.createSegment({
|
|
21
|
+
name: GEMINI.COMPLETION,
|
|
22
|
+
parent: ctx.segment,
|
|
23
|
+
transaction: ctx.transaction
|
|
24
|
+
})
|
|
25
|
+
return ctx.enterSegment({ segment })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
asyncEnd(data) {
|
|
29
|
+
if (!this.enabled) {
|
|
30
|
+
this.logger.debug('`ai_monitoring.enabled` is set to false, not recording Llm events.')
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
const ctx = this.agent.tracer.getContext()
|
|
34
|
+
if (!ctx?.segment || !ctx?.transaction) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
const { result: response, arguments: args, error: err } = data
|
|
38
|
+
const [request] = args
|
|
39
|
+
|
|
40
|
+
this.recordChatCompletionMessages({
|
|
41
|
+
segment: ctx.segment,
|
|
42
|
+
transaction: ctx.transaction,
|
|
43
|
+
request,
|
|
44
|
+
response,
|
|
45
|
+
err
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
this.addLlmMeta({
|
|
49
|
+
transaction: ctx.transaction,
|
|
50
|
+
version: data.moduleVersion
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = GoogleGenAIGenerateContentSubscriber
|
package/lib/transaction/index.js
CHANGED
|
@@ -67,6 +67,12 @@ const MULTIPLE_INSERT_MESSAGE =
|
|
|
67
67
|
'insertDistributedTraceHeaders called on headers object that already contains ' +
|
|
68
68
|
"distributed trace data. These may be overwritten. traceparent? '%s', newrelic? '%s'."
|
|
69
69
|
|
|
70
|
+
const PARTIAL_TYPES = {
|
|
71
|
+
REDUCED: 'reduced',
|
|
72
|
+
ESSENTIAL: 'essential',
|
|
73
|
+
COMPACT: 'compact'
|
|
74
|
+
}
|
|
75
|
+
|
|
70
76
|
/**
|
|
71
77
|
* Bundle together the metrics and the trace segment for a single agent
|
|
72
78
|
* transaction.
|
|
@@ -166,6 +172,7 @@ Transaction.TYPES_SET = TYPES_SET
|
|
|
166
172
|
Transaction.TRANSPORT_TYPES = TRANSPORT_TYPES
|
|
167
173
|
Transaction.TRANSPORT_TYPES_SET = TRANSPORT_TYPES_SET
|
|
168
174
|
Transaction.TRACE_CONTEXT_PARENT_HEADER = TRACE_CONTEXT_PARENT_HEADER
|
|
175
|
+
Transaction.PARTIAL_TYPES = PARTIAL_TYPES
|
|
169
176
|
|
|
170
177
|
/**
|
|
171
178
|
* Add a clear API method for determining whether a transaction is web or
|
|
@@ -63,6 +63,8 @@ function Trace(transaction) {
|
|
|
63
63
|
this.displayName = displayName
|
|
64
64
|
}
|
|
65
65
|
this.domain = null
|
|
66
|
+
this.spans = []
|
|
67
|
+
this.droppedSpans = new Map()
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
/**
|
|
@@ -79,11 +81,86 @@ Trace.prototype.end = function end(node = this.segments.root) {
|
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Adds a span to the trace. If the span is null, it indicates that the span was dropped,
|
|
86
|
+
* and we must keep track of its id and parentId for potential reparenting.
|
|
87
|
+
*
|
|
88
|
+
* @param {object} params to function
|
|
89
|
+
* @param {Span} [params.span] span to add, if present
|
|
90
|
+
* @param {string} params.id id of dropped span
|
|
91
|
+
* @param {string} params.parentId parentId of dropped span
|
|
92
|
+
*/
|
|
93
|
+
Trace.prototype.addSpan = function addSpan({ span, id, parentId }) {
|
|
94
|
+
// span was not dropped, add to trace until all spans have been processed
|
|
95
|
+
if (span) {
|
|
96
|
+
this.spans.push(span)
|
|
97
|
+
// span was dropped so keep track of its id and parent as any spans
|
|
98
|
+
// whose parent id was dropped needs to update to this new id
|
|
99
|
+
} else {
|
|
100
|
+
this.droppedSpans.set(id, parentId)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Iterates over dropped spans and reparents span if its current parent was dropped.
|
|
106
|
+
* It'll traverse until it finds a parent that wasn't dropped or there are no more parents to check.
|
|
107
|
+
*
|
|
108
|
+
* @param {Span} span the span to potentially reparent
|
|
109
|
+
*/
|
|
110
|
+
Trace.prototype.maybeReparentSpan = function maybeReparentSpan(span) {
|
|
111
|
+
let result = this.droppedSpans.get(span.parentId)
|
|
112
|
+
while (this.droppedSpans.has(result)) {
|
|
113
|
+
result = this.droppedSpans.get(result)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (result) {
|
|
117
|
+
logger.debug(`Reparenting span ${span.id} from parent ${span.parentId} to ${result}`)
|
|
118
|
+
span.addIntrinsicAttribute('parentId', result)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Finalizes span events for partial traces by reparenting spans)
|
|
124
|
+
* if their parent was dropped, associates `nr.ids` and `nr.duratiosn` intrinsics for
|
|
125
|
+
* partial traces of type `compact`. Lastly, adds the span events to the span event
|
|
126
|
+
* aggregator.
|
|
127
|
+
*
|
|
128
|
+
* Note: This is a no-op for full traces and traces using infinite tracing.
|
|
129
|
+
*/
|
|
130
|
+
Trace.prototype.finalizeSpanEvents = function finalizeSpanEvents() {
|
|
131
|
+
if (!this.transaction.partialType) {
|
|
132
|
+
logger.debug('Trace is full granularity, not finalizing span events.')
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (this.transaction.agent.spanEventAggregator.toString() === 'StreamingSpanEventAggregator') {
|
|
137
|
+
logger.debug('Infinite tracing is enabled, not finalizing span events.')
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
for (const span of this.spans) {
|
|
142
|
+
this.maybeReparentSpan(span)
|
|
143
|
+
this.transaction.agent.spanEventAggregator.add(span, this.transaction.priority)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.spans.length = 0
|
|
147
|
+
this.droppedSpans.clear()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Creates span events for all segments in the trace.
|
|
152
|
+
* Finalizes spans for partial traces by reparenting spans if their parent was dropped,
|
|
153
|
+
* and applying `compact` logic to assign`nr.ids` and `nr.durations` intrinsics.
|
|
154
|
+
*/
|
|
155
|
+
Trace.prototype.generateSpanEvents = function generateSpanEvents() {
|
|
156
|
+
this.generateSpans(this.segments.root)
|
|
157
|
+
this.finalizeSpanEvents()
|
|
158
|
+
}
|
|
82
159
|
/**
|
|
83
160
|
* Iterates over the trace tree and generates a span event for each segment.
|
|
84
161
|
* @param {Node} [node] the node to process the segment and its children
|
|
85
162
|
*/
|
|
86
|
-
Trace.prototype.
|
|
163
|
+
Trace.prototype.generateSpans = function generateSpans(node = this.segments.root) {
|
|
87
164
|
const config = this.transaction.agent.config
|
|
88
165
|
|
|
89
166
|
if (!shouldGenerateSpanEvents(config, this.transaction)) {
|
|
@@ -127,7 +204,7 @@ Trace.prototype.generateSpanEvents = function generateSpanEvents(node = this.seg
|
|
|
127
204
|
}
|
|
128
205
|
|
|
129
206
|
for (let i = 0; i < children.length; ++i) {
|
|
130
|
-
this.
|
|
207
|
+
this.generateSpans(children[i])
|
|
131
208
|
}
|
|
132
209
|
}
|
|
133
210
|
|