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.
Files changed (68) hide show
  1. package/NEWS.md +92 -0
  2. package/README.md +2 -4
  3. package/THIRD_PARTY_NOTICES.md +429 -9
  4. package/esm-loader.mjs +9 -1
  5. package/index.js +38 -3
  6. package/lib/agent.js +8 -2
  7. package/lib/config/build-instrumentation-config.js +3 -0
  8. package/lib/config/default.js +1397 -1383
  9. package/lib/config/index.js +15 -19
  10. package/lib/context-manager/context.js +13 -4
  11. package/lib/harvester.js +11 -13
  12. package/lib/instrumentation/@google/genai.js +8 -3
  13. package/lib/instrumentation/@hapi/hapi.js +4 -6
  14. package/lib/instrumentation/@nestjs/core.js +12 -14
  15. package/lib/instrumentation/aws-sdk/v3/bedrock.js +2 -2
  16. package/lib/instrumentation/core/http-outbound.js +11 -26
  17. package/lib/instrumentation/kafkajs/producer.js +13 -19
  18. package/lib/instrumentation/openai.js +2 -2
  19. package/lib/instrumentation/undici.js +48 -40
  20. package/lib/instrumentations.js +14 -5
  21. package/lib/llm-events/aws-bedrock/utils.js +1 -1
  22. package/lib/metrics/names.js +8 -0
  23. package/lib/metrics/normalizer.js +1 -3
  24. package/lib/otel/context.js +18 -14
  25. package/lib/otel/logs/bootstrap-logs.js +84 -0
  26. package/lib/otel/logs/no-op-exporter.js +25 -0
  27. package/lib/otel/logs/normalize-timestamp.js +59 -0
  28. package/lib/otel/logs/proxying-provider.js +46 -0
  29. package/lib/otel/logs/severity-to-string.js +56 -0
  30. package/lib/otel/metrics/bootstrap-metrics.js +1 -1
  31. package/lib/otel/setup.js +21 -8
  32. package/lib/patch-module.js +70 -0
  33. package/lib/serverless/aws-lambda.js +1 -3
  34. package/lib/shim/webframework-shim/middleware-mounter.js +1 -3
  35. package/lib/shimmer.js +56 -8
  36. package/lib/subscriber-configs.js +17 -0
  37. package/lib/subscribers/application-logs.js +55 -0
  38. package/lib/subscribers/base.js +177 -0
  39. package/lib/subscribers/create-config.js +27 -0
  40. package/lib/subscribers/db-operation.js +21 -0
  41. package/lib/subscribers/db-query.js +54 -0
  42. package/lib/subscribers/db.js +57 -0
  43. package/lib/subscribers/elasticsearch/config.js +49 -0
  44. package/lib/subscribers/elasticsearch/elasticsearch.js +42 -0
  45. package/lib/subscribers/elasticsearch/opensearch.js +15 -0
  46. package/lib/subscribers/elasticsearch/transport.js +14 -0
  47. package/lib/subscribers/ioredis/config.js +37 -0
  48. package/lib/subscribers/ioredis/index.js +48 -0
  49. package/lib/subscribers/mcp-sdk/client-prompt.js +23 -0
  50. package/lib/subscribers/mcp-sdk/client-resource.js +24 -0
  51. package/lib/subscribers/mcp-sdk/client-tool.js +23 -0
  52. package/lib/subscribers/mcp-sdk/client.js +29 -0
  53. package/lib/subscribers/mcp-sdk/config.js +104 -0
  54. package/lib/subscribers/pino/config.js +20 -0
  55. package/lib/subscribers/pino/index.js +102 -0
  56. package/lib/transaction/index.js +4 -1
  57. package/lib/transaction/trace/aggregator.js +7 -9
  58. package/lib/util/camel-case.js +1 -3
  59. package/lib/util/get-package-version.js +34 -0
  60. package/lib/util/urltils.js +19 -13
  61. package/lib/w3c/tracestate.js +3 -3
  62. package/newrelic.js +10 -0
  63. package/package.json +9 -5
  64. package/lib/instrumentation/@elastic/elasticsearch.js +0 -62
  65. package/lib/instrumentation/@opensearch-project/opensearch.js +0 -66
  66. package/lib/instrumentation/ioredis.js +0 -50
  67. package/lib/instrumentation/pino/nr-hooks.js +0 -19
  68. 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