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
@@ -44,28 +44,24 @@ const exists = fs.existsSync
44
44
  let logger = null // Lazy-loaded in `initialize`.
45
45
  let _configInstance = null
46
46
 
47
- const getConfigFileNames = () =>
48
- [process.env.NEW_RELIC_CONFIG_FILENAME, 'newrelic.js', 'newrelic.cjs', 'newrelic.mjs'].filter(
49
- Boolean
50
- )
51
-
52
- const getConfigFileLocations = () =>
53
- [
54
- process.env.NEW_RELIC_HOME,
55
- process.cwd(),
56
- process.env.HOME,
57
- path.join(__dirname, '../../../..'), // above node_modules
58
- // the REPL has no main module
59
- process.mainModule && process.mainModule.filename
60
- ? path.dirname(process.mainModule.filename)
61
- : undefined
62
- ].filter(Boolean)
47
+ const getConfigFileNames = () => [process.env.NEW_RELIC_CONFIG_FILENAME, 'newrelic.js', 'newrelic.cjs', 'newrelic.mjs'].filter(
48
+ Boolean
49
+ )
50
+
51
+ const getConfigFileLocations = () => [
52
+ process.env.NEW_RELIC_HOME,
53
+ process.cwd(),
54
+ process.env.HOME,
55
+ path.join(__dirname, '../../../..'), // above node_modules
56
+ // the REPL has no main module
57
+ process.mainModule && process.mainModule.filename
58
+ ? path.dirname(process.mainModule.filename)
59
+ : undefined
60
+ ].filter(Boolean)
63
61
 
