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
package/NEWS.md
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
### v13.8.0 (2025-12-11)
|
|
2
|
+
|
|
3
|
+
#### Features
|
|
4
|
+
|
|
5
|
+
* Added support for `@langchain/aws` ([#3563](https://github.com/newrelic/node-newrelic/pull/3563)) ([56c4a78](https://github.com/newrelic/node-newrelic/commit/56c4a782c7e05d94dc78ec0bd5eed19bfcc8a835))
|
|
6
|
+
* Additional support for partial granularity traces (Not available for production use)
|
|
7
|
+
* Added partial granularity tracking metrics ([#3566](https://github.com/newrelic/node-newrelic/pull/3566)) ([061f0fc](https://github.com/newrelic/node-newrelic/commit/061f0fca7e61b24fe08cb55538df4c4326c7dd69))
|
|
8
|
+
* updated `AlwaysOn` sampler to set priority to 3 for full trace and 2 for partial trace ([#3577](https://github.com/newrelic/node-newrelic/pull/3577)) ([e7bcff4](https://github.com/newrelic/node-newrelic/commit/e7bcff4364b5ea8a8af7a7ee58ff05e9e2edb977))
|
|
9
|
+
* Updated the span generation process ([#3585](https://github.com/newrelic/node-newrelic/pull/3585)) ([74777d5](https://github.com/newrelic/node-newrelic/commit/74777d5f7bfc182f5e36480a9bce1b67db13ce45))
|
|
10
|
+
|
|
11
|
+
#### Code refactoring
|
|
12
|
+
|
|
13
|
+
* Converted AWS Bedrock's `LlmError` to use `LlmErrorMessage` ([#3567](https://github.com/newrelic/node-newrelic/pull/3567)) ([8b2bb42](https://github.com/newrelic/node-newrelic/commit/8b2bb428eb3830b7cee4fa30e77a9562628fdd01))
|
|
14
|
+
* Updated `@google/genai` instrumentation to subscribe to events emitted ([#3467](https://github.com/newrelic/node-newrelic/pull/3467)) ([e8d9ba1](https://github.com/newrelic/node-newrelic/commit/e8d9ba188b4399604c6410b2fca406f8d7f338b5))
|
|
15
|
+
* Updated `bunyan` instrumentation to subscribe to events emitted ([#3589](https://github.com/newrelic/node-newrelic/pull/3589)) ([f1e5f12](https://github.com/newrelic/node-newrelic/commit/f1e5f129594a4c3acd8075604bae52013555cf52))
|
|
16
|
+
|
|
17
|
+
#### Documentation
|
|
18
|
+
|
|
19
|
+
* Updated compatibility report ([#3592](https://github.com/newrelic/node-newrelic/pull/3592)) ([7455533](https://github.com/newrelic/node-newrelic/commit/7455533e64d88233adf5aa0246261417049c2122))
|
|
20
|
+
|
|
21
|
+
#### Miscellaneous chores
|
|
22
|
+
|
|
23
|
+
* Refactor span link serialization ([#3574](https://github.com/newrelic/node-newrelic/pull/3574)) ([103044d](https://github.com/newrelic/node-newrelic/commit/103044d25a968649bdb0e39df054757163cd986f))
|
|
24
|
+
|
|
25
|
+
#### Tests
|
|
26
|
+
|
|
27
|
+
* Moved mock aws bedrock server creation to `test/lib/aws-server-stubs/index.js` ([#3593](https://github.com/newrelic/node-newrelic/pull/3593)) ([270df81](https://github.com/newrelic/node-newrelic/commit/270df8109a2adbcb3ff405030e2be7104bee6511))
|
|
28
|
+
|
|
1
29
|
### v13.7.0 (2025-12-08)
|
|
2
30
|
|
|
3
31
|
#### Features
|
|
@@ -8,7 +8,7 @@ const {
|
|
|
8
8
|
LlmChatCompletionMessage,
|
|
9
9
|
LlmChatCompletionSummary,
|
|
10
10
|
LlmEmbedding,
|
|
11
|
-
|
|
11
|
+
LlmErrorMessage,
|
|
12
12
|
BedrockCommand,
|
|
13
13
|
BedrockResponse,
|
|
14
14
|
StreamHandler
|
|
@@ -174,8 +174,13 @@ function recordChatCompletionMessages({
|
|
|
174
174
|
recordEvent({ agent, type: 'LlmChatCompletionSummary', msg: summary })
|
|
175
175
|
|
|
176
176
|
if (err) {
|
|
177
|
-
const
|
|
178
|
-
|
|
177
|
+
const llmErrorMessage = new LlmErrorMessage(
|
|
178
|
+
{ response: bedrockResponse,
|
|
179
|
+
cause: err,
|
|
180
|
+
summary,
|
|
181
|
+
useNameAsCode: true }
|
|
182
|
+
)
|
|
183
|
+
agent.errors.add(transaction, err, llmErrorMessage)
|
|
179
184
|
}
|
|
180
185
|
}
|
|
181
186
|
|
|
@@ -221,8 +226,13 @@ function recordEmbeddingMessage({
|
|
|
221
226
|
}
|
|
222
227
|
|
|
223
228
|
if (err) {
|
|
224
|
-
const
|
|
225
|
-
|
|
229
|
+
const llmErrorMessage = new LlmErrorMessage(
|
|
230
|
+
{ response: bedrockResponse,
|
|
231
|
+
cause: err,
|
|
232
|
+
embedding: embeddings.length === 1 ? embeddings[0] : undefined,
|
|
233
|
+
useNameAsCode: true }
|
|
234
|
+
)
|
|
235
|
+
agent.errors.add(transaction, err, llmErrorMessage)
|
|
226
236
|
}
|
|
227
237
|
}
|
|
228
238
|
|
|
@@ -293,14 +303,7 @@ function getBedrockSpec({ commandName }, shim, _original, _name, args) {
|
|
|
293
303
|
onComplete: handleResponse,
|
|
294
304
|
passThroughParams
|
|
295
305
|
})
|
|
296
|
-
response.output =
|
|
297
|
-
...response.output ?? {},
|
|
298
|
-
output: {
|
|
299
|
-
message: {
|
|
300
|
-
content: handler.generator(handleResponse)
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
306
|
+
response.output.stream = handler.generator(handleResponse)
|
|
304
307
|
} else {
|
|
305
308
|
const handler = new StreamHandler({
|
|
306
309
|
stream: response.output.body,
|
|
@@ -153,7 +153,12 @@ function wrapNextHandler({ shim, output, segment, transaction, request, metadata
|
|
|
153
153
|
shim
|
|
154
154
|
})
|
|
155
155
|
} else {
|
|
156
|
-
|
|
156
|
+
if (typeof result?.value?.content === 'string') {
|
|
157
|
+
// if result.value is a AIMessageChunk
|
|
158
|
+
content += result.value.content
|
|
159
|
+
} else {
|
|
160
|
+
content += result.value
|
|
161
|
+
}
|
|
157
162
|
}
|
|
158
163
|
return result
|
|
159
164
|
} catch (error) {
|
package/lib/instrumentations.js
CHANGED
|
@@ -11,7 +11,6 @@ const InstrumentationDescriptor = require('./instrumentation-descriptor')
|
|
|
11
11
|
module.exports = function instrumentations() {
|
|
12
12
|
return {
|
|
13
13
|
'@azure/functions': { type: InstrumentationDescriptor.TYPE_GENERIC },
|
|
14
|
-
'@google/genai': { type: InstrumentationDescriptor.TYPE_GENERIC },
|
|
15
14
|
'@grpc/grpc-js': { module: './instrumentation/grpc-js' },
|
|
16
15
|
'@hapi/hapi': { type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK },
|
|
17
16
|
'@hapi/vision': { type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK },
|
|
@@ -21,7 +20,6 @@ module.exports = function instrumentations() {
|
|
|
21
20
|
'@redis/client': { type: InstrumentationDescriptor.TYPE_DATASTORE },
|
|
22
21
|
'aws-sdk': { module: './instrumentation/aws-sdk' },
|
|
23
22
|
bluebird: { type: InstrumentationDescriptor.TYPE_PROMISE },
|
|
24
|
-
bunyan: { type: InstrumentationDescriptor.TYPE_GENERIC },
|
|
25
23
|
connect: { type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK },
|
|
26
24
|
fastify: { type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK },
|
|
27
25
|
'generic-pool': { type: InstrumentationDescriptor.TYPE_GENERIC },
|
|
@@ -12,7 +12,7 @@ module.exports = {
|
|
|
12
12
|
LlmChatCompletionSummary: require('./chat-completion-summary'),
|
|
13
13
|
LlmEmbedding: require('./embedding'),
|
|
14
14
|
LlmEvent: require('./event'),
|
|
15
|
-
|
|
15
|
+
LlmErrorMessage: require('../error-message'),
|
|
16
16
|
StreamHandler: require('./stream-handler'),
|
|
17
17
|
ConverseStreamHandler: require('./converse-stream-handler')
|
|
18
18
|
}
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
module.exports = class LlmErrorMessage {
|
|
13
13
|
/**
|
|
14
14
|
* @param {object} params Constructor parameters
|
|
15
|
-
* @param {object}
|
|
16
|
-
* @param {object}
|
|
15
|
+
* @param {object} params.response Instance of an incoming message.
|
|
16
|
+
* @param {object} params.cause An instance of the LLM error object.
|
|
17
17
|
* @param {LlmChatCompletionSummary} [params.summary] Details about the
|
|
18
18
|
* conversation if it was a chat completion conversation.
|
|
19
19
|
* @param {LlmEmbedding} [params.embedding] Details about the conversation
|
|
@@ -21,34 +21,46 @@ module.exports = class LlmErrorMessage {
|
|
|
21
21
|
* @param {LlmVectorStoreSearch} [params.vectorsearch] Details about the vector
|
|
22
22
|
* search if it was a vector search event.
|
|
23
23
|
* @param {LlmTool} [params.tool] Details about the tool event if it was a tool event.
|
|
24
|
+
* @param {boolean} [params.useNameAsCode] defaults to false, only Bedrock sets it to true so far
|
|
24
25
|
*/
|
|
25
|
-
constructor({ response, cause, summary, embedding, vectorsearch, tool } = {}) {
|
|
26
|
-
|
|
27
|
-
// but it does have `message` with the info we need. So, we need to parse
|
|
28
|
-
// the relevant fields from cause.message to get `status` and `error`.
|
|
29
|
-
let parsedError
|
|
30
|
-
const isGeminiVendor = embedding?.vendor === 'gemini' || summary?.vendor === 'gemini'
|
|
31
|
-
if (isGeminiVendor && cause?.message) {
|
|
32
|
-
try {
|
|
33
|
-
// Extract the JSON portion of the cause.message
|
|
34
|
-
const jsonStartIndex = cause.message.indexOf('{')
|
|
35
|
-
const jsonString = cause.message.substring(jsonStartIndex)
|
|
36
|
-
parsedError = JSON.parse(jsonString)?.error
|
|
37
|
-
} catch {
|
|
38
|
-
parsedError = undefined
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
this['http.statusCode'] = response?.status ?? cause?.status ?? parsedError?.code
|
|
26
|
+
constructor({ response, cause, summary = {}, embedding = {}, vectorsearch = {}, tool = {}, useNameAsCode = false } = {}) {
|
|
27
|
+
this['http.statusCode'] = response?.statusCode ?? response?.status ?? cause?.status
|
|
42
28
|
this['error.message'] = cause?.message
|
|
43
|
-
this['error.code'] = response?.code ?? cause?.error?.code
|
|
29
|
+
this['error.code'] = response?.code ?? cause?.error?.code
|
|
30
|
+
if (useNameAsCode) {
|
|
31
|
+
this['error.code'] = cause?.name
|
|
32
|
+
}
|
|
44
33
|
this['error.param'] = response?.param ?? cause?.error?.param
|
|
45
34
|
this.completion_id = summary?.id
|
|
46
35
|
this.embedding_id = embedding?.id
|
|
47
36
|
this.vector_store_id = vectorsearch?.id
|
|
48
37
|
this.tool_id = tool?.id
|
|
38
|
+
|
|
39
|
+
if (embedding?.vendor === 'gemini' || summary?.vendor === 'gemini') {
|
|
40
|
+
this._handleGemini(cause)
|
|
41
|
+
}
|
|
49
42
|
}
|
|
50
43
|
|
|
51
44
|
get [Symbol.toStringTag]() {
|
|
52
45
|
return 'LlmErrorMessage'
|
|
53
46
|
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* For `@google/genai` only, `cause` does not have the `error` or `status` fields,
|
|
50
|
+
* but it does have `message` with the info we need. So, we need to parse
|
|
51
|
+
* the relevant fields from cause.message to get `status` and `error`.
|
|
52
|
+
* @param {object} cause error object
|
|
53
|
+
*/
|
|
54
|
+
_handleGemini(cause) {
|
|
55
|
+
if (cause?.message) {
|
|
56
|
+
try {
|
|
57
|
+
const jsonStartIndex = cause.message.indexOf('{')
|
|
58
|
+
const jsonString = cause.message.substring(jsonStartIndex)
|
|
59
|
+
const parsedError = JSON.parse(jsonString)?.error
|
|
60
|
+
|
|
61
|
+
this['http.statusCode'] = parsedError?.code
|
|
62
|
+
this['error.code'] = parsedError?.code
|
|
63
|
+
} catch { }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
54
66
|
}
|
package/lib/metrics/names.js
CHANGED
|
@@ -291,11 +291,16 @@ const DISTRIBUTED_TRACE = {
|
|
|
291
291
|
|
|
292
292
|
const SPAN_EVENT_PREFIX = 'SpanEvent/'
|
|
293
293
|
|
|
294
|
+
const PARTIAL_GRANULARITY = 'DistributedTrace/PartialGranularity'
|
|
295
|
+
|
|
294
296
|
const SPAN_EVENTS = {
|
|
295
297
|
SEEN: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'TotalEventsSeen',
|
|
296
298
|
SENT: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'TotalEventsSent',
|
|
297
299
|
DROPPED: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'Discarded',
|
|
298
|
-
LIMIT: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'Limit'
|
|
300
|
+
LIMIT: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'Limit',
|
|
301
|
+
PARTIAL_PREFIX: SUPPORTABILITY.PREFIX + PARTIAL_GRANULARITY,
|
|
302
|
+
KEPT: '/Span/Kept',
|
|
303
|
+
INSTRUMENTED: '/Span/Instrumented',
|
|
299
304
|
}
|
|
300
305
|
|
|
301
306
|
const INFINITE_TRACING = {
|
|
@@ -9,7 +9,7 @@ class AlwaysOnSampler extends Sampler {
|
|
|
9
9
|
applySamplingDecision({ transaction, partialType }) {
|
|
10
10
|
if (!transaction) return
|
|
11
11
|
transaction.partialType = partialType
|
|
12
|
-
transaction.priority = 2.0
|
|
12
|
+
transaction.priority = partialType ? 2.0 : 3.0
|
|
13
13
|
transaction.sampled = true
|
|
14
14
|
}
|
|
15
15
|
}
|
package/lib/samplers/index.js
CHANGED
|
@@ -10,6 +10,7 @@ const AlwaysOffSampler = require('./always-off-sampler')
|
|
|
10
10
|
const AlwaysOnSampler = require('./always-on-sampler')
|
|
11
11
|
const TraceIdRatioBasedSampler = require('./ratio-based-sampler')
|
|
12
12
|
const logger = require('../logger').child({ component: 'samplers' })
|
|
13
|
+
const { PARTIAL_TYPES } = require('../transaction')
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Manages the different samplers used for distributed tracing sampling decisions.
|
|
@@ -30,7 +31,7 @@ class Samplers {
|
|
|
30
31
|
constructor(agent) {
|
|
31
32
|
this.fullEnabled = agent.config.distributed_tracing.enabled && agent.config.distributed_tracing.sampler.full_granularity.enabled
|
|
32
33
|
this.partialEnabled = agent.config.distributed_tracing.enabled && agent.config.distributed_tracing.sampler.partial_granularity.enabled
|
|
33
|
-
this.partialType = agent.config.distributed_tracing.sampler.partial_granularity.type
|
|
34
|
+
this.partialType = PARTIAL_TYPES[agent.config.distributed_tracing.sampler.partial_granularity.type.toUpperCase()]
|
|
34
35
|
this.adaptiveSampler = null
|
|
35
36
|
this.root = this.determineSampler({ agent, sampler: 'root' })
|
|
36
37
|
this.remoteParentSampled = this.determineSampler({ agent, sampler: 'remote_parent_sampled' })
|
|
@@ -13,7 +13,6 @@ const Sampler = require('./sampler')
|
|
|
13
13
|
class TraceIdRatioBasedSampler extends Sampler {
|
|
14
14
|
/**
|
|
15
15
|
* @param {object} opts Sampler options.
|
|
16
|
-
* @param {Agent} opts.agent - The New Relic agent instance.
|
|
17
16
|
* @param {number} opts.ratio - The sampling ratio (0 to 1).
|
|
18
17
|
*/
|
|
19
18
|
constructor(opts) {
|
|
@@ -35,8 +35,23 @@ class SpanEventAggregator extends EventAggregator {
|
|
|
35
35
|
reservoir_size: events.limit,
|
|
36
36
|
events_seen: events.seen
|
|
37
37
|
}
|
|
38
|
+
|
|
38
39
|
const eventData = events.toArray()
|
|
39
|
-
|
|
40
|
+
const eventsPayload = []
|
|
41
|
+
for (const event of eventData) {
|
|
42
|
+
eventsPayload.push(event)
|
|
43
|
+
|
|
44
|
+
if (event.spanLinks.length > 0) {
|
|
45
|
+
Array.prototype.push.apply(eventsPayload, event.spanLinks)
|
|
46
|
+
delete event.spanLinks
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return [this.runId, metrics, eventsPayload]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
toString() {
|
|
54
|
+
return 'SpanEventAggregator'
|
|
40
55
|
}
|
|
41
56
|
|
|
42
57
|
start() {
|
|
@@ -75,16 +90,22 @@ class SpanEventAggregator extends EventAggregator {
|
|
|
75
90
|
|
|
76
91
|
return false
|
|
77
92
|
}
|
|
78
|
-
const span = SpanEvent.fromSegment({ segment, transaction, parentId, isRoot, inProcessSpans: this.inProcessSpans })
|
|
79
93
|
|
|
80
|
-
|
|
81
|
-
this.add(span, transaction.priority)
|
|
82
|
-
}
|
|
94
|
+
const span = SpanEvent.fromSegment({ segment, transaction, parentId, isRoot, inProcessSpans: this.inProcessSpans })
|
|
83
95
|
|
|
84
|
-
if
|
|
85
|
-
|
|
86
|
-
|
|
96
|
+
// Do not add span to aggregator if it is part of a partial trace
|
|
97
|
+
// We need to reparent spans or in the case of `compact`, associate
|
|
98
|
+
// all exit spans dropped to the same entity as `nr.ids` and `nr.durations`
|
|
99
|
+
if (transaction.partialType) {
|
|
100
|
+
const prefix = `${this._metricNames.PARTIAL_PREFIX}/${transaction.partialType}`
|
|
101
|
+
this._metrics.getOrCreateMetric(prefix).incrementCallCount()
|
|
102
|
+
this._metrics.getOrCreateMetric(`${prefix}${this._metricNames.INSTRUMENTED}`).incrementCallCount()
|
|
103
|
+
transaction.trace.addSpan({ span, id: segment.id, parentId })
|
|
104
|
+
if (span) {
|
|
105
|
+
this._metrics.getOrCreateMetric(`${prefix}${this._metricNames.KEPT}`).incrementCallCount()
|
|
87
106
|
}
|
|
107
|
+
} else if (span) {
|
|
108
|
+
this.add(span, transaction.priority)
|
|
88
109
|
}
|
|
89
110
|
}
|
|
90
111
|
|
package/lib/spans/span-event.js
CHANGED
|
@@ -13,6 +13,7 @@ const { addSpanKind, isEntryPointSpan, reparentSpan, shouldCreateSpan, HTTP_LIBR
|
|
|
13
13
|
const EMPTY_USER_ATTRS = Object.freeze(Object.create(null))
|
|
14
14
|
const SERVER_ADDRESS = 'server.address'
|
|
15
15
|
const logger = require('../logger').child({ component: 'span-event' })
|
|
16
|
+
const { PARTIAL_TYPES } = require('../transaction')
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* This keeps a static list of attributes that are used by
|
|
@@ -85,6 +86,10 @@ class SpanEvent {
|
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
get parentId() {
|
|
90
|
+
return this.intrinsics.parentId
|
|
91
|
+
}
|
|
92
|
+
|
|
88
93
|
getIntrinsicAttributes() {
|
|
89
94
|
return this.intrinsics
|
|
90
95
|
}
|
|
@@ -180,13 +185,13 @@ class SpanEvent {
|
|
|
180
185
|
const attributes = span.attributes
|
|
181
186
|
const attrKeys = Object.keys(attributes)
|
|
182
187
|
const entityRelationshipAttrs = SPAN_ENTITY_RELATIONSHIP_ATTRIBUTES.filter((item) => attrKeys.includes(item))
|
|
183
|
-
if (partialType ===
|
|
188
|
+
if (partialType === PARTIAL_TYPES.REDUCED) {
|
|
184
189
|
if (entityRelationshipAttrs.length === 0) {
|
|
185
190
|
logger.trace('Span %s does not contain any entity relationship attributes %j and trace is partial granularity type: %s, dropping span.', span.intrinsics.name, span.attributes, partialType)
|
|
186
191
|
return null
|
|
187
192
|
}
|
|
188
193
|
logger.trace('Span %s contains entity relationship attributes and trace is partial granularity type: %s, keeping span unchanged.', span.intrinsics.name, partialType)
|
|
189
|
-
} else if (partialType ===
|
|
194
|
+
} else if (partialType === PARTIAL_TYPES.ESSENTIAL) {
|
|
190
195
|
const attributesToKeep = Object.create(null)
|
|
191
196
|
for (const item in attributes) {
|
|
192
197
|
if (entityRelationshipAttrs.includes(item) || item.startsWith('error.')) {
|
|
@@ -216,6 +221,7 @@ class SpanEvent {
|
|
|
216
221
|
} else {
|
|
217
222
|
span = new SpanEvent(attributes, customAttributes)
|
|
218
223
|
}
|
|
224
|
+
span.spanLinks = segment.spanLinks ?? []
|
|
219
225
|
return span
|
|
220
226
|
}
|
|
221
227
|
|
|
@@ -47,6 +47,10 @@ class StreamingSpanEventAggregator extends Aggregator {
|
|
|
47
47
|
this.inProcessSpans = agent.config.distributed_tracing.in_process_spans.enabled
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
toString() {
|
|
51
|
+
return 'StreamingSpanEventAggregator'
|
|
52
|
+
}
|
|
53
|
+
|
|
50
54
|
/**
|
|
51
55
|
* Start streaming spans to the collector.
|
|
52
56
|
*
|
|
@@ -9,10 +9,12 @@
|
|
|
9
9
|
// 'package-name': [ { path: 'subscriberPath', instrumentations: [] }, ... ]
|
|
10
10
|
const subscribers = {
|
|
11
11
|
...require('./subscribers/amqplib/config'),
|
|
12
|
+
...require('./subscribers/bunyan/config'),
|
|
12
13
|
...require('./subscribers/cassandra-driver/config'),
|
|
13
14
|
...require('./subscribers/elasticsearch/config'),
|
|
14
15
|
...require('./subscribers/express/config'),
|
|
15
16
|
...require('./subscribers/fastify/config'),
|
|
17
|
+
...require('./subscribers/google-genai/config'),
|
|
16
18
|
...require('./subscribers/ioredis/config'),
|
|
17
19
|
...require('./subscribers/mcp-sdk/config'),
|
|
18
20
|
...require('./subscribers/mysql/config'),
|
|
@@ -21,6 +21,10 @@ class ApplicationLogsSubscriber extends Subscriber {
|
|
|
21
21
|
return super.enabled && isApplicationLoggingEnabled(this.config)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
isLogForwardingEnabled() {
|
|
25
|
+
return isLogForwardingEnabled(this.config, this.agent)
|
|
26
|
+
}
|
|
27
|
+
|
|
24
28
|
/**
|
|
25
29
|
* The intent of this method is to create a module usage metric
|
|
26
30
|
* but only once. You should call this in your handler.
|
|
@@ -48,7 +52,7 @@ class ApplicationLogsSubscriber extends Subscriber {
|
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
forwardLogLine(data) {
|
|
51
|
-
if (isLogForwardingEnabled(
|
|
55
|
+
if (this.isLogForwardingEnabled()) {
|
|
52
56
|
const ctx = this.agent.tracer.getContext()
|
|
53
57
|
const formatLogLine = this.reformatLogLine(data, ctx)
|
|
54
58
|
this.agent.logs.add(formatLogLine)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
const ApplicationLogsSubscriber = require('../application-logs')
|
|
8
|
+
|
|
9
|
+
class BunyanBaseSubscriber extends ApplicationLogsSubscriber {
|
|
10
|
+
NAME_FROM_LEVEL = {
|
|
11
|
+
10: 'trace',
|
|
12
|
+
20: 'debug',
|
|
13
|
+
30: 'info',
|
|
14
|
+
40: 'warn',
|
|
15
|
+
50: 'error',
|
|
16
|
+
60: 'fatal'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
constructor({ agent, logger, channelName }) {
|
|
20
|
+
super({ agent, logger, packageName: 'bunyan', channelName })
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = BunyanBaseSubscriber
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
bunyan: [
|
|
8
|
+
{
|
|
9
|
+
path: './bunyan',
|
|
10
|
+
instrumentations: [
|
|
11
|
+
{
|
|
12
|
+
channelName: 'nr_logger',
|
|
13
|
+
module: { name: 'bunyan', versionRange: '>=1.8.12', filePath: 'lib/bunyan.js' },
|
|
14
|
+
functionQuery: {
|
|
15
|
+
functionName: 'Logger',
|
|
16
|
+
kind: 'Sync'
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
path: './bunyan/emit',
|
|
23
|
+
instrumentations: [
|
|
24
|
+
{
|
|
25
|
+
channelName: 'nr_emit',
|
|
26
|
+
module: { name: 'bunyan', versionRange: '>=1.8.12', filePath: 'lib/bunyan.js' },
|
|
27
|
+
functionQuery: {
|
|
28
|
+
expressionName: '_emit',
|
|
29
|
+
kind: 'Sync'
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
const BunyanSubscriber = require('./base')
|
|
8
|
+
const { truncate } = require('../../util/application-logging')
|
|
9
|
+
|
|
10
|
+
class BunyanEmitSubscriber extends BunyanSubscriber {
|
|
11
|
+
constructor({ agent, logger }) {
|
|
12
|
+
super({ agent, logger, channelName: 'nr_emit' })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
handler(data, ctx) {
|
|
16
|
+
const [line] = data.arguments
|
|
17
|
+
if (!line) {
|
|
18
|
+
return ctx
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
this.incrementLinesMetric(this.NAME_FROM_LEVEL[line.level])
|
|
22
|
+
const decoratedLine = this.decorateLogLine()
|
|
23
|
+
if (decoratedLine) {
|
|
24
|
+
line.message = truncate(line.msg) + decoratedLine
|
|
25
|
+
data.arguments[0] = line
|
|
26
|
+
}
|
|
27
|
+
return ctx
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = BunyanEmitSubscriber
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
const BunyanSubscriber = require('./base')
|
|
8
|
+
const { truncate } = require('../../util/application-logging')
|
|
9
|
+
|
|
10
|
+
class BunyanLoggerSubscriber extends BunyanSubscriber {
|
|
11
|
+
constructor({ agent, logger }) {
|
|
12
|
+
super({ agent, logger, channelName: 'nr_logger' })
|
|
13
|
+
this.events = ['end']
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
handler(data, ctx) {
|
|
17
|
+
this.createModuleUsageMetric('bunyan')
|
|
18
|
+
return ctx
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
end(data) {
|
|
22
|
+
const self = this
|
|
23
|
+
if (this.isLogForwardingEnabled()) {
|
|
24
|
+
const logger = data.self
|
|
25
|
+
logger.addStream({
|
|
26
|
+
name: 'NRLogForwarder',
|
|
27
|
+
type: 'raw',
|
|
28
|
+
level: logger.level(),
|
|
29
|
+
stream: {
|
|
30
|
+
write: function nrLogWrite(logLine) {
|
|
31
|
+
self.forwardLogLine(logLine)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
reformatLogLine(logLine) {
|
|
39
|
+
// shallow copy, since we're modifying things
|
|
40
|
+
const newLog = Object.assign({}, logLine)
|
|
41
|
+
newLog.timestamp = Date.now()
|
|
42
|
+
newLog.level = this.NAME_FROM_LEVEL[logLine.level]
|
|
43
|
+
|
|
44
|
+
// put log message into a consistent spot and ensure it's not too long
|
|
45
|
+
newLog.message = truncate(newLog.msg)
|
|
46
|
+
|
|
47
|
+
// tidy up the error output to help with max length restrictions
|
|
48
|
+
if (newLog.err) {
|
|
49
|
+
newLog['error.message'] = truncate(newLog.err.message)
|
|
50
|
+
newLog['error.stack'] = truncate(newLog.err.stack)
|
|
51
|
+
newLog['error.class'] =
|
|
52
|
+
newLog.err.name === 'Error' ? newLog.err.constructor.name : newLog.err.name
|
|
53
|
+
// clear out the old error message
|
|
54
|
+
delete newLog.err
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Add the metadata to the object being logged
|
|
58
|
+
const metadata = this.agent.getLinkingMetadata(true)
|
|
59
|
+
return Object.assign({}, newLog, metadata)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = BunyanLoggerSubscriber
|