newrelic 13.2.1 → 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 (56) hide show
  1. package/NEWS.md +44 -10
  2. package/api.js +1 -1
  3. package/lib/instrumentation/@azure/functions.js +8 -46
  4. package/lib/instrumentation/core/http-outbound.js +26 -10
  5. package/lib/instrumentation/core/http.js +20 -118
  6. package/lib/instrumentations.js +0 -14
  7. package/lib/otel/logs/index.js +80 -0
  8. package/lib/otel/metrics/index.js +119 -0
  9. package/lib/otel/setup-signal.js +37 -0
  10. package/lib/otel/setup.js +15 -25
  11. package/lib/otel/{attr-reconciler.js → traces/attr-reconciler.js} +2 -2
  12. package/lib/otel/{exception-mapping.js → traces/exception-mapping.js} +1 -1
  13. package/lib/otel/traces/index.js +40 -0
  14. package/lib/otel/{rules.js → traces/rules.js} +1 -1
  15. package/lib/otel/{segment-synthesis.js → traces/segment-synthesis.js} +5 -4
  16. package/lib/otel/{segments → traces/segments}/consumer.js +4 -4
  17. package/lib/otel/{segments → traces/segments}/database.js +6 -6
  18. package/lib/otel/{segments → traces/segments}/http-external.js +11 -8
  19. package/lib/otel/traces/segments/index.js +22 -0
  20. package/lib/otel/{segments → traces/segments}/internal.js +1 -1
  21. package/lib/otel/{segments → traces/segments}/producer.js +2 -2
  22. package/lib/otel/{segments → traces/segments}/server.js +4 -4
  23. package/lib/otel/{segments → traces/segments}/utils.js +1 -1
  24. package/lib/otel/{span-processor.js → traces/span-processor.js} +13 -19
  25. package/lib/otel/{transformation-rules.json → traces/transformation-rules.json} +3 -2
  26. package/lib/otel/{utils.js → traces/utils.js} +2 -1
  27. package/lib/serverless/aws-lambda.js +7 -68
  28. package/lib/shim/webframework-shim/middleware.js +1 -1
  29. package/lib/shimmer.js +1 -1
  30. package/lib/subscriber-configs.js +2 -1
  31. package/lib/subscribers/base.js +30 -0
  32. package/lib/subscribers/elasticsearch/config.js +1 -0
  33. package/lib/subscribers/mcp-sdk/client-request.js +66 -0
  34. package/lib/subscribers/mcp-sdk/config.js +13 -75
  35. package/lib/subscribers/openai/base.js +21 -0
  36. package/lib/subscribers/openai/chat-responses.js +15 -0
  37. package/lib/subscribers/openai/chat.js +85 -0
  38. package/lib/subscribers/openai/client.js +31 -0
  39. package/lib/subscribers/openai/config.js +85 -0
  40. package/lib/subscribers/openai/embeddings.js +54 -0
  41. package/lib/subscribers/openai/utils.js +349 -0
  42. package/lib/transaction/index.js +157 -56
  43. package/lib/transaction/trace/segment.js +5 -2
  44. package/lib/util/urltils.js +158 -192
  45. package/lib/utilization/common.js +0 -6
  46. package/package.json +1 -1
  47. package/lib/instrumentation/openai.js +0 -482
  48. package/lib/otel/logs/bootstrap-logs.js +0 -84
  49. package/lib/otel/metrics/bootstrap-metrics.js +0 -117
  50. package/lib/otel/segments/index.js +0 -22
  51. package/lib/subscribers/mcp-sdk/client-prompt.js +0 -23
  52. package/lib/subscribers/mcp-sdk/client-resource.js +0 -24
  53. package/lib/subscribers/mcp-sdk/client-tool.js +0 -23
  54. package/lib/subscribers/mcp-sdk/client.js +0 -29
  55. /package/lib/otel/{constants.js → traces/constants.js} +0 -0
  56. /package/lib/otel/{sampler.js → traces/sampler.js} +0 -0
