newrelic 13.3.3 → 13.5.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 (169) hide show
  1. package/NEWS.md +54 -0
  2. package/THIRD_PARTY_NOTICES.md +11 -11
  3. package/api.js +1 -1
  4. package/esm-loader.mjs +2 -2
  5. package/lib/agent.js +2 -2
  6. package/lib/aggregators/event-aggregator.js +0 -2
  7. package/lib/attributes.js +6 -6
  8. package/lib/collector/api.js +1 -1
  9. package/lib/collector/facts.js +1 -1
  10. package/lib/collector/http-agents.js +2 -2
  11. package/lib/collector/parse-response.js +9 -12
  12. package/lib/collector/remote-method.js +4 -5
  13. package/lib/collector/serverless.js +1 -1
  14. package/lib/config/build-instrumentation-config.js +13 -3
  15. package/lib/config/default.js +21 -10
  16. package/lib/config/formatters.js +5 -6
  17. package/lib/config/index.js +37 -24
  18. package/lib/harvester.js +1 -1
  19. package/lib/header-processing.js +3 -3
  20. package/lib/health-reporter.js +2 -2
  21. package/lib/instrumentation/@node-redis/client.js +1 -1
  22. package/lib/instrumentation/@prisma/client.js +1 -1
  23. package/lib/instrumentation/aws-sdk/util.js +3 -3
  24. package/lib/instrumentation/aws-sdk/v2/instrumentation-helper.js +2 -2
  25. package/lib/instrumentation/aws-sdk/v3/bedrock.js +5 -5
  26. package/lib/instrumentation/aws-sdk/v3/common.js +14 -14
  27. package/lib/instrumentation/aws-sdk/v3/dynamodb.js +13 -13
  28. package/lib/instrumentation/aws-sdk/v3/lambda.js +3 -3
  29. package/lib/instrumentation/aws-sdk/v3/sns.js +12 -12
  30. package/lib/instrumentation/aws-sdk/v3/sqs.js +9 -9
  31. package/lib/instrumentation/core/http-outbound.js +1 -2
  32. package/lib/instrumentation/fastify/spec-builders.js +9 -29
  33. package/lib/instrumentation/fastify.js +23 -94
  34. package/lib/instrumentation/kafkajs/consumer.js +1 -1
  35. package/lib/instrumentation/koa/instrumentation.js +1 -1
  36. package/lib/instrumentation/langchain/runnable.js +1 -1
  37. package/lib/instrumentation/memcached.js +4 -4
  38. package/lib/instrumentation/mongodb.js +2 -2
  39. package/lib/instrumentation/nextjs/next-server.js +1 -3
  40. package/lib/instrumentation/nextjs/utils.js +2 -2
  41. package/lib/instrumentation/restify.js +2 -2
  42. package/lib/instrumentation/when/contextualizer.js +1 -1
  43. package/lib/instrumentation-descriptor.js +2 -11
  44. package/lib/instrumentations.js +1 -20
  45. package/lib/llm-events/aws-bedrock/bedrock-response.js +6 -6
  46. package/lib/llm-events/aws-bedrock/chat-completion-summary.js +2 -1
  47. package/lib/llm-events/aws-bedrock/converse-stream-handler.js +1 -1
  48. package/lib/llm-events/aws-bedrock/stream-handler.js +4 -4
  49. package/lib/metrics/index.js +2 -2
  50. package/lib/metrics/normalizer/rule.js +1 -1
  51. package/lib/metrics/normalizer.js +4 -5
  52. package/lib/metrics/recorders/database-operation.js +2 -3
  53. package/lib/metrics/recorders/message-transaction.js +3 -3
  54. package/lib/metrics/recorders/middleware.js +1 -2
  55. package/lib/otel/context-manager.js +4 -4
  56. package/lib/otel/trace-propagator.js +1 -1
  57. package/lib/otel/traces/segments/database.js +1 -1
  58. package/lib/otel/traces/utils.js +9 -9
  59. package/lib/prioritized-attributes.js +2 -2
  60. package/lib/{adaptive-sampler.js → samplers/adaptive-sampler.js} +8 -0
  61. package/lib/serverless/aws-lambda.js +4 -4
  62. package/lib/shim/datastore-shim.js +2 -2
  63. package/lib/shim/message-shim/consume.js +4 -4
  64. package/lib/shim/message-shim/subscribe-consume.js +1 -2
  65. package/lib/shim/promise-shim.js +1 -4
  66. package/lib/shim/shim.js +14 -29
  67. package/lib/shim/specs/class.js +3 -3
  68. package/lib/shim/specs/middleware-mounter.js +2 -2
  69. package/lib/shim/specs/middleware.js +3 -3
  70. package/lib/shim/specs/query.js +1 -1
  71. package/lib/shim/specs/recorder.js +1 -1
  72. package/lib/shim/specs/wrap.js +2 -2
  73. package/lib/shim/transaction-shim.js +0 -1
  74. package/lib/shim/webframework-shim/common.js +2 -4
  75. package/lib/shim/webframework-shim/index.js +10 -1
  76. package/lib/shim/webframework-shim/middleware.js +17 -21
  77. package/lib/shimmer.js +56 -103
  78. package/lib/spans/span-context.js +1 -1
  79. package/lib/spans/span-event-aggregator.js +2 -2
  80. package/lib/spans/span-event.js +6 -5
  81. package/lib/spans/streaming-span-attributes.js +1 -1
  82. package/lib/spans/streaming-span-event-aggregator.js +5 -3
  83. package/lib/spans/streaming-span-event.js +1 -1
  84. package/lib/stats/index.js +1 -1
  85. package/lib/subscriber-configs.js +6 -1
  86. package/lib/subscribers/amqplib/accept-message.js +14 -0
  87. package/lib/subscribers/amqplib/callback-model.js +39 -0
  88. package/lib/subscribers/amqplib/channel-model.js +39 -0
  89. package/lib/subscribers/amqplib/config.js +697 -0
  90. package/lib/subscribers/amqplib/connect.js +74 -0
  91. package/lib/subscribers/amqplib/consume.js +54 -0
  92. package/lib/subscribers/amqplib/get-cb.js +16 -0
  93. package/lib/subscribers/amqplib/get.js +50 -0
  94. package/lib/subscribers/amqplib/purge-queue-cb.js +16 -0
  95. package/lib/subscribers/amqplib/purge-queue.js +37 -0
  96. package/lib/subscribers/amqplib/send-message.js +44 -0
  97. package/lib/subscribers/amqplib/send-or-enqueue.js +18 -0
  98. package/lib/subscribers/amqplib/utils.js +53 -0
  99. package/lib/subscribers/application-logs.js +1 -1
  100. package/lib/subscribers/base.js +113 -29
  101. package/lib/subscribers/cassandra-driver/client-batch.js +49 -0
  102. package/lib/subscribers/cassandra-driver/client-connect.js +35 -0
  103. package/lib/subscribers/cassandra-driver/client-each-row.js +36 -0
  104. package/lib/subscribers/cassandra-driver/client-execute.js +36 -0
  105. package/lib/subscribers/cassandra-driver/client-shutdown.js +32 -0
  106. package/lib/subscribers/cassandra-driver/config.js +145 -0
  107. package/lib/subscribers/cassandra-driver/legacy-client-batch.js +22 -0
  108. package/lib/subscribers/cassandra-driver/legacy-client-connect.js +22 -0
  109. package/lib/subscribers/cassandra-driver/legacy-client-each-row.js +22 -0
  110. package/lib/subscribers/cassandra-driver/legacy-client-execute.js +21 -0
  111. package/lib/subscribers/cassandra-driver/legacy-client-shutdown.js +32 -0
  112. package/lib/subscribers/create-config.js +4 -6
  113. package/lib/subscribers/db-operation.js +12 -0
  114. package/lib/subscribers/db-query.js +5 -1
  115. package/lib/subscribers/dc-base.js +89 -0
  116. package/lib/subscribers/fastify/add-hook.js +46 -0
  117. package/lib/subscribers/fastify/common.js +121 -0
  118. package/lib/subscribers/fastify/config.js +39 -0
  119. package/lib/subscribers/fastify/decorate.js +35 -0
  120. package/lib/subscribers/fastify/index.js +31 -0
  121. package/lib/subscribers/mcp-sdk/client-request.js +1 -1
  122. package/lib/subscribers/mcp-sdk/config.js +2 -0
  123. package/lib/subscribers/message-consumer.js +146 -0
  124. package/lib/subscribers/message-producer.js +60 -0
  125. package/lib/subscribers/meta-subscriber.js +69 -0
  126. package/lib/subscribers/openai/base.js +27 -1
  127. package/lib/subscribers/openai/chat.js +0 -27
  128. package/lib/subscribers/openai/config.js +3 -3
  129. package/lib/subscribers/openai/utils.js +10 -11
  130. package/lib/subscribers/pg/config.js +91 -0
  131. package/lib/subscribers/pg/connect.js +28 -0
  132. package/lib/subscribers/pg/native-connect.js +25 -0
  133. package/lib/subscribers/pg/native-query.js +22 -0
  134. package/lib/subscribers/pg/query.js +66 -0
  135. package/lib/subscribers/pino/index.js +2 -2
  136. package/lib/subscribers/propagation.js +23 -0
  137. package/lib/subscribers/undici/config.js +11 -0
  138. package/lib/subscribers/undici/index.js +195 -0
  139. package/lib/synthetics.js +4 -4
  140. package/lib/timer.js +1 -1
  141. package/lib/tracking-packages.js +25 -0
  142. package/lib/transaction/dt-payload.js +1 -1
  143. package/lib/transaction/handle.js +8 -9
  144. package/lib/transaction/index.js +25 -21
  145. package/lib/transaction/name-state.js +11 -11
  146. package/lib/transaction/trace/segment.js +3 -4
  147. package/lib/transaction/tracer/index.js +1 -1
  148. package/lib/util/attribute-types.js +1 -1
  149. package/lib/util/byte-limit.js +1 -1
  150. package/lib/util/cat.js +13 -13
  151. package/lib/util/flatten.js +2 -3
  152. package/lib/util/is-absolute-path.js +1 -1
  153. package/lib/util/is-string.js +15 -0
  154. package/lib/util/llm-utils.js +3 -3
  155. package/lib/util/logger.js +8 -8
  156. package/lib/util/stream-sink.js +1 -1
  157. package/lib/utilization/docker-info.js +4 -3
  158. package/lib/utilization/ecs-info.js +1 -1
  159. package/lib/w3c/traceparent.js +3 -3
  160. package/lib/w3c/tracestate.js +1 -1
  161. package/package.json +2 -2
  162. package/lib/instrumentation/amqplib/amqplib.js +0 -122
  163. package/lib/instrumentation/amqplib/channel-model.js +0 -124
  164. package/lib/instrumentation/amqplib/channel.js +0 -72
  165. package/lib/instrumentation/amqplib/nr-hooks.js +0 -21
  166. package/lib/instrumentation/amqplib/utils.js +0 -143
  167. package/lib/instrumentation/cassandra-driver.js +0 -131
  168. package/lib/instrumentation/pg.js +0 -137
  169. package/lib/instrumentation/undici.js +0 -229
