newrelic 13.0.0 → 13.2.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 +92 -0
- package/README.md +2 -4
- package/THIRD_PARTY_NOTICES.md +429 -9
- package/esm-loader.mjs +9 -1
- package/index.js +38 -3
- package/lib/agent.js +8 -2
- package/lib/config/build-instrumentation-config.js +3 -0
- package/lib/config/default.js +1397 -1383
- package/lib/config/index.js +15 -19
- package/lib/context-manager/context.js +13 -4
- package/lib/harvester.js +11 -13
- package/lib/instrumentation/@google/genai.js +8 -3
- package/lib/instrumentation/@hapi/hapi.js +4 -6
- package/lib/instrumentation/@nestjs/core.js +12 -14
- package/lib/instrumentation/aws-sdk/v3/bedrock.js +2 -2
- package/lib/instrumentation/core/http-outbound.js +11 -26
- package/lib/instrumentation/kafkajs/producer.js +13 -19
- package/lib/instrumentation/openai.js +2 -2
- package/lib/instrumentation/undici.js +48 -40
- package/lib/instrumentations.js +14 -5
- package/lib/llm-events/aws-bedrock/utils.js +1 -1
- package/lib/metrics/names.js +8 -0
- package/lib/metrics/normalizer.js +1 -3
- package/lib/otel/context.js +18 -14
- package/lib/otel/logs/bootstrap-logs.js +84 -0
- package/lib/otel/logs/no-op-exporter.js +25 -0
- package/lib/otel/logs/normalize-timestamp.js +59 -0
- package/lib/otel/logs/proxying-provider.js +46 -0
- package/lib/otel/logs/severity-to-string.js +56 -0
- package/lib/otel/metrics/bootstrap-metrics.js +1 -1
- package/lib/otel/setup.js +21 -8
- package/lib/patch-module.js +70 -0
- package/lib/serverless/aws-lambda.js +1 -3
- package/lib/shim/webframework-shim/middleware-mounter.js +1 -3
- package/lib/shimmer.js +56 -8
- package/lib/subscriber-configs.js +17 -0
- package/lib/subscribers/application-logs.js +55 -0
- package/lib/subscribers/base.js +177 -0
- package/lib/subscribers/create-config.js +27 -0
- package/lib/subscribers/db-operation.js +21 -0
- package/lib/subscribers/db-query.js +54 -0
- package/lib/subscribers/db.js +57 -0
- package/lib/subscribers/elasticsearch/config.js +49 -0
- package/lib/subscribers/elasticsearch/elasticsearch.js +42 -0
- package/lib/subscribers/elasticsearch/opensearch.js +15 -0
- package/lib/subscribers/elasticsearch/transport.js +14 -0
- package/lib/subscribers/ioredis/config.js +37 -0
- package/lib/subscribers/ioredis/index.js +48 -0
- package/lib/subscribers/mcp-sdk/client-prompt.js +23 -0
- package/lib/subscribers/mcp-sdk/client-resource.js +24 -0
- package/lib/subscribers/mcp-sdk/client-tool.js +23 -0
- package/lib/subscribers/mcp-sdk/client.js +29 -0
- package/lib/subscribers/mcp-sdk/config.js +104 -0
- package/lib/subscribers/pino/config.js +20 -0
- package/lib/subscribers/pino/index.js +102 -0
- package/lib/transaction/index.js +4 -1
- package/lib/transaction/trace/aggregator.js +7 -9
- package/lib/util/camel-case.js +1 -3
- package/lib/util/get-package-version.js +34 -0
- package/lib/util/urltils.js +19 -13
- package/lib/w3c/tracestate.js +3 -3
- package/newrelic.js +10 -0
- package/package.json +9 -5
- package/lib/instrumentation/@elastic/elasticsearch.js +0 -62
- package/lib/instrumentation/@opensearch-project/opensearch.js +0 -66
- package/lib/instrumentation/ioredis.js +0 -50
- package/lib/instrumentation/pino/nr-hooks.js +0 -19
- package/lib/instrumentation/pino/pino.js +0 -168
|
@@ -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 Subscriber = require('./base')
|
|
7
|
+
const { isApplicationLoggingEnabled, isLocalDecoratingEnabled, isLogForwardingEnabled, isMetricsEnabled, createModuleUsageMetric, incrementLoggingLinesMetrics } = require('../util/application-logging')
|
|
8
|
+
|
|
9
|
+
class ApplicationLogsSubscriber extends Subscriber {
|
|
10
|
+
constructor({ agent, logger, packageName, channelName, }) {
|
|
11
|
+
super({ agent, logger, packageName, channelName, })
|
|
12
|
+
this.requireActiveTx = false
|
|
13
|
+
this.libMetricCreated = false
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get enabled() {
|
|
17
|
+
return super.enabled && isApplicationLoggingEnabled(this.config)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The intent of this method is to create a module usage metric
|
|
22
|
+
* but only once. You should call this in your handler.
|
|
23
|
+
* We cannot recreate creating this metric on require of a logging library
|
|
24
|
+
* because we no longer monkey patch but subscribe to events.
|
|
25
|
+
* @param lib
|
|
26
|
+
*/
|
|
27
|
+
createModuleUsageMetric(lib) {
|
|
28
|
+
if (this.libMetricCreated === false) {
|
|
29
|
+
createModuleUsageMetric(lib, this.agent.metrics)
|
|
30
|
+
this.libMetricCreated = true
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
decorateLogLine() {
|
|
35
|
+
if (isLocalDecoratingEnabled(this.config)) {
|
|
36
|
+
return this.agent.getNRLinkingMetadata()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
incrementLinesMetric(level) {
|
|
41
|
+
if (isMetricsEnabled(this.config)) {
|
|
42
|
+
incrementLoggingLinesMetrics(level, this.agent.metrics)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
forwardLogLine(data) {
|
|
47
|
+
if (isLogForwardingEnabled(this.config, this.agent)) {
|
|
48
|
+
const ctx = this.agent.tracer.getContext()
|
|
49
|
+
const formatLogLine = this.reformatLogLine(data, ctx)
|
|
50
|
+
this.agent.logs.add(formatLogLine)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = ApplicationLogsSubscriber
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
8
|
+
const { tracingChannel } = require('node:diagnostics_channel')
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The baseline parameters available to all subscribers.
|
|
12
|
+
*
|
|
13
|
+
* @typedef {object} SubscriberParams
|
|
14
|
+
* @property {object} agent A New Relic Node.js agent instance.
|
|
15
|
+
* @property {object} logger An agent logger instance.
|
|
16
|
+
* @property {string} packageName The npm installable name for the package
|
|
17
|
+
* being instrumented. This is what a developer would provide to the `require`
|
|
18
|
+
* function.
|
|
19
|
+
* @property {string} channelName A unique name for the diagnostics channel
|
|
20
|
+
* that will be created and monitored.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @property {object} agent A New Relic Node.js agent instance.
|
|
25
|
+
* @property {object} logger An agent logger instance.
|
|
26
|
+
* @property {object} config The agent configuration object.
|
|
27
|
+
* @property {string} packageName The name of the module being instrumented.
|
|
28
|
+
* This is the same string one would pass to the `require` function.
|
|
29
|
+
* @property {string} channelName A unique name for the diagnostics channel
|
|
30
|
+
* that will be registered.
|
|
31
|
+
* @property {string[]} [events=[]] Set of tracing channel event names to
|
|
32
|
+
* register handlers for. For any name in the set, a corresponding method
|
|
33
|
+
* must exist on the subscriber instance. The method will be passed the
|
|
34
|
+
* event object. Possible event names are `start`, `end`, `asyncStart`,
|
|
35
|
+
* `asyncEnd`, and `error`. @link https://nodejs.org/api/diagnostics_channel.html#class-tracingchannel
|
|
36
|
+
* @property {boolean} [opaque=false] If true, any children segments will not be created
|
|
37
|
+
* @property {boolean} [internal=false] If true, any children segments from the same library will not be created
|
|
38
|
+
* @property {string} [prefix='orchestrion:'] String to prepend to diagnostics
|
|
39
|
+
* channel event names. This provides a namespace for the events we are
|
|
40
|
+
* injecting into a module.
|
|
41
|
+
* @property {boolean} [requireActiveTx=true] If true, the subscriber will only handle events when there is an active transaction.
|
|
42
|
+
* @property {string} id A unique identifier for the subscriber, combining the prefix, package name, and channel name.
|
|
43
|
+
* @property {TracingChannel} channel The tracing channel instance this subscriber will be monitoring.
|
|
44
|
+
* @property {AsyncLocalStorage} store The async local storage instance used for context management.
|
|
45
|
+
*/
|
|
46
|
+
class Subscriber {
|
|
47
|
+
constructor({ agent, logger, packageName, channelName }) {
|
|
48
|
+
this.agent = agent
|
|
49
|
+
this.logger = logger.child({ component: `${packageName}-subscriber` })
|
|
50
|
+
this.config = agent.config
|
|
51
|
+
this.packageName = packageName
|
|
52
|
+
this.channelName = channelName
|
|
53
|
+
this.events = []
|
|
54
|
+
this.opaque = false
|
|
55
|
+
this.internal = false
|
|
56
|
+
this.prefix = 'orchestrion:'
|
|
57
|
+
this.requireActiveTx = true
|
|
58
|
+
this.id = `${this.prefix}${this.packageName}:${this.channelName}`
|
|
59
|
+
this.channel = tracingChannel(this.id)
|
|
60
|
+
this.store = agent.tracer._contextManager._asyncLocalStorage
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
shouldCreateSegment(parent) {
|
|
64
|
+
return !(parent?.opaque ||
|
|
65
|
+
(this.internal && this.packageName === parent?.shimId)
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Creates a segment with a name, parent, transaction and optional recorder.
|
|
71
|
+
* If the segment is successfully created, it will be started and added to the context.
|
|
72
|
+
* @param {Object} params - Parameters for creating the segment
|
|
73
|
+
* @param {string} params.name - The name of the segment
|
|
74
|
+
* @param {Object} params.recorder - Optional recorder for the segment
|
|
75
|
+
* @param {Context} params.ctx - The context containing the parent segment and transaction
|
|
76
|
+
* @returns {Context} - The updated context with the new segment or existing context if segment creation fails
|
|
77
|
+
*/
|
|
78
|
+
createSegment({ name, recorder, ctx }) {
|
|
79
|
+
const parent = ctx?.segment
|
|
80
|
+
|
|
81
|
+
if (this.shouldCreateSegment(parent) === false) {
|
|
82
|
+
this.logger.trace('Skipping segment creation for %s, %s(parent) is of the same package: %s and incoming segment is marked as internal', name, parent?.name, this.packageName)
|
|
83
|
+
return ctx
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const segment = this.agent.tracer.createSegment({
|
|
87
|
+
name,
|
|
88
|
+
parent,
|
|
89
|
+
recorder,
|
|
90
|
+
transaction: ctx?.transaction,
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
if (segment) {
|
|
94
|
+
segment.opaque = this.opaque
|
|
95
|
+
segment.shimId = this.packageName
|
|
96
|
+
segment.start()
|
|
97
|
+
this.logger.trace('Created segment %s', name)
|
|
98
|
+
this.addAttributes(segment)
|
|
99
|
+
const newCtx = ctx.enterSegment({ segment })
|
|
100
|
+
return newCtx
|
|
101
|
+
} else {
|
|
102
|
+
this.logger.trace('Failed to create segment for %s', name)
|
|
103
|
+
return ctx
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* By default this is a no-op, but can be overridden by subclasses
|
|
109
|
+
* @param {Segment} segment - The segment to which attributes will be added
|
|
110
|
+
* @returns {void}
|
|
111
|
+
*/
|
|
112
|
+
addAttributes(segment) {
|
|
113
|
+
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Checks if the subscriber is enabled based on the agent's configuration.
|
|
118
|
+
*/
|
|
119
|
+
get enabled() {
|
|
120
|
+
return this.config.instrumentation[this.packageName].enabled === true
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Enables the subscriber by binding the store to the channel and setting up the handler.
|
|
125
|
+
* If the subscriber requires an active transaction, it will check the context before passing the event to the handler.
|
|
126
|
+
* @returns {Context} - The context after processing the event
|
|
127
|
+
*/
|
|
128
|
+
enable() {
|
|
129
|
+
this.channel.start.bindStore(this.store, (data) => {
|
|
130
|
+
const ctx = this.agent.tracer.getContext()
|
|
131
|
+
if (this.requireActiveTx && !ctx?.transaction?.isActive()) {
|
|
132
|
+
this.logger.debug('Not recording event for %s, transaction is not active', this.package)
|
|
133
|
+
return ctx
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return this.handler(data, ctx)
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Disables the subscriber by unbinding the store from the channel.
|
|
142
|
+
*/
|
|
143
|
+
disable() {
|
|
144
|
+
this.channel.start.unbindStore(this.store)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Common handler for when async events end.
|
|
149
|
+
* It gets the context and touches the segment if it exists.
|
|
150
|
+
*/
|
|
151
|
+
asyncEnd() {
|
|
152
|
+
const ctx = this.agent.tracer.getContext()
|
|
153
|
+
ctx?.segment?.touch()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/*
|
|
157
|
+
* Subscribes to the events defined in the `events` array.
|
|
158
|
+
*/
|
|
159
|
+
subscribe() {
|
|
160
|
+
this.subscriptions = this.events.reduce((events, curr) => {
|
|
161
|
+
events[curr] = this[curr].bind(this)
|
|
162
|
+
return events
|
|
163
|
+
}, {})
|
|
164
|
+
|
|
165
|
+
this.channel.subscribe(this.subscriptions)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Unsubscribes from the events defined in the `events` array..
|
|
170
|
+
*/
|
|
171
|
+
unsubscribe() {
|
|
172
|
+
this.channel.unsubscribe(this.subscriptions)
|
|
173
|
+
this.subscriptions = null
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
module.exports = Subscriber
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Builds a set of packages and instrumentaions from a list of subscriber configurations.
|
|
10
|
+
*
|
|
11
|
+
* @param {Array} subscribers - An array of subscriber objects, each containing a package name and an array of instrumentations.
|
|
12
|
+
* @returns {Object} An object containing a Set of unique package names and an array of instrumentations.
|
|
13
|
+
*/
|
|
14
|
+
function createSubscribersConfig (subscribers = []) {
|
|
15
|
+
const packages = new Set()
|
|
16
|
+
const instrumentations = []
|
|
17
|
+
for (const [packageName, subscriberList] of Object.entries(subscribers)) {
|
|
18
|
+
packages.add(packageName)
|
|
19
|
+
for (const subscriber of subscriberList) {
|
|
20
|
+
instrumentations.push(...subscriber.instrumentations)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return { packages, instrumentations }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = createSubscribersConfig
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const DbSubscriber = require('./db')
|
|
7
|
+
const recordOperationMetrics = require('../metrics/recorders/database-operation')
|
|
8
|
+
const { DB } = require('../metrics/names')
|
|
9
|
+
|
|
10
|
+
class DbOperationSubscriber extends DbSubscriber {
|
|
11
|
+
handler(data, ctx) {
|
|
12
|
+
const name = `${DB.OPERATION}/${this.system}/${this.operation}`
|
|
13
|
+
return this.createSegment({
|
|
14
|
+
name,
|
|
15
|
+
ctx,
|
|
16
|
+
recorder: recordOperationMetrics.bind(this),
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = DbOperationSubscriber
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const DbSubscriber = require('./db')
|
|
7
|
+
const recordQueryMetrics = require('../metrics/recorders/database')
|
|
8
|
+
const { DB } = require('../metrics/names')
|
|
9
|
+
const ParsedStatement = require('../db/parsed-statement')
|
|
10
|
+
const parseSql = require('../db/query-parsers/sql')
|
|
11
|
+
|
|
12
|
+
class DbQuerySubscriber extends DbSubscriber {
|
|
13
|
+
handler(data, ctx) {
|
|
14
|
+
const queryString = this.queryString
|
|
15
|
+
const parsed = this.parseQueryString(queryString)
|
|
16
|
+
const name = `${DB.STATEMENT}/${this.system}/${parsed.collection}/${parsed.operation}`
|
|
17
|
+
return this.createSegment({
|
|
18
|
+
name,
|
|
19
|
+
ctx,
|
|
20
|
+
recorder: recordQueryMetrics.bind(parsed),
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
parseQueryString(queryString) {
|
|
25
|
+
const parsed = this.parseQuery(queryString)
|
|
26
|
+
let collection = parsed.collection
|
|
27
|
+
// strip enclosing special characters from collection (table) name
|
|
28
|
+
if (typeof collection === 'string' && collection.length > 2) {
|
|
29
|
+
if (/^[[{'"`]/.test(collection)) {
|
|
30
|
+
collection = collection.substring(1)
|
|
31
|
+
}
|
|
32
|
+
if (/[\]}'"`]$/.test(collection)) {
|
|
33
|
+
collection = collection.substring(0, collection.length - 1)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const queryRecorded =
|
|
38
|
+
this.config.transaction_tracer.record_sql === 'raw' ||
|
|
39
|
+
this.config.transaction_tracer.record_sql === 'obfuscated'
|
|
40
|
+
|
|
41
|
+
return new ParsedStatement(
|
|
42
|
+
this._metrics.PREFIX,
|
|
43
|
+
parsed.operation,
|
|
44
|
+
collection,
|
|
45
|
+
queryRecorded ? parsed.query : null
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
parseQuery(queryString) {
|
|
50
|
+
return parseSql(queryString)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = DbQuerySubscriber
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const Subscriber = require('./base')
|
|
7
|
+
const { ALL, DB } = require('../metrics/names')
|
|
8
|
+
const urltils = require('../util/urltils')
|
|
9
|
+
|
|
10
|
+
class DbSubscriber extends Subscriber {
|
|
11
|
+
constructor({ agent, logger, packageName, channelName, system }) {
|
|
12
|
+
super({ agent, logger, packageName, channelName })
|
|
13
|
+
this.system = system
|
|
14
|
+
// must be prefixed with `_` as that's what the metrics recorder expects
|
|
15
|
+
this._metrics = {
|
|
16
|
+
PREFIX: this.system,
|
|
17
|
+
ALL: `${DB.PREFIX}${this.system}/${ALL}`
|
|
18
|
+
}
|
|
19
|
+
this.instanceKeys = ['host', 'port_path_or_id']
|
|
20
|
+
this.hostKey = 'host'
|
|
21
|
+
this.dbNameKey = 'database_name'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get instanceReporting() {
|
|
25
|
+
return this.config.datastore_tracer.instance_reporting.enabled
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get dbNameReporting() {
|
|
29
|
+
return this.config.datastore_tracer.database_name_reporting.enabled
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
addAttributes(segment) {
|
|
33
|
+
for (let [key, value] of Object.entries(this.parameters)) {
|
|
34
|
+
if (this.instanceKeys.includes(key) && !this.instanceReporting) {
|
|
35
|
+
continue
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (key === this.dbNameKey && !this.dbNameReporting) {
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (key === this.hostKey && urltils.isLocalhost(value)) {
|
|
43
|
+
// eslint-disable-next-line sonarjs/updated-loop-counter
|
|
44
|
+
value = this.config.getHostnameSafe()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (key === this.dbNameKey && typeof value === 'number') {
|
|
48
|
+
// eslint-disable-next-line sonarjs/updated-loop-counter
|
|
49
|
+
value = String(value)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
segment.addAttribute(key, value)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = DbSubscriber
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
'@elastic/elasticsearch': [{
|
|
8
|
+
path: './elasticsearch/elasticsearch.js',
|
|
9
|
+
instrumentations: [
|
|
10
|
+
{
|
|
11
|
+
channelName: 'nr_request',
|
|
12
|
+
module: { name: '@elastic/elasticsearch', versionRange: '>=7.16.0', filePath: 'lib/Transport.js' },
|
|
13
|
+
functionQuery: {
|
|
14
|
+
className: 'Transport',
|
|
15
|
+
methodName: 'request',
|
|
16
|
+
kind: 'Async'
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}],
|
|
21
|
+
'@elastic/transport': [{
|
|
22
|
+
path: './elasticsearch/transport.js',
|
|
23
|
+
instrumentations: [
|
|
24
|
+
{
|
|
25
|
+
channelName: 'nr_request',
|
|
26
|
+
module: { name: '@elastic/transport', versionRange: '>=8.0.0', filePath: 'lib/Transport.js' },
|
|
27
|
+
functionQuery: {
|
|
28
|
+
className: 'Transport',
|
|
29
|
+
methodName: 'request',
|
|
30
|
+
kind: 'Async'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}],
|
|
35
|
+
'@opensearch-project/opensearch': [{
|
|
36
|
+
path: './elasticsearch/opensearch.js',
|
|
37
|
+
instrumentations: [
|
|
38
|
+
{
|
|
39
|
+
channelName: 'nr_request',
|
|
40
|
+
module: { name: '@opensearch-project/opensearch', versionRange: '>=2.1.0', filePath: 'lib/Transport.js' },
|
|
41
|
+
functionQuery: {
|
|
42
|
+
className: 'Transport',
|
|
43
|
+
methodName: 'request',
|
|
44
|
+
kind: 'Async'
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}]
|
|
49
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
const DbQuerySubscriber = require('../db-query')
|
|
8
|
+
const stringify = require('json-stringify-safe')
|
|
9
|
+
const { queryParser } = require('../../db/query-parsers/elasticsearch')
|
|
10
|
+
|
|
11
|
+
class ElasticSearchSubscriber extends DbQuerySubscriber {
|
|
12
|
+
constructor({ agent, logger, packageName = '@elastic/elasticsearch', channelName = 'nr_request', system = 'ElasticSearch' } = {}) {
|
|
13
|
+
super({ agent, logger, packageName, channelName, system })
|
|
14
|
+
this.events = ['asyncEnd']
|
|
15
|
+
this.opaque = true
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
handler(data, ctx) {
|
|
19
|
+
const { self, arguments: args } = data
|
|
20
|
+
this.queryString = stringify(args?.[0])
|
|
21
|
+
this.setParameters(self)
|
|
22
|
+
return super.handler(data, ctx)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setParameters(self) {
|
|
26
|
+
this.parameters = {}
|
|
27
|
+
this.parameters.product = this.system
|
|
28
|
+
const connectionPool = self?.connectionPool?.connections?.[0]
|
|
29
|
+
if (connectionPool) {
|
|
30
|
+
const host = connectionPool?.url?.host?.split(':')
|
|
31
|
+
const port = connectionPool?.url?.port || host?.[1]
|
|
32
|
+
this.parameters.host = host?.[0]
|
|
33
|
+
this.parameters.port_path_or_id = port
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
parseQuery(queryString) {
|
|
38
|
+
return queryParser(queryString)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = ElasticSearchSubscriber
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
|
|
8
|
+
const ElasticSearchSubscriber = require('./elasticsearch.js')
|
|
9
|
+
class OpenSearchSubscriber extends ElasticSearchSubscriber {
|
|
10
|
+
constructor({ agent, logger }) {
|
|
11
|
+
super({ agent, logger, packageName: '@opensearch-project/opensearch', channelName: 'nr_request', system: 'OpenSearch' })
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = OpenSearchSubscriber
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const ElasticSearchSubscriber = require('./elasticsearch')
|
|
7
|
+
|
|
8
|
+
class ElasticSearchTransportSubscriber extends ElasticSearchSubscriber {
|
|
9
|
+
constructor({ agent, logger }) {
|
|
10
|
+
super({ agent, logger, packageName: '@elastic/transport' })
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = ElasticSearchTransportSubscriber
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
ioredis: [{
|
|
8
|
+
path: './ioredis',
|
|
9
|
+
instrumentations: [
|
|
10
|
+
{
|
|
11
|
+
channelName: 'nr_sendCommand',
|
|
12
|
+
module: { name: 'ioredis', versionRange: '>=4', filePath: 'built/Redis.js' },
|
|
13
|
+
functionQuery: {
|
|
14
|
+
className: 'Redis',
|
|
15
|
+
methodName: 'sendCommand',
|
|
16
|
+
kind: 'Async'
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
channelName: 'nr_sendCommand',
|
|
21
|
+
module: { name: 'ioredis', versionRange: '>=4', filePath: 'built/redis.js' },
|
|
22
|
+
functionQuery: {
|
|
23
|
+
expressionName: 'sendCommand',
|
|
24
|
+
kind: 'Async'
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
channelName: 'nr_sendCommand',
|
|
29
|
+
module: { name: 'ioredis', versionRange: '>=4', filePath: 'built/redis/index.js' },
|
|
30
|
+
functionQuery: {
|
|
31
|
+
expressionName: 'sendCommand',
|
|
32
|
+
kind: 'Async'
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}]
|
|
37
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
const DbOperationSubscriber = require('../db-operation')
|
|
8
|
+
const stringify = require('json-stringify-safe')
|
|
9
|
+
|
|
10
|
+
class IoRedisSubscriber extends DbOperationSubscriber {
|
|
11
|
+
constructor({ agent, logger }) {
|
|
12
|
+
super({ agent, logger, packageName: 'ioredis', channelName: 'nr_sendCommand', system: 'Redis' })
|
|
13
|
+
this.events = ['asyncEnd']
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
handler(data, ctx) {
|
|
17
|
+
const { self, arguments: args } = data
|
|
18
|
+
const [command] = args
|
|
19
|
+
this.operation = command.name
|
|
20
|
+
this.setParameters(self, command)
|
|
21
|
+
return super.handler(data, ctx)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
setParameters(self, command) {
|
|
25
|
+
this.parameters = {}
|
|
26
|
+
this.parameters.product = this.system
|
|
27
|
+
this.parameters.host = self?.connector?.options?.host
|
|
28
|
+
this.parameters.port_path_or_id = self?.connector?.options?.port
|
|
29
|
+
this.parameters.key = this.parseKey(command.args)
|
|
30
|
+
this.parameters.database_name = self?.condition?.select
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
parseKey(keys) {
|
|
34
|
+
let key
|
|
35
|
+
if (keys && typeof keys !== 'function') {
|
|
36
|
+
try {
|
|
37
|
+
key = stringify(keys[0])
|
|
38
|
+
} catch (err) {
|
|
39
|
+
this.logger.debug(err, 'Failed to stringify ioredis key')
|
|
40
|
+
key = '<unknown>'
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return key
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = IoRedisSubscriber
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
|
|
8
|
+
const McpClientSubscriber = require('./client')
|
|
9
|
+
const { MCP } = require('../../metrics/names')
|
|
10
|
+
|
|
11
|
+
class McpClientPromptSubscriber extends McpClientSubscriber {
|
|
12
|
+
constructor({ agent, logger }) {
|
|
13
|
+
super({ agent, logger, channelName: 'nr_getPrompt' })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
handler(data, ctx) {
|
|
17
|
+
const promptName = data?.arguments?.[0]?.name
|
|
18
|
+
this.segmentName = `${MCP.PROMPT}/getPrompt/${promptName}`
|
|
19
|
+
return super.handler(ctx)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = McpClientPromptSubscriber
|
|
@@ -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
|
+
|
|
8
|
+
const McpClientSubscriber = require('./client')
|
|
9
|
+
const { MCP } = require('../../metrics/names')
|
|
10
|
+
|
|
11
|
+
class McpClientResourceSubscriber extends McpClientSubscriber {
|
|
12
|
+
constructor({ agent, logger }) {
|
|
13
|
+
super({ agent, logger, channelName: 'nr_readResource' })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
handler(data, ctx) {
|
|
17
|
+
const uri = data?.arguments?.[0]?.uri
|
|
18
|
+
const scheme = typeof uri === 'string' ? uri.split('://')[0] : undefined
|
|
19
|
+
this.segmentName = `${MCP.RESOURCE}/readResource/${scheme}`
|
|
20
|
+
return super.handler(ctx)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = McpClientResourceSubscriber
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
|
|
8
|
+
const McpClientSubscriber = require('./client')
|
|
9
|
+
const { MCP } = require('../../metrics/names')
|
|
10
|
+
|
|
11
|
+
class McpClientToolSubscriber extends McpClientSubscriber {
|
|
12
|
+
constructor({ agent, logger }) {
|
|
13
|
+
super({ agent, logger, channelName: 'nr_callTool' })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
handler(data, ctx) {
|
|
17
|
+
const toolName = data?.arguments?.[0]?.name
|
|
18
|
+
this.segmentName = `${MCP.TOOL}/callTool/${toolName}`
|
|
19
|
+
return super.handler(ctx)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = McpClientToolSubscriber
|