@@ -0,0 +1,37 @@
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 defaultLogger = require('#agentlib/logger.js').child({ component: 'opentelemetry-bridge' })
9
+
10
+ /**
11
+ * SetupSignal is an interface to standardize how we bootstrap OpenTelemetry
12
+ * observability signals within the OTEL bridge. Subclasses are expected to
13
+ * implement their setup in the constructor, and to provide a `.teardown`
14
+ * method that properly cleans up any created resources (i.e. unblocks the
15
+ * parent singleton managed by the OTEL packages from being garbage collected).
16
+ */
17
+ class SetupSignal {
18
+ agent
19
+ logger
20
+
21
+ coreApi = require('@opentelemetry/api')
22
+
23
+ constructor({ agent, logger = defaultLogger } = {}) {
24
+ if (Object.prototype.toString.call(agent) !== '[object Agent]') {
25
+ throw Error('must provide an instance of the New Relic agent')
26
+ }
27
+
28
+ this.agent = agent
29
+ this.logger = logger
30
+ }
31
+
32
+ teardown() {
33
+ this.logger.warn('teardown method is not implemented')
34
+ }
35
+ }
36
+
37
+ module.exports = SetupSignal
package/lib/otel/setup.js CHANGED
@@ -6,18 +6,17 @@
6
6
  'use strict'
7
7
 
8
8
  const opentelemetry = require('@opentelemetry/api')
9
- const logsApi = require('@opentelemetry/api-logs')
10
- const { BasicTracerProvider } = require('@opentelemetry/sdk-trace-base')
11
9
 
12
- const NrSpanProcessor = require('./span-processor')
13
- const NrSampler = require('./sampler')
10
+ const SetupLogs = require('./logs/index.js')
11
+ const SetupMetrics = require('./metrics/index.js')
12
+ const SetupTraces = require('./traces/index.js')
14
13
  const ContextManager = require('./context-manager')
15
14
  const TracePropagator = require('./trace-propagator')
16
15
  const defaultLogger = require('../logger').child({ component: 'opentelemetry-bridge' })
17
16
  const createOtelLogger = require('./logger')
18
17
  const interceptSpanKey = require('./span-key-interceptor')
19
- const bootstrapMetrics = require('./metrics/bootstrap-metrics')
20
- const bootstrapLogs = require('./logs/bootstrap-logs')
18
+
19
+ const signals = []
21
20
 