@@ -0,0 +1,74 @@
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 { amqpConnection } = require('#agentlib/symbols.js')
8
+ const isString = require('#agentlib/util/is-string.js')
9
+
10
+ /**
11
+ * This subscriber is two-fold:
12
+ * 1. capture the connection arguments and assign to symbol on connection to be used for all other amqplib subscribers
13
+ * 2. create a segment for the connect. Not this will attempt to create a segment but the base subscriber will decide if it is created or not, otherwise it'll return the current context
14
+ */
15
+ class ConnectSubscriber extends Subscriber {
16
+ constructor({ agent, logger }) {
17
+ super({ agent, logger, packageName: 'amqplib', channelName: 'nr_connect' })
18
+ this.events = ['asyncStart', 'asyncEnd']
19
+ this.requireActiveTx = false
20
+ this.callback = -1
21
+ }
22
+
23
+ handler(data, ctx) {
24
+ this.parameters = this.parseConnectionArgs(data.arguments)
25
+ return this.createSegment({
26
+ name: 'amqplib.connect',
27
+ ctx
28
+ })
29
+ }
30
+
31
+ asyncStart(data) {
32
+ if (this.parameters && data.result) {
33
+ data.result[amqpConnection] = this.parameters
34
+ }
35
+ super.asyncStart(data)
36
+ }
37
+
38
+ /**
39
+ * Parses the connection args to return host/port
40
+ *
41
+ * @param {Array} args arguments passed to connect
42
+ * @returns {object} { host, port }
43
+ */
44
+ parseConnectionArgs(args = []) {
45
+ const [connArgs] = args
46
+ const params = {}
47
+ if (isString(connArgs)) {
48
+ try {
49
+ const parsedUrl = new URL(connArgs)
50
+ params.host = parsedUrl.hostname
51
+ if (parsedUrl.port) {
52
+ params.port = parseInt(parsedUrl.port, 10)
53
+ }
54
+ } catch (err) {
55
+ this.logger.error('Failed to parse connection string: %s', err.message)
56
+ }
57
+ } else {
58
+ params.port = connArgs.port || (connArgs.protocol === 'amqp' ? 5672 : 5671)
59
+ params.host = connArgs.hostname
60
+ }
61
+
62
+ return params
63
+ }
64
+
65
+ addAttributes(segment) {
66
+ for (let [key, value] of Object.entries(this.parameters)) {
67
+ // eslint-disable-next-line sonarjs/updated-loop-counter
68
+ key = key === 'port' ? 'port_path_or_id' : key
69
+ segment.addAttribute(key, value)
70
+ }
71
+ }
72
+ }
73
+
74
+ module.exports = ConnectSubscriber
@@ -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 MessageConsumer = require('../message-consumer')
7
+ const { getParameters, getParametersFromMessage, TEMP_RE } = require('./utils')
8
+
9
+ class ConsumeSubscriber extends MessageConsumer {
10
+ constructor({ agent, logger, channelName = 'nr_consume' }) {
11
+ super({ agent, logger, packageName: 'amqplib', channelName, system: 'RabbitMQ', type: 'Exchange', callback: 1, transport: 'AMQP' })
12
+ this.segmentName = 'amqplib.Channel#consume'
13
+ }
14
+
15
+ /**
16
+ * Handles the consumption of a message(s),
17
+ * this will create a new transaction every time this function is called.
18
+ * @param {object} data the data associated with the `asyncStart` event
19
+ */
20
+ asyncStart(data) {
21
+ const [queue] = data?.arguments
22
+ const message = data?.error
23
+ this.consumerParameters = getParametersFromMessage.call(this, { channel: data?.self, message })
24
+ this.headers = message?.properties?.headers
25
+ this.destination = this.getExchange(message?.fields)
26
+ this.queue = queue
27
+
28
+ super.asyncStart(data)
29
+ }
30
+
31
+ getExchange(fields = {}) {
32
+ const isDefault = fields.exchange === ''
33
+ let exchange = 'Default'
34
+ if (!isDefault) {
35
+ exchange = TEMP_RE.test(fields.exchange) ? null : fields.exchange
36
+ }
37
+
38
+ return exchange
39
+ }
40
+
41
+ /**
42
+ * Handles the creation of the segment if `channel.consume` is made within
43
+ * an active transaction.
44
+ * @param {object} data the data associated with the subscribed event
45
+ * @param {object} ctx context
46
+ */
47
+ handler(data, ctx) {
48
+ this.parameters = {}
49
+ getParameters.call(this, { params: this.parameters, channel: data.self })
50
+ return super.handler(data, ctx)
51
+ }
52
+ }
53
+
54
+ module.exports = ConsumeSubscriber
@@ -0,0 +1,16 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ const GetMessageSubscriber = require('./get')
7
+
8
+ class GetMessageCbSubscriber extends GetMessageSubscriber {
9
+ constructor({ agent, logger }) {
10
+ super({ agent, logger, packageName: 'amqplib', channelName: 'cb_get' })
11
+ this.events = ['asyncStart', 'asyncEnd']
12
+ this.callback = -1
13
+ }
14
+ }
15
+
16
+ module.exports = GetMessageCbSubscriber
@@ -0,0 +1,50 @@
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 genericRecorder = require('#agentlib/metrics/recorders/generic.js')
8
+ const { getParametersFromMessage, EXCHANGE_PREFIX } = require('./utils')
9
+
10
+ class GetMessageSubscriber extends Subscriber {
11
+ constructor({ agent, logger, channelName = 'nr_get' }) {
12
+ super({ agent, logger, packageName: 'amqplib', channelName })
13
+ this.events = ['asyncEnd']
14
+ }
15
+
16
+ handler(data, ctx) {
17
+ const queue = this.extractQueue(data.arguments)
18
+ const name = `${EXCHANGE_PREFIX}/Consume/${queue}`
19
+ return this.createSegment({
20
+ name,
21
+ ctx,
22
+ recorder: genericRecorder
23
+ })
24
+ }
25
+
26
+ asyncEnd(data) {
27
+ const ctx = this.agent.tracer.getContext()
28
+ const segment = ctx?.segment
29
+ const { result: message } = data
30
+ if (!(message?.fields || message?.properties) || !segment) {
31
+ this.logger.trace('No message from consume, not capturing segment parameters')
32
+ super.asyncEnd(data)
33
+ return
34
+ }
35
+
36
+ const params = getParametersFromMessage.call(this, { message, channel: data?.self })
37
+ for (const [key, value] of Object.entries(params)) {
38
+ segment.addAttribute(key, value)
39
+ }
40
+
41
+ super.asyncEnd(data)
42
+ }
43
+
44
+ extractQueue(args) {
45
+ const [queue] = args
46
+ return `Named/${queue}`
47
+ }
48
+ }
49
+
50
+ module.exports = GetMessageSubscriber
@@ -0,0 +1,16 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ const PurgeQueueSubscriber = require('./purge-queue')
7
+
8
+ class PurgeQueueCbSubscriber extends PurgeQueueSubscriber {
9
+ constructor({ agent, logger }) {
10
+ super({ agent, logger, channelName: 'cb_purgeQueue' })
11
+ this.events = ['asyncStart', 'asyncEnd']
12
+ this.callback = -1
13
+ }
14
+ }
15
+
16
+ module.exports = PurgeQueueCbSubscriber
@@ -0,0 +1,37 @@
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 genericRecorder = require('#agentlib/metrics/recorders/generic.js')
8
+ const { TEMP_RE, QUEUE_PREFIX } = require('./utils')
9
+
10
+ class PurgeQueueSubscriber extends Subscriber {
11
+ constructor({ agent, logger, channelName = 'nr_purgeQueue' }) {
12
+ super({ agent, logger, packageName: 'amqplib', channelName })
13
+ this.events = ['asyncEnd']
14
+ }
15
+
16
+ handler(data, ctx) {
17
+ const queue = this.extractQueue(data.arguments)
18
+ const name = `${QUEUE_PREFIX}/Purge/${queue}`
19
+ return this.createSegment({
20
+ name,
21
+ ctx,
22
+ recorder: genericRecorder
23
+ })
24
+ }
25
+
26
+ extractQueue(args) {
27
+ let [queue] = args
28
+
29
+ if (TEMP_RE.test(queue)) {
30
+ queue = null
31
+ }
32
+
33
+ return queue ? `Named/${queue}` : 'Temp'
34
+ }
35
+ }
36
+
37
+ module.exports = PurgeQueueSubscriber
@@ -0,0 +1,44 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ const MessageProducerSubscriber = require('../message-producer')
7
+ const { getParameters, TEMP_RE } = require('./utils')
8
+
9
+ class ChannelSubscriber extends MessageProducerSubscriber {
10
+ constructor({ agent, logger }) {
11
+ super({ agent, logger, packageName: 'amqplib', channelName: 'nr_sendMessage', system: 'RabbitMQ', type: 'Exchange' })
12
+ this.events = ['end']
13
+ }
14
+
15
+ handler(data, ctx) {
16
+ const [fields] = data.arguments
17
+
18
+ if (!fields) {
19
+ return ctx
20
+ }
21
+
22
+ this.destination = this.getExchange(fields)
23
+ this.parameters = this.getParameters({ fields, channel: data.self })
24
+ this.headers = fields?.headers
25
+ return super.handler(data, ctx)
26
+ }
27
+
28
+ getParameters({ fields = {}, channel = {} }) {
29
+ const params = {}
30
+ getParameters.call(this, { fields, channel, params })
31
+ return params
32
+ }
33
+
34
+ getExchange(fields = {}) {
35
+ const isDefault = fields.exchange === ''
36
+ let exchange = 'Default'
37
+ if (!isDefault) {
38
+ exchange = TEMP_RE.test(fields.exchange) ? null : fields.exchange
39
+ }
40
+ return exchange
41
+ }
42
+ }
43
+
44
+ module.exports = ChannelSubscriber
@@ -0,0 +1,18 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ const PropagationSubscriber = require('../propagation')
7
+
8
+ /**
9
+ * This subscriber is purely for propagation of async context.
10
+ * It breaks without binding the transaction and segment from the result of the previous handler.
11
+ */
12
+ class SendOrEnqueueSubscriber extends PropagationSubscriber {
13
+ constructor({ agent, logger }) {
14
+ super({ agent, logger, packageName: 'amqplib', channelName: 'nr_sendOrEnqueue', callback: -1 })
15
+ }
16
+ }
17
+
18
+ module.exports = SendOrEnqueueSubscriber
@@ -0,0 +1,53 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+ const { amqpConnection } = require('#agentlib/symbols.js')
8
+
9
+ const TEMP_RE = /^amq\./
10
+ const RMQ_PREFIX = 'MessageBroker/RabbitMQ'
11
+ const EXCHANGE_PREFIX = `${RMQ_PREFIX}/Exchange`
12
+ const QUEUE_PREFIX = `${RMQ_PREFIX}/Queue`
13
+
14
+ function getParametersFromMessage({ message, channel }) {
15
+ const params = {}
16
+ getParameters.call(this, { channel, fields: message.fields, params })
17
+ getParameters.call(this, { channel, fields: message.properties, params })
18
+ return params
19
+ }
20
+
21
+ function getParameters({ fields = {}, channel = {}, params }) {
22
+ if (this.config.message_tracer.segment_parameters.enabled === false) {
23
+ this.logger.trace('Not capturing segment parameters, `message_tracer.segments_parameters.enabled` is false')
24
+ return
25
+ }
26
+
27
+ const { host, port } = channel?.connection?.[amqpConnection]
28
+
29
+ if (fields.routingKey) {
30
+ params.routing_key = fields.routingKey
31
+ }
32
+ if (fields.correlationId) {
33
+ params.correlation_id = fields.correlationId
34
+ }
35
+ if (fields.replyTo) {
36
+ params.reply_to = fields.replyTo
37
+ }
38
+ if (host) {
39
+ params.host = host
40
+ }
41
+
42
+ if (port) {
43
+ params.port = port
44
+ }
45
+ }
46
+
47
+ module.exports = {
48
+ getParameters,
49
+ getParametersFromMessage,
50
+ EXCHANGE_PREFIX,
51
+ QUEUE_PREFIX,
52
+ TEMP_RE
53
+ }
@@ -22,7 +22,7 @@ class ApplicationLogsSubscriber extends Subscriber {
22
22
  * but only once. You should call this in your handler.
23
23
  * We cannot recreate creating this metric on require of a logging library
24
24
  * because we no longer monkey patch but subscribe to events.
25
- * @param lib
25
+ * @param {string} lib name of library
26
26
  */
27
27
  createModuleUsageMetric(lib) {
28
28
  if (this.libMetricCreated === false) {
@@ -6,8 +6,7 @@
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
+ const cat = require('#agentlib/util/cat.js')
11
10
  // Used for the `traceCallback` work.
12
11
  // This can be removed when we add true support into orchestrion
13
12
  const makeCall = (fn) => (...args) => fn.call(...args)
@@ -46,6 +45,7 @@ const ArrayPrototypeSplice = makeCall(Array.prototype.splice)
46
45
  * channel event names. This provides a namespace for the events we are
47
46
  * injecting into a module.
48
47
  * @property {boolean} [requireActiveTx=true] If true, the subscriber will only handle events when there is an active transaction.
48
+ * @property {boolean} [propagateContext=false] If true, it will bind `asyncStart` to the store and re-propagate the active context. It will also attach the `transaction` to the event in `start.bindStore`. This is used for functions that queue async code and context is lost.
49
49
  * @property {string} id A unique identifier for the subscriber, combining the prefix, package name, and channel name.
50
50
  * @property {TracingChannel} channel The tracing channel instance this subscriber will be monitoring.
51
51
  * @property {AsyncLocalStorage} store The async local storage instance used for context management.
@@ -63,6 +63,7 @@ class Subscriber {
63
63
  this.internal = false
64
64
  this.prefix = 'orchestrion:'
65
65
  this.requireActiveTx = true
66
+ this.propagateContext = false
66
67
  this.id = `${this.prefix}${this.packageName}:${this.channelName}`
67
68
  this.channel = tracingChannel(this.id)
68
69
  this.store = agent.tracer._contextManager._asyncLocalStorage
@@ -119,38 +120,35 @@ class Subscriber {
119
120
  }
120
121
 
121
122
  /**
122
- * Creates the `Supportability/Features/Instrumentation/OnRequire/<packageName>`
123
- * and `Supportability/Features/Instrumentation/OnRequire/<packageName>/Version/<majorVersion>`
124
- * metrics to track the usage of an instrumented package.
125
- * We only want to increment this once per package, so we check the call count
123
+ * Wraps an event emitter and runs the wrap in the new context
124
+ * If the event is `end` or `error` it'll touch the active segment
126
125
  *
127
- * @param {string} [version] - The version of the package being instrumented.
128
- * @returns {void}
126
+ * @param {object} params to function
127
+ * @param {Array} params.args arguments to function
128
+ * @param {number} params.index index of argument to wrap
129
+ * @param {string} params.name name of emit function
130
+ * @param {Context} params.ctx context to bind wrapped emit to
129
131
  */
130
- trackInstrumentationUsage(version) {
131
- const prefix = NAMES.FEATURES.INSTRUMENTATION.ON_REQUIRE
132
- const instrumentationMetric = `${prefix}/${this.packageName}`
133
- const metric = this.agent.metrics.getOrCreateMetric(instrumentationMetric)
134
- if (metric.callCount === 0) {
135
- metric.incrementCallCount()
136
- }
137
-
138
- if (version) {
139
- const majorVersion = semver.major(version)
140
- const versionMetricName = `${instrumentationMetric}/Version/${majorVersion}`
141
- const versionMetric = this.agent.metrics.getOrCreateMetric(versionMetricName)
142
- if (versionMetric.callCount === 0) {
143
- versionMetric.incrementCallCount()
132
+ wrapEventEmitter({ args, index, name, ctx }) {
133
+ const orig = args[index][name]
134
+ const self = this
135
+ function wrapEmit(...emitArgs) {
136
+ const ctx = self.agent.tracer.getContext()
137
+ const [evnt] = emitArgs
138
+ if (evnt === 'end' || evnt === 'error') {
139
+ ctx?.segment?.touch()
144
140
  }
141
+ return orig.apply(this, emitArgs)
145
142
  }
143
+ args[index][name] = this.agent.tracer.bindFunction(wrapEmit, ctx, false)
146
144
  }
147
145
 
148
146
  /**
149
147
  * Creates a segment with a name, parent, transaction and optional recorder.
150
148
  * If the segment is successfully created, it will be started and added to the context.
151
- * @param {Object} params - Parameters for creating the segment
149
+ * @param {object} params - Parameters for creating the segment
152
150
  * @param {string} params.name - The name of the segment
153
- * @param {Object} params.recorder - Optional recorder for the segment
151
+ * @param {object} params.recorder - Optional recorder for the segment
154
152
  * @param {Context} params.ctx - The context containing the parent segment and transaction
155
153
  * @returns {Context} - The updated context with the new segment or existing context if segment creation fails
156
154
  */
@@ -192,9 +190,20 @@ class Subscriber {
192
190
 
193
191
  }
194
192
 
193
+ /**
194
+ * Not all subscribers need change the context on `start`.
195
+ * This is defined on base to fulfill those use cases
196
+ * @param {object} data event passed to handler
197
+ * @param {Context} ctx context passed to handler
198
+ * @returns {Context} either new context or existing
199
+ */
200
+ handler(data, ctx) {
201
+ return ctx
202
+ }
203
+
195
204
  /**
196
205
  * Checks if the subscriber is enabled based on the agent's configuration.
197
- * @returns boolean if subscriber is enabled
206
+ * @returns {boolean} if subscriber is enabled
198
207
  */
199
208
  get enabled() {
200
209
  return this.config.instrumentation[this.packageName].enabled === true
@@ -203,15 +212,19 @@ class Subscriber {
203
212
  /**
204
213
  * Enables the subscriber by binding the store to the channel and setting up the handler.
205
214
  * If the subscriber requires an active transaction, it will check the context before passing the event to the handler.
206
- * @returns {Context} - The context after processing the event
215
+ * @returns {void} The `bindStore` function with our handler.
207
216
  */
208
217
  enable() {
209
- this.channel.start.bindStore(this.store, (data) => {
218
+ /**
219
+ * Event handler for processing incoming events.
220
+ * @param {object} data Event data
221
+ * @returns {Context} The context after processing the event
222
+ */
223
+ const handler = (data) => {
210
224
  // only wrap the callback if a subscriber has a callback property defined
211
225
  if (this.callback !== null) {
212
226
  this.traceCallback(this.callback, data)
213
227
  }
214
- this.trackInstrumentationUsage(data?.moduleVersion)
215
228
  const ctx = this.agent.tracer.getContext()
216
229
  if (this.requireActiveTx && !ctx?.transaction?.isActive()) {
217
230
  this.logger.trace('Not recording event for %s, transaction is not active', this.package)
@@ -227,8 +240,28 @@ class Subscriber {
227
240
  data.segment = result?.segment
228
241
  this.logger.trace('Adding segment %s to event context', data?.segment?.name)
229
242
  }
243
+
244
+ // attach to event as it will be used to re-bind context in `asyncStart.bindStore`
245
+ if (this.propagateContext) {
246
+ data.transaction = result?.transaction
247
+ }
230
248
  return result
231
- })
249
+ }
250
+
251
+ this.channel.start.bindStore(this.store, handler)
252
+ if (this.propagateContext) {
253
+ this.channel.asyncStart.bindStore(this.store, (data) => {
254
+ const { transaction, segment } = data
255
+ const ctx = this.agent.tracer.getContext()
256
+
257
+ if (!(transaction && segment)) {
258
+ this.logger.trace('No active transaction/segment, returning existing context')
259
+ return ctx
260
+ }
261
+ const newCtx = ctx.enterSegment({ transaction, segment })
262
+ return newCtx
263
+ })
264
+ }
232
265
  }
233
266
 
234
267
  /**
@@ -236,6 +269,9 @@ class Subscriber {
236
269
  */
237
270
  disable() {
238
271
  this.channel.start.unbindStore(this.store)
272
+ if (this.propagateContext) {
273
+ this.channel.asyncStart.unbindStore(this.store)
274
+ }
239
275
  }
240
276
 
241
277
  /**
@@ -275,6 +311,54 @@ class Subscriber {
275
311
  }
276
312
  }
277
313
 
314
+ end() {
315
+ const ctx = this.agent.tracer.getContext()
316
+ ctx?.segment?.touch()
317
+ }
318
+
319
+ /**
320
+ * Handles injecting w3c tracecontext in outgoing headers. If DT is disabled, and CAT is enabled
321
+ * it properly handles CAT.
322
+ *
323
+ * **Note**: This passes in the trace, segment and trace flags manually because this is called in the `start`
324
+ * right before a function is bound to context but segment is created for the function.
325
+ *
326
+ * @param {object} params to function
327
+ * @param {Context} params.ctx current context, not yet bound to context manager
328
+ * @param {object} params.headers headers for outgoing call
329
+ * @param {boolean} params.useMqNames flag to indicate use the MQ specific CAT header names
330
+ * @returns {void}
331
+ */
332
+ insertDTHeaders({ ctx, headers, useMqNames } = {}) {
333
+ const crossAppTracingEnabled = this.config.cross_application_tracer.enabled
334
+ const distributedTracingEnabled = this.config.distributed_tracing.enabled
335
+
336
+ if (!distributedTracingEnabled && !crossAppTracingEnabled) {
337
+ this.logger.trace('Distributed Tracing and CAT are both disabled, not adding headers.')
338
+ return
339
+ }
340
+
341
+ if (!headers) {
342
+ this.logger.debug('Missing headers object, not adding headers!')
343
+ return
344
+ }
345
+
346
+ const tx = ctx?.transaction
347
+ if (!tx?.isActive()) {
348
+ this.logger.trace('No active transaction found, not adding headers.')
349
+ return
350
+ }
351
+
352
+ if (distributedTracingEnabled) {
353
+ // we have to pass in traceId, segment id, and hard code traceFlags to 1
354
+ // because we're inserting headers right before the original function is bound.
355
+ const traceFlags = tx.sampled === true ? 1 : 0
356
+ tx.insertDistributedTraceHeaders(headers, null, { traceId: tx.traceId, spanId: ctx?.segment?.id, traceFlags })
357
+ } else {
358
+ cat.addCatHeaders(this.config, tx, headers, useMqNames)
359
+ }
360
+ }
361
+
278
362
  /*
279
363
  * Subscribes to the events defined in the `events` array.
280
364
  */
@@ -0,0 +1,49 @@
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 DbQuerySubscriber = require('../db-query')
9
+
10
+ /**
11
+ * Given the arguments for Cassandra's `batch` method, this finds the first
12
+ * query in the batch.
13
+ *
14
+ * @param {Array} args - original arguments passed to the batch function
15
+ * @returns {string} The query for this batch request.
16
+ */
17
+ function findBatchQueryArg(args) {
18
+ const sql = (args[0] && args[0][0]) || ''
19
+ return sql.query || sql
20
+ }
21
+
22
+ /**
23
+ * Subscribes to the `batch` event in `cassandra-driver`
24
+ * and extracts relevant information from the queries.
25
+ */
26
+ class CassandraBatchSubscriber extends DbQuerySubscriber {
27
+ constructor({ agent, logger, channelName = 'nr_batch' }) {
28
+ super({ agent, logger, channelName, packageName: 'cassandra-driver', system: 'Cassandra' })
29
+ this.events = ['asyncEnd']
30
+ this.isBatch = true
31
+ }
32
+
33
+ handler(data, ctx) {
34
+ const { self, arguments: args } = data
35
+ this.queryString = findBatchQueryArg(args)
36
+ this.setParameters(self)
37
+ return super.handler(data, ctx)
38
+ }
39
+
40
+ setParameters(self) {
41
+ this.parameters = {}
42
+ this.parameters.product = this.system
43
+ this.parameters.database_name = self?.keyspace
44
+ this.parameters.host = self?.options?.contactPoints?.[0]
45
+ this.parameters.port_path_or_id = self?.options?.protocolOptions?.port
46
+ }
47
+ }
48
+
49
+ module.exports = CassandraBatchSubscriber
@@ -0,0 +1,35 @@
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 DbOperationSubscriber = require('../db-operation')
9
+
10
+ /**
11
+ * Subscribes to the `connect` event in `cassandra-driver`'s `Client`.
12
+ */
13
+ class CassandraConnectSubscriber extends DbOperationSubscriber {
14
+ constructor({ agent, logger, channelName = 'nr_connect' }) {
15
+ super({ agent, logger, channelName, packageName: 'cassandra-driver', system: 'Cassandra' })
16
+ this.events = ['asyncEnd']
17
+ this.operation = 'connect'
18
+ }
19
+
20
+ handler(data, ctx) {
21
+ const { self } = data
22
+ this.setParameters(self)
23
+ return super.handler(data, ctx)
24
+ }
25
+
26
+ setParameters(self) {
27
+ this.parameters = {}
28
+ this.parameters.product = this.system
29
+ this.parameters.database_name = self?.keyspace
30
+ this.parameters.host = self?.options?.contactPoints?.[0]
31
+ this.parameters.port_path_or_id = self?.options?.protocolOptions?.port
32
+ }
33
+ }
34
+
35
+ module.exports = CassandraConnectSubscriber