64
62
  function _findConfigFile() {
65
63
  const configFileCandidates = getConfigFileLocations().reduce((files, configPath) => {
66
- const configFiles = getConfigFileNames().map((filename) =>
67
- path.join(path.resolve(configPath), filename)
68
- )
64
+ const configFiles = getConfigFileNames().map((filename) => path.join(path.resolve(configPath), filename))
69
65
 
70
66
  return files.concat(configFiles)
71
67
  }, [])
@@ -6,9 +6,10 @@
6
6
  'use strict'
7
7
 
8
8
  module.exports = class Context {
9
- constructor(transaction, segment) {
9
+ constructor({ transaction, segment, extras = {} } = {}) {
10
10
  this._transaction = transaction
11
11
  this._segment = segment
12
+ this._extras = extras
12
13
  }
13
14
 
14
15
  get segment() {
@@ -19,6 +20,14 @@ module.exports = class Context {
19
20
  return this._transaction
20
21
  }
21
22
 
23
+ get extras() {
24
+ return this._extras
25
+ }
26
+
27
+ set extras(extras) {
28
+ this._extras = { ...this._extras, ...extras }
29
+ }
30
+
22
31
  /**
23
32
  * Constructs a new context from segment about to be bound to context manager
24
33
  * along with the current transaction.
@@ -28,8 +37,8 @@ module.exports = class Context {
28
37
  * @param {Transaction} params.transaction active transaction
29
38
  * @returns {Context} a newly constructed context
30
39
  */
31
- enterSegment({ segment, transaction = this._transaction }) {
32
- return new this.constructor(transaction, segment)
40
+ enterSegment({ segment, transaction = this.transaction }) {
41
+ return new this.constructor({ transaction, segment, extras: this.extras })
33
42
  }
34
43
 
35
44
  /**
@@ -40,6 +49,6 @@ module.exports = class Context {
40
49
  * @returns {Context} a newly constructed context
41
50
  */
42
51
  enterTransaction(transaction) {
43
- return new this.constructor(transaction, transaction?.trace?.root)
52
+ return new this.constructor({ transaction, segment: transaction?.trace?.root, extras: this.extras })
44
53
  }
45
54
  }
package/lib/harvester.js CHANGED
@@ -34,20 +34,18 @@ module.exports = class Harvester {
34
34
  */
35
35
  clear(callback) {
36
36
  Promise.all(
37
- this.aggregators.map((aggregator) => {
38
- return new Promise((resolve) => {
39
- if (aggregator.enabled) {
40
- aggregator.once(`finished_data_send-${aggregator.method}`, function finish() {
41
- resolve()
42
- })
43
-
44
- aggregator.send()
45
- } else {
46
- // No data to flush because aggregator is not enabled
37
+ this.aggregators.map((aggregator) => new Promise((resolve) => {
38
+ if (aggregator.enabled) {
39
+ aggregator.once(`finished_data_send-${aggregator.method}`, function finish() {
47
40
  resolve()
48
- }
49
- })
50
- })
41
+ })
42
+
43
+ aggregator.send()
44
+ } else {
45
+ // No data to flush because aggregator is not enabled
46
+ resolve()
47
+ }
48
+ }))
51
49
  ).then(() => {
52
50
  // Get out of the promise so callback errors aren't treated as
53
51
  // promise rejections.
@@ -196,7 +196,9 @@ module.exports = function initialize(agent, googleGenAi, moduleName, shim) {
196
196
 
197
197
  // Instruments chat completion creation
198
198
  // and creates the LLM events
199
- shim.record(models.prototype, 'generateContentInternal',
199
+ shim.record(
200
+ models.prototype,
201
+ 'generateContentInternal',
200
202
  function wrapGenerateContent(shim, func, name, args) {
201
203
  const [request] = args
202
204
 
@@ -222,7 +224,9 @@ module.exports = function initialize(agent, googleGenAi, moduleName, shim) {
222
224
 
223
225
  // Instruments chat completion streaming
224
226
  // and creates the LLM events
225
- shim.record(models.prototype, 'generateContentStreamInternal',
227
+ shim.record(
228
+ models.prototype,
229
+ 'generateContentStreamInternal',
226
230
  function wrapGenerateContentStream(shim, func, name, args) {
227
231
  if (!agent.config.ai_monitoring.streaming.enabled) {
228
232
  shim.logger.warn(
@@ -241,7 +245,8 @@ module.exports = function initialize(agent, googleGenAi, moduleName, shim) {
241
245
  addLlmMeta({ agent, transaction })
242
246
  }
243
247
  })
244
- })
248
+ }
249
+ )
245
250
 
246
251
  // Instruments embedding creation
247
252
  // and creates LlmEmbedding event
@@ -204,12 +204,10 @@ function wrapPreHandlers(shim, container, path) {
204
204
  }
205
205
 
206
206
  function wrapPreHandler(shim, container, path) {
207
- return shim.record(container, (shim) => {
208
- return new SegmentSpec({
209
- name: [shim.HAPI, ' pre handler: ', '(', path, ')'].join(''),
210
- recorder: record
211
- })
212
- })
207
+ return shim.record(container, (shim) => new SegmentSpec({
208
+ name: [shim.HAPI, ' pre handler: ', '(', path, ')'].join(''),
209
+ recorder: record
210
+ }))
213
211
  }
214
212
 
215
213
  function wrapRouteHandler(shim, handler, path) {
@@ -18,20 +18,18 @@ module.exports = function initialize(agent, core, moduleName, shim) {
18
18
  return
19
19
  }
20
20
 
21
- shim.wrap(core.BaseExceptionFilter.prototype, 'handleUnknownError', (shim, original) => {
22
- return function wrappedHandleUnknownError(exception) {
23
- const transaction = shim.tracer.getTransaction()
24
- if (transaction) {
25
- shim.agent.errors.add(transaction, exception)
26
- logger.trace(exception, 'Captured error handled by Nest.js exception filter.')
27
- } else {
28
- logger.trace(
29
- exception,
30
- 'Ignoring error handled by Nest.js exception filter: not in a transaction'
31
- )
32
- }
33
-
34
- return original.apply(this, arguments)
21
+ shim.wrap(core.BaseExceptionFilter.prototype, 'handleUnknownError', (shim, original) => function wrappedHandleUnknownError(exception) {
22
+ const transaction = shim.tracer.getTransaction()
23
+ if (transaction) {
24
+ shim.agent.errors.add(transaction, exception)
25
+ logger.trace(exception, 'Captured error handled by Nest.js exception filter.')
26
+ } else {
27
+ logger.trace(
28
+ exception,
29
+ 'Ignoring error handled by Nest.js exception filter: not in a transaction'
30
+ )
35
31
  }
32
+
33
+ return original.apply(this, arguments)
36
34
  })
37
35
  }
@@ -204,7 +204,7 @@ function recordEmbeddingMessage({
204
204
  return
205
205
  }
206
206
 
207
- const embeddings = bedrockCommand.prompt.map(prompt => new LlmEmbedding({
207
+ const embeddings = bedrockCommand.prompt.map((prompt) => new LlmEmbedding({
208
208
  agent,
209
209
  segment,
210
210
  transaction,
@@ -214,7 +214,7 @@ function recordEmbeddingMessage({
214
214
  isError: err !== null
215
215
  }))
216
216
 
217
- embeddings.forEach(embedding => {
217
+ embeddings.forEach((embedding) => {
218
218
  recordEvent({ agent, type: 'LlmEmbedding', msg: embedding })
219
219
  })
220
220
 
@@ -11,7 +11,6 @@ const urltils = require('../../util/urltils')
11
11
  const logger = require('../../logger').child({ component: 'outbound' })
12
12
  const shimmer = require('../../shimmer')
13
13
  const url = require('url')
14
- const copy = require('../../util/copy')
15
14
  const symbols = require('../../symbols')
16
15
  const synthetics = require('../../synthetics')
17
16
  const { URL } = require('node:url')
@@ -57,24 +56,6 @@ function getDefaultHostName(opts) {
57
56
  return opts.hostname || opts.host || DEFAULT_HOST
58
57
  }
59
58
 
60
- /**
61
- * Parses http opts to an object
62
- * If string will call url.parse, otherwise it will
63
- * do a shallow copy
64
- *
65
- * @param {string|object} opts a url string or HTTP request options
66
- * @returns {object} parsed http opts
67
- */
68
- function parseOpts(opts) {
69
- if (typeof opts === 'string') {
70
- opts = url.parse(opts)
71
- } else {
72
- opts = copy.shallow(opts)
73
- }
74
-
75
- return opts
76
- }
77
-
78
59
  /**
79
60
  * Extracts host, hostname, port from http request options
80
61
  *
@@ -122,8 +103,6 @@ function extractHostPortViaProxy(opts) {
122
103
  * @returns {object} The instrumented outbound HTTP request.
123
104
  */
124
105
  module.exports = function instrumentOutbound(agent, opts, makeRequest) {
125
- opts = parseOpts(opts)
126
-
127
106
  const viaProxy = extractHostPortViaProxy(opts)
128
107
  const { host, hostname, port } = viaProxy ? viaProxy : extractHostPort(opts)
129
108
 
@@ -265,23 +244,29 @@ function assignOutgoingHeaders(currentHeaders, outboundHeaders) {
265
244
  function applySegment({ opts, makeRequest, host, port, hostname, segment, config }) {
266
245
  segment.start()
267
246
  const request = makeRequest(opts)
268
- const parsed = urltils.scrubAndParseParameters(request.path)
269
- parsed.path = urltils.obfuscatePath(config, parsed.path)
247
+
248
+ // Attempt to parse into new URL.
249
+ // If no href, revert to url.parse.
250
+ // Rarely, the href doesn't contain the
251
+ // path, so we have to use url.parse to get the path.
252
+ const urlObject = (opts?.href?.includes(request.path)) ? new URL(opts.href) : url.parse(request.path, true)
253
+ const parsed = urltils.scrubAndParseParameters(urlObject)
254
+ const obfuscatedPath = urltils.obfuscatePath(config, parsed.path)
255
+
270
256
  const proto = parsed.protocol || opts.protocol || 'http:'
271
- segment.name += parsed.path
257
+ segment.name += obfuscatedPath
272
258
  segment.captureExternalAttributes({
273
259
  protocol: proto,
274
260
  hostname,
275
261
  host,
276
262
  method: opts.method,
277
263
  port,
278
- path: parsed.path,
264
+ path: obfuscatedPath,
279
265
  queryParams: parsed.parameters
280
266
  })
281
267
  request[symbols.segment] = segment
282
268
  return request
283
269
  }
284
-
285
270
  /**
286
271
  * Wrap the emit method. We're doing a special wrapper instead of using
287
272
  * `tracer.bindEmitter` because we want to do some logic based on certain
@@ -42,15 +42,13 @@ function nrSend(shim, fn, name, args) {
42
42
  promise: true,
43
43
  destinationName: data.topic,
44
44
  destinationType: shim.TOPIC,
45
- messageHeaders: (inject) => {
46
- return data.messages.map((msg) => {
47
- if (msg.headers) {
48
- return inject(msg.headers)
49
- }
50
- msg.headers = {}
45
+ messageHeaders: (inject) => data.messages.map((msg) => {
46
+ if (msg.headers) {
51
47
  return inject(msg.headers)
52
- })
53
- }
48
+ }
49
+ msg.headers = {}
50
+ return inject(msg.headers)
51
+ })
54
52
  })
55
53
  }
56
54
 
@@ -74,16 +72,12 @@ function nrSendBatch(shim, fn, name, args) {
74
72
  promise: true,
75
73
  destinationName: data.topicMessages[0].topic,
76
74
  destinationType: shim.TOPIC,
77
- messageHeaders: (inject) => {
78
- return data.topicMessages.map((tm) => {
79
- return tm.messages.map((m) => {
80
- if (m.headers) {
81
- return inject(m.headers)
82
- }
83
- m.headers = {}
84
- return inject(m.headers)
85
- })
86
- })
87
- }
75
+ messageHeaders: (inject) => data.topicMessages.map((tm) => tm.messages.map((m) => {
76
+ if (m.headers) {
77
+ return inject(m.headers)
78
+ }
79
+ m.headers = {}
80
+ return inject(m.headers)
81
+ }))
88
82
  })
89
83
  }
@@ -64,13 +64,13 @@ function getMessagesFromRequest(request, shim) {
64
64
 
65
65
  if (Array.isArray(request?.input)) {
66
66
  // Handle array of input messages
67
- messages = request.input.filter(msg => msg?.content && msg?.role)
67
+ messages = request.input.filter((msg) => msg?.content && msg?.role)
68
68
  } else if (typeof request?.input === 'string') {
69
69
  // Handle single string input as a user message
70
70
  messages = [{ content: request.input, role: 'user' }]
71
71
  } else if (Array.isArray(request?.messages)) {
72
72
  // Handle array of messages
73
- messages = request.messages.filter(msg => msg?.content && msg?.role)
73
+ messages = request.messages.filter((msg) => msg?.content && msg?.role)
74
74
  } else {
75
75
  shim.logger.warn('No valid messages found in OpenAI request object.')
76
76
  }
@@ -9,7 +9,6 @@ const cat = require('../util/cat')
9
9
  const recordExternal = require('../metrics/recorders/http_external')
10
10
  const logger = require('../logger').child({ component: 'undici' })
11
11
  const NAMES = require('../metrics/names')
12
- const symbols = require('../symbols')
13
12
  // eslint-disable-next-line n/no-unsupported-features/node-builtins
14
13
  const diagnosticsChannel = require('diagnostics_channel')
15
14
  const synthetics = require('../synthetics')
@@ -27,13 +26,10 @@ const channels = [
27
26
  * See: https://github.com/nodejs/undici/blob/main/docs/api/DiagnosticsChannel.md
28
27
  *
29
28
  * @param agent
30
- * @param _undici
31
- * @param _modName
32
- * @param shim
33
29
  */
34
- module.exports = function addUndiciChannels(agent, _undici, _modName, shim) {
30
+ module.exports = function addUndiciChannels(agent) {
35
31
  channels.forEach(({ channel, hook }, index) => {
36
- const boundHook = hook.bind(null, shim)
32
+ const boundHook = hook.bind(null, agent)
37
33
  diagnosticsChannel.subscribe(channel, boundHook)
38
34
  // store the bound hook for unsubscription later
39
35
  channels[index].boundHook = boundHook
@@ -75,18 +71,25 @@ function addDTHeaders({ transaction, config, request }) {
75
71
  * Creates the external segment with url, procedure and request.parameters attributes
76
72
  *
77
73
  * @param {object} params object to fn
78
- * @param {Shim} params.shim instance of shim
79
- * @param {object} params.request undici request object
80
- * @param {TraceSegment} params.segment current active, about to be parent of external segment
74
+ * @param {Agent} params.agent NR agent instance
75
+ * @param {object} params.context active context
76
+ * @param params.request
81
77
  */
82
- function createExternalSegment({ shim, request, segment }) {
78
+ function createExternalSegment({ agent, request, context }) {
83
79
  const url = new URL(request.origin + request.path)
84
- const obfuscatedPath = urltils.obfuscatePath(shim.agent.config, url.pathname)
80
+ const obfuscatedPath = urltils.obfuscatePath(agent.config, url.pathname)
85
81
  const name = NAMES.EXTERNAL.PREFIX + url.host + obfuscatedPath
82
+ const transaction = context?.transaction
83
+ const parent = context?.extras?.undiciParent
86
84
  // Metrics for `External/<host>` will have a suffix of undici
87
85
  // We will have to see if this matters for people only using fetch
88
86
  // It's undici under the hood so ¯\_(ツ)_/¯
89
- const externalSegment = shim.createSegment(name, recordExternal(url.host, 'undici'), segment)
87
+ const externalSegment = agent.tracer.createSegment({
88
+ name,
89
+ recorder: recordExternal(url.host, 'undici'),
90
+ parent,
91
+ transaction
92
+ })
90
93
 
91
94
  // the captureExternalAttributes expects queryParams to be an object, do conversion
92
95
  // to object see: https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
@@ -94,7 +97,11 @@ function createExternalSegment({ shim, request, segment }) {
94
97
 
95
98
  if (externalSegment) {
96
99
  externalSegment.start()
97
- shim.setActiveSegment(externalSegment)
100
+ // storing the undici external segment in the context extras
101
+ // this has to be done because the various hook points produce different segments
102
+ // and we need to be able to access the segment later
103
+ context.extras = { undiciSegment: externalSegment }
104
+ agent.tracer.setSegment({ segment: externalSegment, transaction })
98
105
  externalSegment.captureExternalAttributes({
99
106
  protocol: url.protocol,
100
107
  hostname: url.hostname,
@@ -104,8 +111,6 @@ function createExternalSegment({ shim, request, segment }) {
104
111
  path: obfuscatedPath,
105
112
  queryParams
106
113
  })
107
-
108
- request[symbols.segment] = externalSegment
109
114
  }
110
115
  }
111
116
 
@@ -115,15 +120,18 @@ function createExternalSegment({ shim, request, segment }) {
115
120
  * external segment with the standard url/procedure/request.parameters
116
121
  * attributes. We will also attach relevant DT headers to outgoing http request.
117
122
  *
118
- * @param {Shim} shim instance of shim
123
+ * @param {Agent} agent NR agent instance
119
124
  * @param {object} params object from undici hook
120
125
  * @param {object} params.request undici request object
121
126
  */
122
- function requestCreateHook(shim, { request }) {
123
- const { config } = shim.agent
124
- const { transaction, segment } = shim.tracer.getContext()
125
- request[symbols.parentSegment] = segment
126
- request[symbols.transaction] = transaction
127
+ function requestCreateHook(agent, { request }) {
128
+ const { config } = agent
129
+ const context = agent.tracer.getContext()
130
+ const { segment, transaction } = context
131
+ // storing the parent of the undici external segment in the context extras
132
+ // this has to be done because the various hook points produce different segments
133
+ // and we need to be able to access the segment later
134
+ context.extras = { undiciParent: segment }
127
135
  if (!(segment || transaction) || segment?.opaque) {
128
136
  logger.trace(
129
137
  'Not capturing data for outbound request (%s) because parent segment opaque (%s)',
@@ -135,7 +143,7 @@ function requestCreateHook(shim, { request }) {
135
143
  }
136
144
 
137
145
  try {
138
- createExternalSegment({ shim, request, segment })
146
+ createExternalSegment({ agent, request, context })
139
147
  addDTHeaders({ transaction, config, request })
140
148
  } catch (err) {
141
149
  logger.warn(err, 'Unable to create external segment')
@@ -147,15 +155,15 @@ function requestCreateHook(shim, { request }) {
147
155
  * We will add the relevant http response attributes to active segment.
148
156
  * Also add CAT specific keys to active segment.
149
157
  *
150
- * @param {Shim} shim instance of shim
158
+ * @param {object} agent
151
159
  * @param {object} params object from undici hook
152
- * @param {object} params.request undici request object
153
160
  * @param {object} params.response { statusCode, headers, statusText }
154
161
  */
155
- function requestHeadersHook(shim, { request, response }) {
156
- const { config } = shim.agent
157
- const activeSegment = request[symbols.segment]
158
- const transaction = request[symbols.transaction]
162
+ function requestHeadersHook(agent, { response }) {
163
+ const { config } = agent
164
+ const context = agent.tracer.getContext()
165
+ const activeSegment = context?.extras?.undiciSegment
166
+ const transaction = context?.transaction
159
167
  if (!activeSegment) {
160
168
  return
161
169
  }
@@ -185,37 +193,37 @@ function requestHeadersHook(shim, { request, response }) {
185
193
  * Gets the active segment, parent segment and transaction from given ctx(request, client connector)
186
194
  * and ends segment and sets the previous parent segment as the active segment. If an error exists it will add the error to the transaction
187
195
  *
188
- * @param {Shim} shim instance of shim
196
+ * @param {Agent} agent NR agent instance
189
197
  * @param {object} params object from undici hook
190
- * @param {object} params.request or client connector
191
198
  * @param {Error} params.error error from undici request
192
199
  */
193
- function endAndRestoreSegment(shim, { request, error }) {
194
- const { config } = shim.agent
195
- const activeSegment = request[symbols.segment]
196
- const parentSegment = request[symbols.parentSegment]
197
- const tx = request[symbols.transaction]
200
+ function endAndRestoreSegment(agent, { error }) {
201
+ const { config } = agent
202
+ const context = agent.tracer.getContext()
203
+ const activeSegment = context?.extras?.undiciSegment
204
+ const parentSegment = context?.extras?.undiciParent
205
+ const tx = context?.transaction
198
206
  if (activeSegment) {
199
207
  activeSegment.end()
200
208
  }
201
209
 
202
210
  if (error && tx && config.feature_flag.undici_error_tracking === true) {
203
- handleError(shim, tx, error)
211
+ handleError(agent, tx, error)
204
212
  }
205
213
 
206
214
  if (parentSegment) {
207
- shim.setActiveSegment(parentSegment)
215
+ agent.tracer.setSegment({ segment: parentSegment, transaction: tx })
208
216
  }
209
217
  }
210
218
 
211
219
  /**
212
220
  * Adds the error to the active transaction
213
221
  *
214
- * @param {Shim} shim instance of shim
222
+ * @param {Agent} agent NR agent instance
215
223
  * @param {Transaction} tx active transaction
216
224
  * @param {Error} error error from undici request
217
225
  */
218
- function handleError(shim, tx, error) {
226
+ function handleError(agent, tx, error) {
219
227
  logger.trace(error, 'Captured outbound error on behalf of the user.')
220
- shim.agent.errors.add(tx, error)
228
+ agent.errors.add(tx, error)
221
229
  }
@@ -11,8 +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
- '@elastic/elasticsearch': { type: InstrumentationDescriptor.TYPE_DATASTORE },
15
- '@opensearch-project/opensearch': { type: InstrumentationDescriptor.TYPE_DATASTORE },
16
14
  '@google/genai': { type: InstrumentationDescriptor.TYPE_GENERIC },
17
15
  '@grpc/grpc-js': { module: './instrumentation/grpc-js' },
18
16
  '@hapi/hapi': { type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK },
@@ -30,7 +28,6 @@ module.exports = function instrumentations() {
30
28
  express: { type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK },
31
29
  fastify: { type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK },
32
30
  'generic-pool': { type: InstrumentationDescriptor.TYPE_GENERIC },
33
- ioredis: { type: InstrumentationDescriptor.TYPE_DATASTORE },
34
31
  kafkajs: { type: InstrumentationDescriptor.TYPE_MESSAGE },
35
32
  koa: { module: './instrumentation/koa' },
36
33
  langchain: { module: './instrumentation/langchain' },
@@ -40,7 +37,6 @@ module.exports = function instrumentations() {
40
37
  next: { module: './instrumentation/nextjs' },
41
38
  openai: { type: InstrumentationDescriptor.TYPE_GENERIC },
42
39
  pg: { type: InstrumentationDescriptor.TYPE_DATASTORE },
43
- pino: { module: './instrumentation/pino' },
44
40
  q: { type: null },
45
41
  redis: { type: InstrumentationDescriptor.TYPE_DATASTORE },
46
42
  restify: { type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK },
@@ -62,6 +58,19 @@ module.exports = function instrumentations() {
62
58
  'fancy-log': { type: InstrumentationDescriptor.TYPE_TRACKING },
63
59
  knex: { type: InstrumentationDescriptor.TYPE_TRACKING },
64
60
  loglevel: { type: InstrumentationDescriptor.TYPE_TRACKING },
65
- npmlog: { type: InstrumentationDescriptor.TYPE_TRACKING }
61
+ npmlog: { type: InstrumentationDescriptor.TYPE_TRACKING },
62
+
63
+ /**
64
+ * The modules below are listed here is a temporary solution to maintaining
65
+ * the Supportability/Features/onRequire/<module> metrics for libraries
66
+ * that have been migrated to use tracing chanel instrumentation.
67
+ * Once orchestrion can emit the package version, these can be removed.
68
+ * {@link https://github.com/newrelic/node-newrelic/issues/3308 Github Issue}
69
+ */
70
+ '@elastic/elasticsearch': { type: InstrumentationDescriptor.TYPE_TRACKING },
71
+ '@modelcontextprotocol/sdk/client/index.js': { type: InstrumentationDescriptor.TYPE_TRACKING },
72
+ '@opensearch-project/opensearch': { type: InstrumentationDescriptor.TYPE_TRACKING },
73
+ ioredis: { type: InstrumentationDescriptor.TYPE_TRACKING },
74
+ pino: { type: InstrumentationDescriptor.TYPE_TRACKING }
66
75
  }
67
76
  }
@@ -56,7 +56,7 @@ function stringifyConverseChunkedMessage(chunks) {
56
56
  return `<tool_result>${
57
57
  stringifyConverseChunkedMessage(
58
58
  (chunk.toolResult.content ?? [])
59
- .filter(subChunk => !('toolUse' in subChunk || 'toolResult' in subChunk))
59
+ .filter((subChunk) => !('toolUse' in subChunk || 'toolResult' in subChunk))
60
60
  )
61
61
  }</tool_result>`
62
62
  } else if ('guardContent' in chunk) {
@@ -199,6 +199,13 @@ AI.LANGCHAIN = {
199
199
  VECTORSTORE: `${AI.VECTORSTORE}/Langchain`
200
200
  }
201
201
 
202
+ const MCP = {
203
+ TRACKING_PREFIX: `${AI.TRACKING_PREFIX}/MCP`,
204
+ TOOL: `${AI.TOOL}/MCP`,
205
+ RESOURCE: 'Llm/resource/MCP',
206
+ PROMPT: 'Llm/prompt/MCP'
207
+ }
208
+
202
209
  const RESTIFY = {
203
210
  PREFIX: 'Restify/'
204
211
  }
@@ -380,6 +387,7 @@ module.exports = {
380
387
  KAFKA,
381
388
  LOOP,
382
389
  LOGGING,
390
+ MCP,
383
391
  MEMCACHE,
384
392
  MEMORY,
385
393
  MESSAGE_TRANSACTION,
@@ -109,9 +109,7 @@ MetricNormalizer.prototype.load = function load(json) {
109
109
  /* I (FLN) always forget this, so making a note: JS sort is always
110
110
  * IN-PLACE, even though it returns the sorted array.
111
111
  */
112
- this.rules.sort((a, b) => {
113
- return a.precedence - b.precedence
114
- })
112
+ this.rules.sort((a, b) => a.precedence - b.precedence)
115
113
 
116
114
  logger.debug('Loaded %s %s normalization rule(s).', this.rules.length, this.type)
117
115
  }