newrelic 13.2.0 → 13.3.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 (59) hide show
  1. package/NEWS.md +44 -0
  2. package/THIRD_PARTY_NOTICES.md +219 -8
  3. package/api.js +1 -1
  4. package/esm-loader.mjs +6 -1
  5. package/lib/instrumentation/@azure/functions.js +8 -46
  6. package/lib/instrumentation/core/http-outbound.js +26 -10
  7. package/lib/instrumentation/core/http.js +20 -118
  8. package/lib/instrumentations.js +0 -14
  9. package/lib/otel/logs/index.js +80 -0
  10. package/lib/otel/metrics/index.js +119 -0
  11. package/lib/otel/setup-signal.js +37 -0
  12. package/lib/otel/setup.js +15 -25
  13. package/lib/otel/{attr-reconciler.js → traces/attr-reconciler.js} +2 -2
  14. package/lib/otel/{exception-mapping.js → traces/exception-mapping.js} +1 -1
  15. package/lib/otel/traces/index.js +40 -0
  16. package/lib/otel/{rules.js → traces/rules.js} +1 -1
  17. package/lib/otel/{segment-synthesis.js → traces/segment-synthesis.js} +5 -4
  18. package/lib/otel/{segments → traces/segments}/consumer.js +4 -4
  19. package/lib/otel/{segments → traces/segments}/database.js +6 -6
  20. package/lib/otel/{segments → traces/segments}/http-external.js +11 -8
  21. package/lib/otel/traces/segments/index.js +22 -0
  22. package/lib/otel/{segments → traces/segments}/internal.js +1 -1
  23. package/lib/otel/{segments → traces/segments}/producer.js +2 -2
  24. package/lib/otel/{segments → traces/segments}/server.js +4 -4
  25. package/lib/otel/{segments → traces/segments}/utils.js +1 -1
  26. package/lib/otel/{span-processor.js → traces/span-processor.js} +13 -19
  27. package/lib/otel/{transformation-rules.json → traces/transformation-rules.json} +3 -2
  28. package/lib/otel/{utils.js → traces/utils.js} +2 -1
  29. package/lib/serverless/aws-lambda.js +7 -68
  30. package/lib/shim/webframework-shim/middleware.js +1 -1
  31. package/lib/shimmer.js +2 -2
  32. package/lib/subscriber-configs.js +2 -1
  33. package/lib/subscribers/base.js +30 -0
  34. package/lib/subscribers/elasticsearch/config.js +1 -0
  35. package/lib/subscribers/mcp-sdk/client-request.js +66 -0
  36. package/lib/subscribers/mcp-sdk/config.js +13 -75
  37. package/lib/subscribers/openai/base.js +21 -0
  38. package/lib/subscribers/openai/chat-responses.js +15 -0
  39. package/lib/subscribers/openai/chat.js +85 -0
  40. package/lib/subscribers/openai/client.js +31 -0
  41. package/lib/subscribers/openai/config.js +85 -0
  42. package/lib/subscribers/openai/embeddings.js +54 -0
  43. package/lib/subscribers/openai/utils.js +349 -0
  44. package/lib/transaction/index.js +157 -56
  45. package/lib/transaction/trace/segment.js +5 -2
  46. package/lib/util/urltils.js +158 -192
  47. package/lib/utilization/common.js +0 -6
  48. package/package.json +3 -4
  49. package/lib/instrumentation/openai.js +0 -482
  50. package/lib/otel/logs/bootstrap-logs.js +0 -84
  51. package/lib/otel/metrics/bootstrap-metrics.js +0 -117
  52. package/lib/otel/segments/index.js +0 -22
  53. package/lib/patch-module.js +0 -70
  54. package/lib/subscribers/mcp-sdk/client-prompt.js +0 -23
  55. package/lib/subscribers/mcp-sdk/client-resource.js +0 -24
  56. package/lib/subscribers/mcp-sdk/client-tool.js +0 -23
  57. package/lib/subscribers/mcp-sdk/client.js +0 -29
  58. /package/lib/otel/{constants.js → traces/constants.js} +0 -0
  59. /package/lib/otel/{sampler.js → traces/sampler.js} +0 -0