22
21
  function setupOtel(agent, logger = defaultLogger) {
23
22
  if (agent.config.opentelemetry_bridge.enabled !== true) {
@@ -38,19 +37,22 @@ function setupOtel(agent, logger = defaultLogger) {
38
37
  opentelemetry.propagation.setGlobalPropagator(new TracePropagator(agent))
39
38
 
40
39
  if (agent.config.opentelemetry_bridge.traces.enabled === true) {
41
- bootstrapTraces(agent)
40
+ const signal = new SetupTraces({ agent })
41
+ signals.push(signal)
42
42
  } else {
43
43
  logger.debug('`opentelemetry_bridge.traces` is not enabled, skipping')
44
44
  }
45
45
 
46
46
  if (agent.config.opentelemetry_bridge.metrics.enabled === true) {
47
- bootstrapMetrics(agent)
47
+ const signal = new SetupMetrics({ agent })
48
+ signals.push(signal)
48
49
  } else {
49
50
  logger.debug('`opentelemetry_bridge.metrics` is not enabled, skipping')
50
51
  }
51
52
 
52
53
  if (agent.config.opentelemetry_bridge.logs.enabled === true) {
53
- bootstrapLogs({ agent })
54
+ const signal = new SetupLogs({ agent })
55
+ signals.push(signal)
54
56
  } else {
55
57
  logger.debug('`opentelemetry_bridge.logs` is not enabled, skipping')
56
58
  }
@@ -60,30 +62,18 @@ function setupOtel(agent, logger = defaultLogger) {
60
62
  .incrementCallCount()
61
63
  }
62
64
 
63
- function bootstrapTraces(agent) {
64
- opentelemetry.trace.setGlobalTracerProvider(new BasicTracerProvider({
65
- sampler: new NrSampler(),
66
- spanProcessors: [new NrSpanProcessor(agent)],
67
- generalLimits: {
68
- attributeValueLengthLimit: 4095
69
- }
70
- }))
71
-
72
- agent.metrics
73
- .getOrCreateMetric('Supportability/Nodejs/OpenTelemetryBridge/Traces')
74
- .incrementCallCount()
75
- }
76
-
77
65
  function teardownOtel(agent) {
78
66
  if (agent?.config?.opentelemetry_bridge?.enabled !== true) {
79
67
  return
80
68
  }
81
69
 
82
- opentelemetry.trace.disable()
70
+ for (const signal of signals) {
71
+ signal.teardown()
72
+ }
73
+
83
74
  opentelemetry.context.disable()
84
75
  opentelemetry.propagation.disable()
85
76
  opentelemetry.diag.disable()
86
- logsApi.logs.disable()
87
77
  }
88
78
 
89
79
  module.exports = {
@@ -5,8 +5,8 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- const urltils = require('../util/urltils')
9
- const constants = require('./constants')
8
+ const urltils = require('#agentlib/util/urltils.js')
9
+ const constants = require('./constants.js')
10
10
 
11
11
  const hostKeys = [
12
12
  constants.ATTR_NET_HOST_NAME,
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  'use strict'
7
- const constants = require('./constants')
7
+ const constants = require('./constants.js')
8
8
  const attrMappings = {
9
9
  msg: {
10
10
  attrs: [constants.EXCEPTION_MESSAGE, constants.EXCEPTION_TYPE]
@@ -0,0 +1,40 @@
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 { BasicTracerProvider } = require('@opentelemetry/sdk-trace-base')
9
+
10
+ const defaultLogger = require('../../logger').child({ component: 'opentelemetry-traces' })
11
+ const SetupSignal = require('../setup-signal.js')
12
+ const NrSampler = require('./sampler.js')
13
+ const NrSpanProcessor = require('./span-processor.js')
14
+
15
+ class SetupTraces extends SetupSignal {
16
+ constructor({ agent, logger = defaultLogger } = {}) {
17
+ super({ agent, logger })
18
+
19
+ const sampler = new NrSampler()
20
+ const spanProcessor = new NrSpanProcessor(agent)
21
+ const traceProvider = new BasicTracerProvider({
22
+ sampler,
23
+ spanProcessors: [spanProcessor],
24
+ generalLimits: {
25
+ attributeValueLengthLimit: 4_095
26
+ }
27
+ })
28
+ this.coreApi.trace.setGlobalTracerProvider(traceProvider)
29
+
30
+ agent.metrics
31
+ .getOrCreateMetric('Supportability/Nodejs/OpenTelemetryBridge/Traces')
32
+ .incrementCallCount()
33
+ }
34
+
35
+ teardown() {
36
+ this.coreApi.trace.disable()
37
+ }
38
+ }
39
+
40
+ module.exports = SetupTraces
@@ -6,7 +6,7 @@
6
6
  'use strict'
7
7
 
8
8
  const { SpanKind } = require('@opentelemetry/api')
9
- const srcJson = require('./transformation-rules')
9
+ const srcJson = require('./transformation-rules.json')
10
10
 
11
11
  /**
12
12
  * Mapping rules are stored, and provided to the agent, as JSON. We call these
@@ -4,8 +4,9 @@
4
4
  */
5
5
 
6
6
  'use strict'
7
- const { RulesEngine } = require('./rules')
8
- const defaultLogger = require('../logger').child({ component: 'segment-synthesizer' })
7
+
8
+ const defaultLogger = require('#agentlib/logger.js').child({ component: 'segment-synthesizer' })
9
+ const { RulesEngine } = require('./rules.js')
9
10
  const {
10
11
  createConsumerSegment,
11
12
  createDbSegment,
@@ -13,7 +14,7 @@ const {
13
14
  createInternalSegment,
14
15
  createProducerSegment,
15
16
  createServerSegment
16
- } = require('./segments')
17
+ } = require('./segments/index.js')
17
18
 
18
19
  class SegmentSynthesizer {
19
20
  constructor(agent, { logger = defaultLogger } = {}) {
@@ -43,7 +44,7 @@ class SegmentSynthesizer {
43
44
  }
44
45
 
45
46
  case 'external': {
46
- return createHttpExternalSegment(this.agent, otelSpan, rule)
47
+ return createHttpExternalSegment(this.agent, otelSpan, rule, this.logger)
47
48
  }
48
49
 
49
50
  case 'internal': {
@@ -7,10 +7,10 @@
7
7
 
8
8
  module.exports = createConsumerSegment
9
9
 
10
- const Transaction = require('../../transaction/')
11
- const recorder = require('../../metrics/recorders/message-transaction')
12
- const { propagateTraceContext } = require('./utils')
13
- const createInternalSegment = require('./internal')
10
+ const Transaction = require('#agentlib/transaction/index.js')
11
+ const recorder = require('#agentlib/metrics/recorders/message-transaction.js')
12
+ const { propagateTraceContext } = require('./utils.js')
13
+ const createInternalSegment = require('./internal.js')
14
14
 
15
15
  function createConsumerSegment(agent, otelSpan, rule) {
16
16
  const activeTx = agent.tracer.getTransaction()
@@ -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
@@ -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)