@@ -5,12 +5,12 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- const parseSql = require('../../db/query-parsers/sql')
9
- const recordQueryMetrics = require('../../metrics/recorders/database')
10
- const recordOperationMetrics = require('../../metrics/recorders/database-operation')
11
- const ParsedStatement = require('../../db/parsed-statement')
12
- const metrics = require('../../metrics/names')
13
- const { transformTemplate } = require('../utils')
8
+ const parseSql = require('#agentlib/db/query-parsers/sql.js')
9
+ const recordQueryMetrics = require('#agentlib/metrics/recorders/database.js')
10
+ const recordOperationMetrics = require('#agentlib/metrics/recorders/database-operation.js')
11
+ const ParsedStatement = require('#agentlib/db/parsed-statement.js')
12
+ const metrics = require('#agentlib/metrics/names.js')
13
+ const { transformTemplate } = require('../utils.js')
14
14
 
15
15
  module.exports = function createDbSegment(agent, otelSpan, rule) {
16
16
  const context = agent.tracer.getContext()
@@ -5,10 +5,10 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- const recordExternal = require('../../metrics/recorders/http_external')
9
- const urltils = require('../../util/urltils')
10
- const { transformTemplate } = require('../utils')
11
- const { UNKNOWN } = require('../constants')
8
+ const recordExternal = require('#agentlib/metrics/recorders/http_external.js')
9
+ const urltils = require('#agentlib/util/urltils.js')
10
+ const { transformTemplate } = require('../utils.js')
11
+ const { UNKNOWN } = require('../constants.js')
12
12
 
13
13
  function assignHost(otelSpan, segmentTransformation) {
14
14
  let host = UNKNOWN
@@ -23,18 +23,21 @@ function assignHost(otelSpan, segmentTransformation) {
23
23
  return host
24
24
  }
25
25
 
26
- module.exports = function createHttpExternalSegment(agent, otelSpan, rule) {
26
+ module.exports = function createHttpExternalSegment(agent, otelSpan, rule, logger) {
27
27
  const context = agent.tracer.getContext()
28
28
  const segmentTransformation = rule.segmentTransformation
29
29
 
30
30
  const host = assignHost(otelSpan, segmentTransformation)
31
31
  const url = otelSpan?.attributes[segmentTransformation?.url]
32
32
  const system = otelSpan?.attributes[segmentTransformation?.system] ?? 'http'
33
- let parsedUrl
34
33
  let obfuscatedPath = `/${UNKNOWN}`
35
34
  if (url) {
36
- parsedUrl = new URL(url)
37
- obfuscatedPath = urltils.obfuscatePath(agent.config, parsedUrl.pathname)
35
+ try {
36
+ const parsedUrl = new URL(url)
37
+ obfuscatedPath = urltils.obfuscatePath(agent.config, parsedUrl.pathname)
38
+ } catch (err) {
39
+ logger.debug('Could not parse URL %s: %s', url, err.message)
40
+ }
38
41
  }
39
42
 
40
43
  const name = transformTemplate(segmentTransformation?.name?.template, { host, path: obfuscatedPath, ...otelSpan?.attributes })
@@ -0,0 +1,22 @@
1
+ /*
2
+ * Copyright 2024 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const createConsumerSegment = require('./consumer.js')
9
+ const createDbSegment = require('./database.js')
10
+ const createHttpExternalSegment = require('./http-external.js')
11
+ const createProducerSegment = require('./producer.js')
12
+ const createServerSegment = require('./server.js')
13
+ const createInternalSegment = require('./internal.js')
14
+
15
+ module.exports = {
16
+ createConsumerSegment,
17
+ createDbSegment,
18
+ createHttpExternalSegment,
19
+ createInternalSegment,
20
+ createProducerSegment,
21
+ createServerSegment
22
+ }
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  'use strict'
7
- const genericRecorder = require('../../metrics/recorders/generic')
7
+ const genericRecorder = require('#agentlib/metrics/recorders/generic.js')
8
8
 
9
9
  module.exports = function createInternalSegment(agent, otelSpan, rule) {
10
10
  const context = agent.tracer.getContext()
@@ -5,8 +5,8 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- const genericRecorder = require('../../metrics/recorders/generic')
9
- const { transformTemplate } = require('../utils')
8
+ const genericRecorder = require('#agentlib/metrics/recorders/generic.js')
9
+ const { transformTemplate } = require('../utils.js')
10
10
 
11
11
  module.exports = function createProducerSegment(agent, otelSpan, rule) {
12
12
  const context = agent.tracer.getContext()
@@ -5,10 +5,10 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- const Transaction = require('../../transaction')
9
- const httpRecorder = require('../../metrics/recorders/http')
10
- const { propagateTraceContext } = require('./utils')
11
- const createInternalSegment = require('./internal')
8
+ const Transaction = require('#agentlib/transaction/index.js')
9
+ const httpRecorder = require('#agentlib/metrics/recorders/http.js')
10
+ const { propagateTraceContext } = require('./utils.js')
11
+ const createInternalSegment = require('./internal.js')
12
12
 
13
13
  module.exports = function createServerSegment(agent, otelSpan, rule) {
14
14
  const activeTx = agent.tracer.getTransaction()
@@ -5,7 +5,7 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- const Traceparent = require('../../w3c/traceparent')
8
+ const Traceparent = require('#agentlib/w3c/traceparent.js')
9
9
 
10
10
  /**
11
11
  * Accepts trace context payload if span has a parent. It will use the
@@ -7,19 +7,18 @@
7
7
 
8
8
  const { hrTimeToMilliseconds } = require('@opentelemetry/core')
9
9
  const { SpanKind } = require('@opentelemetry/api')
10
- const urltils = require('../util/urltils')
10
+ const urltils = require('#agentlib/util/urltils.js')
11
11
 
12
- const AttributeReconciler = require('./attr-reconciler')
13
- const SegmentSynthesizer = require('./segment-synthesis')
14
- const { otelSynthesis } = require('../symbols')
15
- const { DESTINATIONS } = require('../config/attribute-filter')
16
- const { assignToTarget, buildRuleMappings, extractAttributeValue, processRegex, transformTemplate } = require('./utils')
17
- const defaultLogger = require('../logger').child({ component: 'span-processor' })
12
+ const AttributeReconciler = require('./attr-reconciler.js')
13
+ const SegmentSynthesizer = require('./segment-synthesis.js')
14
+ const { otelSynthesis } = require('#agentlib/symbols.js')
15
+ const { assignToTarget, buildRuleMappings, extractAttributeValue, processRegex, transformTemplate } = require('./utils.js')
16
+ const defaultLogger = require('#agentlib/logger.js').child({ component: 'span-processor' })
18
17
 
19
18
  const {
20
19
  SPAN_STATUS_CODE
21
20
  } = require('./constants')
22
- const exceptionAttr = require('./exception-mapping')
21
+ const exceptionAttr = require('./exception-mapping.js')
23
22
 
24
23
  module.exports = class NrSpanProcessor {
25
24
  #reconciler
@@ -215,24 +214,19 @@ module.exports = class NrSpanProcessor {
215
214
 
216
215
  try {
217
216
  const requestUrl = new URL(httpUrl)
218
- transaction.parsedUrl = requestUrl
219
217
  transaction.url = urltils.obfuscatePath(this.agent.config, requestUrl.pathname)
220
218
  transaction.applyUserNamingRules(requestUrl.pathname)
221
- } catch {
219
+ // If `http.route` was not emitted on server span, name the transaction from the path
220
+ // to avoid transactions being named `*`
221
+ transaction.nameState.appendPathIfEmpty(requestUrl.pathname)
222
+ } catch (err) {
223
+ this.logger.debug('Could not parse URL from span for transaction URL: %s, err: %s', httpUrl, err.message)
222
224
  transaction.url = httpUrl
223
225
  }
224
226
 
225
- transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_COMMON, 'request.uri', transaction.url)
226
-
227
- // If `http.route` was not emitted on server span, name the transaction from the path
228
- // to avoid transactions being named `*`
229
- if (transaction.parsedUrl) {
230
- transaction.nameState.appendPathIfEmpty(transaction.parsedUrl?.path)
231
- }
232
-
233
227
  // Add the status code to transaction name
234
228
  if (transaction.statusCode) {
235
- transaction.finalizeNameFromUri(transaction.parsedUrl, transaction.statusCode)
229
+ transaction.finalizeNameFromWeb(transaction.statusCode)
236
230
  }
237
231
  }
238
232
  }
@@ -731,8 +731,9 @@
731
731
  ],
732
732
  "segment": {
733
733
  "host": "server.address",
734
+ "url": "url.full",
734
735
  "name": {
735
- "template": "External/${server.address}${url.path}"
736
+ "template": "External/${host}${path}"
736
737
  }
737
738
  }
738
739
  },
@@ -798,7 +799,7 @@
798
799
  "host": "net.peer.name",
799
800
  "url": "http.url",
800
801
  "name": {
801
- "template": "External/${net.peer.name}${path}"
802
+ "template": "External/${host}${path}"
802
803
  }
803
804
  }
804
805
  },
@@ -4,7 +4,8 @@
4
4
  */
5
5
 
6
6
  'use strict'
7
- const { DESTINATIONS } = require('../config/attribute-filter')
7
+
8
+ const { DESTINATIONS } = require('#agentlib/config/attribute-filter.js')
8
9
 
9
10
  /**
10
11
  * Transforms a template string by replacing placeholders with values from the data object.
@@ -6,13 +6,11 @@
6
6
  'use strict'
7
7
 
8
8
  const apiGateway = require('./api-gateway')
9
- const headerAttributes = require('../header-attributes')
10
9
  const get = require('../util/get')
11
10
  const logger = require('../logger').child({ component: 'aws-lambda' })
12
11
  const recordBackground = require('../metrics/recorders/other')
13
12
  const recordWeb = require('../metrics/recorders/http')
14
13
  const TransactionShim = require('../shim/transaction-shim')
15
- const urltils = require('../util/urltils')
16
14
  const specs = require('../shim/specs')
17
15
 
18
16
  // CONSTANTS
@@ -255,7 +253,7 @@ class AwsLambda {
255
253
 
256
254
  if (isApiGatewayLambdaProxy) {
257
255
  const webRequest = new apiGateway.LambdaProxyWebRequest(event)
258
- setWebRequest(shim, transaction, webRequest)
256
+ setWebRequest(transaction, webRequest)
259
257
  resultProcessor = getApiGatewayLambdaProxyResultProcessor(transaction)
260
258
  }
261
259
  const cbIndex = args.length - 1
@@ -411,57 +409,21 @@ function getApiGatewayLambdaProxyResultProcessor(transaction) {
411
409
  return function processApiGatewayLambdaProxyResponse(response) {
412
410
  if (apiGateway.isValidLambdaProxyResponse(response)) {
413
411
  const webResponse = new apiGateway.LambdaProxyWebResponse(response)
414
- setWebResponse(transaction, webResponse)
412
+ transaction.finalizeWeb({ end: false, statusCode: webResponse.statusCode, headers: webResponse.headers })
415
413
  } else {
416
414
  logger.debug('Did not contain a valid API Gateway Lambda Proxy response.')
417
415
  }
418
416
  }
419
417
  }
420
418
 
421
- function setWebRequest(shim, transaction, request) {
422
- transaction.type = shim.WEB
423
-
424
- const segment = transaction.baseSegment
425
-
426
- transaction.url = urltils.scrub(request.url.path)
427
- transaction.verb = request.method
428
- transaction.trace.attributes.addAttribute(
429
- ATTR_DEST.TRANS_COMMON,
430
- 'request.method',
431
- request.method
432
- )
433
-
434
- segment.addSpanAttribute('request.method', request.method)
435
-
436
- transaction.port = request.url.port
419
+ function setWebRequest(transaction, request) {
420
+ // make a fake URL to scrub the path from a URL object
421
+ const absoluteUrl = `http://localhost${request.url.path}`
422
+ transaction.initializeWeb({ absoluteUrl, method: request.method, port: request.url.port, transport: request?.transportType?.toUpperCase(), headers: request.headers })
437
423
 
438
424
  // These are only query parameters, from lib/serverless/api-gateway.js
425
+ // Not handled in `transaction.initializeWeb` because the query params are not on the absoluteUrl
439
426
  transaction.addRequestParameters(request.url.requestParameters)
440
-
441
- // URL is sent as an agent attribute with transaction events
442
- transaction.trace.attributes.addAttribute(
443
- ATTR_DEST.TRANS_EVENT | ATTR_DEST.ERROR_EVENT,
444
- 'request.uri',
445
- request.url.path
446
- )
447
-
448
- segment.addSpanAttribute('request.uri', request.url.path)
449
-
450
- headerAttributes.collectRequestHeaders(request.headers, transaction)
451
-
452
- if (shim.agent.config.distributed_tracing.enabled) {
453
- const lowercaseHeaders = lowercaseObjectKeys(request.headers)
454
-
455
- const transportType = request.transportType && request.transportType.toUpperCase()
456
- transaction.acceptDistributedTraceHeaders(transportType, lowercaseHeaders)
457
- }
458
- }
459
-
460
- function lowercaseObjectKeys(original) {
461
- return Object.keys(original).reduce((destination, key) => {
462
- destination[key.toLowerCase()] = original[key]
463
- return destination
464
- }, {})
465
427
  }
466
428
 
467
429
  function endTransaction(transaction, enderIndex) {
@@ -490,27 +452,4 @@ function endTransaction(transaction, enderIndex) {
490
452
  }
491
453
  }
492
454
 
493
- function setWebResponse(transaction, response) {
494
- transaction.statusCode = response.statusCode
495
-
496
- const responseCode = String(response.statusCode)
497
-
498
- if (/^\d+$/.test(responseCode)) {
499
- transaction.trace.attributes.addAttribute(
500
- ATTR_DEST.TRANS_COMMON,
501
- 'http.statusCode',
502
- responseCode
503
- )
504
-
505
- // We are adding http.statusCode to base segment as
506
- // we found in testing async invoked lambdas, the
507
- // active segment is not available at this point.
508
- const segment = transaction.baseSegment
509
-
510
- segment.addSpanAttribute('http.statusCode', responseCode)
511
- }
512
-
513
- headerAttributes.collectResponseHeaders(response.headers, transaction)
514
- }
515
-
516
455
  module.exports = AwsLambda
@@ -70,7 +70,7 @@ function copyParams({ spec, shim, fn, fnName, args, req }) {
70
70
  ? null
71
71
  : spec.params.call(this, shim, fn, fnName, args, req)
72
72
 
73
- // Route parameters are handled here, query parameters are handled in lib/transaction/index.js#_markAsWeb as part of finalization
73
+ // Route parameters are handled here, query parameters are handled in lib/transaction/index.js#finalizeNameFromWeb as part of finalization
74
74
  return shim.prefixRouteParameters(params)
75
75
  }
76
76
 
package/lib/shimmer.js CHANGED
@@ -25,7 +25,7 @@ const symbols = require('./symbols')
25
25
  const { unsubscribe } = require('./instrumentation/undici')
26
26
  const subscriptions = require('./subscriber-configs')
27
27
  const createSubscriberConfigs = require('./subscribers/create-config')
28
- const ModulePatch = require('./patch-module')
28
+ const ModulePatch = require('@apm-js-collab/tracing-hooks')
29
29
 
30
30
  /**
31
31
  * Unwrapping is only likely to be used by test code, and is a fairly drastic
@@ -400,7 +400,7 @@ const shimmer = (module.exports = {
400
400
  if (subscriber.enabled === false) {
401
401
  logger.debug(
402
402
  'Skipping subscriber %s because it is disabled in the config',
403
- subscriber.id
403
+ subscriber.packageName
404
404
  )
405
405
  continue
406
406
  }
@@ -11,7 +11,8 @@ const subscribers = {
11
11
  ...require('./subscribers/elasticsearch/config'),
12
12
  ...require('./subscribers/ioredis/config'),
13
13
  ...require('./subscribers/mcp-sdk/config'),
14
- ...require('./subscribers/pino/config'),
14
+ ...require('./subscribers/openai/config'),
15
+ ...require('./subscribers/pino/config')
15
16
  }
16
17
 
17
18
  module.exports = subscribers
@@ -6,6 +6,8 @@
6
6
  'use strict'
7
7
  // eslint-disable-next-line n/no-unsupported-features/node-builtins
8
8
  const { tracingChannel } = require('node:diagnostics_channel')
9
+ const semver = require('semver')
10
+ const NAMES = require('../metrics/names')
9
11
 
10
12
  /**
11
13
  * The baseline parameters available to all subscribers.
@@ -66,6 +68,33 @@ class Subscriber {
66
68
  )
67
69
  }
68
70
 
71
+ /**
72
+ * Creates the `Supportability/Features/Instrumentation/OnRequire/<packageName>`
73
+ * and `Supportability/Features/Instrumentation/OnRequire/<packageName>/Version/<majorVersion>`
74
+ * metrics to track the usage of an instrumented package.
75
+ * We only want to increment this once per package, so we check the call count
76
+ *
77
+ * @param {string} [version] - The version of the package being instrumented.
78
+ * @returns {void}
79
+ */
80
+ trackInstrumentationUsage(version) {
81
+ const prefix = NAMES.FEATURES.INSTRUMENTATION.ON_REQUIRE
82
+ const instrumentationMetric = `${prefix}/${this.packageName}`
83
+ const metric = this.agent.metrics.getOrCreateMetric(instrumentationMetric)
84
+ if (metric.callCount === 0) {
85
+ metric.incrementCallCount()
86
+ }
87
+
88
+ if (version) {
89
+ const majorVersion = semver.major(version)
90
+ const versionMetricName = `${instrumentationMetric}/Version/${majorVersion}`
91
+ const versionMetric = this.agent.metrics.getOrCreateMetric(versionMetricName)
92
+ if (versionMetric.callCount === 0) {
93
+ versionMetric.incrementCallCount()
94
+ }
95
+ }
96
+ }
97
+
69
98
  /**
70
99
  * Creates a segment with a name, parent, transaction and optional recorder.
71
100
  * If the segment is successfully created, it will be started and added to the context.
@@ -127,6 +156,7 @@ class Subscriber {
127
156
  */
128
157
  enable() {
129
158
  this.channel.start.bindStore(this.store, (data) => {
159
+ this.trackInstrumentationUsage(data?.moduleVersion)
130
160
  const ctx = this.agent.tracer.getContext()
131
161
  if (this.requireActiveTx && !ctx?.transaction?.isActive()) {
132
162
  this.logger.debug('Not recording event for %s, transaction is not active', this.package)
@@ -2,6 +2,7 @@
2
2
  * Copyright 2025 New Relic Corporation. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
+ 'use strict'
5
6
 
6
7
  module.exports = {
7
8
  '@elastic/elasticsearch': [{
@@ -0,0 +1,66 @@
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 Subscriber = require('../base')
9
+ const { MCP } = require('../../metrics/names')
10
+
11
+ class McpClientRequestSubscriber extends Subscriber {
12
+ constructor({ agent, logger }) {
13
+ super({ agent, logger, packageName: '@modelcontextprotocol/sdk', channelName: 'nr_request' })
14
+ this.events = ['asyncEnd']
15
+ }
16
+
17
+ get enabled() {
18
+ return super.enabled && this.config.ai_monitoring.enabled === true
19
+ }
20
+
21
+ /**
22
+ * Determine the MCP function to record based on the incoming request.
23
+ * @param {*} args0 - The arguments passed to the request.
24
+ * @returns {Object} - An object containing the MCP prefix, function name, and primitive name.
25
+ */
26
+ determineFunction(args0) {
27
+ const methodName = args0?.method
28
+ let mcpPrefix
29
+ let functionName
30
+ let primitiveName
31
+
32
+ if (methodName === 'tools/call') {
33
+ mcpPrefix = MCP.TOOL
34
+ functionName = 'callTool'
35
+ primitiveName = args0?.params?.name ?? 'tool'
36
+ } else if (methodName === 'prompts/get') {
37
+ mcpPrefix = MCP.PROMPT
38
+ functionName = 'getPrompt'
39
+ primitiveName = args0?.params?.name ?? 'prompt'
40
+ } else if (methodName === 'resources/read') {
41
+ mcpPrefix = MCP.RESOURCE
42
+ functionName = 'readResource'
43
+ const uri = args0?.params?.uri
44
+ primitiveName = typeof uri === 'string' ? uri.split('://')[0] : 'resource'
45
+ } else {
46
+ this.logger.debug(`@modelcontextprotocol/sdk: Will not create '${methodName}' segment.`)
47
+ }
48
+ return { mcpPrefix, functionName, primitiveName }
49
+ }
50
+
51
+ handler(data, ctx) {
52
+ const { mcpPrefix, functionName, primitiveName } = this.determineFunction(data?.arguments?.[0])
53
+ if (!mcpPrefix || !functionName || !primitiveName) {
54
+ // If it is not a function we care about, don't instrument at all
55
+ return
56
+ }
57
+
58
+ const segmentName = `${mcpPrefix}/${functionName}/${primitiveName}`
59
+ return this.createSegment({
60
+ name: segmentName,
61
+ ctx
62
+ })
63
+ }
64
+ }
65
+
66
+ module.exports = McpClientRequestSubscriber
@@ -3,96 +3,32 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
6
- const mcpClientToolConfig = {
7
- path: './mcp-sdk/client-tool',
6
+ const mcpClientRequestConfig = {
7
+ path: './mcp-sdk/client-request',
8
8
  instrumentations: [
9
9
  {
10
- channelName: 'nr_callTool',
10
+ channelName: 'nr_request',
11
11
  module: {
12
12
  name: '@modelcontextprotocol/sdk',
13
13
  versionRange: '>=1.13.0',
14
- filePath: 'dist/cjs/client/index.js'
14
+ filePath: 'dist/cjs/shared/protocol.js'
15
15
  },
16
16
  functionQuery: {
17
- className: 'Client',
18
- methodName: 'callTool', // must be methodName, not functionName
17
+ className: 'Protocol',
18
+ methodName: 'request',
19
19
  kind: 'Async'
20
20
  }
21
21
  },
22
22
  {
23
- channelName: 'nr_callTool',
23
+ channelName: 'nr_request',
24
24
  module: {
25
25
  name: '@modelcontextprotocol/sdk',
26
26
  versionRange: '>=1.13.0',
27
- filePath: 'dist/esm/client/index.js'
27
+ filePath: 'dist/esm/shared/protocol.js'
28
28
  },
29
29
  functionQuery: {
30
- className: 'Client',
31
- methodName: 'callTool',
32
- kind: 'Async'
33
- }
34
- },
35
- ]
36
- }
37
-
38
- const mcpClientResourceConfig = {
39
- path: './mcp-sdk/client-resource',
40
- instrumentations: [
41
- {
42
- channelName: 'nr_readResource',
43
- module: {
44
- name: '@modelcontextprotocol/sdk',
45
- versionRange: '>=1.13.0',
46
- filePath: 'dist/cjs/client/index.js'
47
- },
48
- functionQuery: {
49
- className: 'Client',
50
- methodName: 'readResource',
51
- kind: 'Async'
52
- }
53
- },
54
- {
55
- channelName: 'nr_readResource',
56
- module: {
57
- name: '@modelcontextprotocol/sdk',
58
- versionRange: '>=1.13.0',
59
- filePath: 'dist/esm/client/index.js'
60
- },
61
- functionQuery: {
62
- className: 'Client',
63
- methodName: 'readResource',
64
- kind: 'Async'
65
- }
66
- },
67
- ]
68
- }
69
-
70
- const mcpClientPromptConfig = {
71
- path: './mcp-sdk/client-prompt',
72
- instrumentations: [
73
- {
74
- channelName: 'nr_getPrompt',
75
- module: {
76
- name: '@modelcontextprotocol/sdk',
77
- versionRange: '>=1.13.0',
78
- filePath: 'dist/cjs/client/index.js'
79
- },
80
- functionQuery: {
81
- className: 'Client',
82
- methodName: 'getPrompt',
83
- kind: 'Async'
84
- }
85
- },
86
- {
87
- channelName: 'nr_getPrompt',
88
- module: {
89
- name: '@modelcontextprotocol/sdk',
90
- versionRange: '>=1.13.0',
91
- filePath: 'dist/esm/client/index.js'
92
- },
93
- functionQuery: {
94
- className: 'Client',
95
- methodName: 'getPrompt',
30
+ className: 'Protocol',
31
+ methodName: 'request',
96
32
  kind: 'Async'
97
33
  }
98
34
  },
@@ -100,5 +36,7 @@ const mcpClientPromptConfig = {
100
36
  }
101
37
 
102
38
  module.exports = {
103
- '@modelcontextprotocol/sdk': [mcpClientToolConfig, mcpClientResourceConfig, mcpClientPromptConfig],
39
+ '@modelcontextprotocol/sdk': [
40
+ mcpClientRequestConfig
41
+ ]
104
42
  }
@@ -0,0 +1,21 @@
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 Subscriber = require('../base')
9
+
10
+ class OpenAISubscriber extends Subscriber {
11
+ constructor({ agent, logger, channelName }) {
12
+ super({ agent, logger, packageName: 'openai', channelName })
13
+ this.events = ['asyncEnd']
14
+ }
15
+
16
+ get enabled() {
17
+ return super.enabled && this.config.ai_monitoring?.enabled
18
+ }
19
+ }
20
+
21
+ module.exports = OpenAISubscriber
@@ -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
+ const OpenAIChatCompletions = require('./chat')
8
+
9
+ class OpenAIResponses extends OpenAIChatCompletions {
10
+ constructor({ agent, logger }) {
11
+ super({ agent, logger, channelName: 'nr_responses' })
12
+ }
13
+ }
14
+
15
+ module.exports = OpenAIResponses