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.
- package/NEWS.md +54 -0
- package/THIRD_PARTY_NOTICES.md +11 -11
- package/api.js +1 -1
- package/esm-loader.mjs +2 -2
- package/lib/agent.js +2 -2
- package/lib/aggregators/event-aggregator.js +0 -2
- package/lib/attributes.js +6 -6
- package/lib/collector/api.js +1 -1
- package/lib/collector/facts.js +1 -1
- package/lib/collector/http-agents.js +2 -2
- package/lib/collector/parse-response.js +9 -12
- package/lib/collector/remote-method.js +4 -5
- package/lib/collector/serverless.js +1 -1
- package/lib/config/build-instrumentation-config.js +13 -3
- package/lib/config/default.js +21 -10
- package/lib/config/formatters.js +5 -6
- package/lib/config/index.js +37 -24
- package/lib/harvester.js +1 -1
- package/lib/header-processing.js +3 -3
- package/lib/health-reporter.js +2 -2
- package/lib/instrumentation/@node-redis/client.js +1 -1
- package/lib/instrumentation/@prisma/client.js +1 -1
- package/lib/instrumentation/aws-sdk/util.js +3 -3
- package/lib/instrumentation/aws-sdk/v2/instrumentation-helper.js +2 -2
- package/lib/instrumentation/aws-sdk/v3/bedrock.js +5 -5
- package/lib/instrumentation/aws-sdk/v3/common.js +14 -14
- package/lib/instrumentation/aws-sdk/v3/dynamodb.js +13 -13
- package/lib/instrumentation/aws-sdk/v3/lambda.js +3 -3
- package/lib/instrumentation/aws-sdk/v3/sns.js +12 -12
- package/lib/instrumentation/aws-sdk/v3/sqs.js +9 -9
- package/lib/instrumentation/core/http-outbound.js +1 -2
- package/lib/instrumentation/fastify/spec-builders.js +9 -29
- package/lib/instrumentation/fastify.js +23 -94
- package/lib/instrumentation/kafkajs/consumer.js +1 -1
- package/lib/instrumentation/koa/instrumentation.js +1 -1
- package/lib/instrumentation/langchain/runnable.js +1 -1
- package/lib/instrumentation/memcached.js +4 -4
- package/lib/instrumentation/mongodb.js +2 -2
- package/lib/instrumentation/nextjs/next-server.js +1 -3
- package/lib/instrumentation/nextjs/utils.js +2 -2
- package/lib/instrumentation/restify.js +2 -2
- package/lib/instrumentation/when/contextualizer.js +1 -1
- package/lib/instrumentation-descriptor.js +2 -11
- package/lib/instrumentations.js +1 -20
- package/lib/llm-events/aws-bedrock/bedrock-response.js +6 -6
- package/lib/llm-events/aws-bedrock/chat-completion-summary.js +2 -1
- package/lib/llm-events/aws-bedrock/converse-stream-handler.js +1 -1
- package/lib/llm-events/aws-bedrock/stream-handler.js +4 -4
- package/lib/metrics/index.js +2 -2
- package/lib/metrics/normalizer/rule.js +1 -1
- package/lib/metrics/normalizer.js +4 -5
- package/lib/metrics/recorders/database-operation.js +2 -3
- package/lib/metrics/recorders/message-transaction.js +3 -3
- package/lib/metrics/recorders/middleware.js +1 -2
- package/lib/otel/context-manager.js +4 -4
- package/lib/otel/trace-propagator.js +1 -1
- package/lib/otel/traces/segments/database.js +1 -1
- package/lib/otel/traces/utils.js +9 -9
- package/lib/prioritized-attributes.js +2 -2
- package/lib/{adaptive-sampler.js → samplers/adaptive-sampler.js} +8 -0
- package/lib/serverless/aws-lambda.js +4 -4
- package/lib/shim/datastore-shim.js +2 -2
- package/lib/shim/message-shim/consume.js +4 -4
- package/lib/shim/message-shim/subscribe-consume.js +1 -2
- package/lib/shim/promise-shim.js +1 -4
- package/lib/shim/shim.js +14 -29
- package/lib/shim/specs/class.js +3 -3
- package/lib/shim/specs/middleware-mounter.js +2 -2
- package/lib/shim/specs/middleware.js +3 -3
- package/lib/shim/specs/query.js +1 -1
- package/lib/shim/specs/recorder.js +1 -1
- package/lib/shim/specs/wrap.js +2 -2
- package/lib/shim/transaction-shim.js +0 -1
- package/lib/shim/webframework-shim/common.js +2 -4
- package/lib/shim/webframework-shim/index.js +10 -1
- package/lib/shim/webframework-shim/middleware.js +17 -21
- package/lib/shimmer.js +56 -103
- package/lib/spans/span-context.js +1 -1
- package/lib/spans/span-event-aggregator.js +2 -2
- package/lib/spans/span-event.js +6 -5
- package/lib/spans/streaming-span-attributes.js +1 -1
- package/lib/spans/streaming-span-event-aggregator.js +5 -3
- package/lib/spans/streaming-span-event.js +1 -1
- package/lib/stats/index.js +1 -1
- package/lib/subscriber-configs.js +6 -1
- package/lib/subscribers/amqplib/accept-message.js +14 -0
- package/lib/subscribers/amqplib/callback-model.js +39 -0
- package/lib/subscribers/amqplib/channel-model.js +39 -0
- package/lib/subscribers/amqplib/config.js +697 -0
- package/lib/subscribers/amqplib/connect.js +74 -0
- package/lib/subscribers/amqplib/consume.js +54 -0
- package/lib/subscribers/amqplib/get-cb.js +16 -0
- package/lib/subscribers/amqplib/get.js +50 -0
- package/lib/subscribers/amqplib/purge-queue-cb.js +16 -0
- package/lib/subscribers/amqplib/purge-queue.js +37 -0
- package/lib/subscribers/amqplib/send-message.js +44 -0
- package/lib/subscribers/amqplib/send-or-enqueue.js +18 -0
- package/lib/subscribers/amqplib/utils.js +53 -0
- package/lib/subscribers/application-logs.js +1 -1
- package/lib/subscribers/base.js +113 -29
- package/lib/subscribers/cassandra-driver/client-batch.js +49 -0
- package/lib/subscribers/cassandra-driver/client-connect.js +35 -0
- package/lib/subscribers/cassandra-driver/client-each-row.js +36 -0
- package/lib/subscribers/cassandra-driver/client-execute.js +36 -0
- package/lib/subscribers/cassandra-driver/client-shutdown.js +32 -0
- package/lib/subscribers/cassandra-driver/config.js +145 -0
- package/lib/subscribers/cassandra-driver/legacy-client-batch.js +22 -0
- package/lib/subscribers/cassandra-driver/legacy-client-connect.js +22 -0
- package/lib/subscribers/cassandra-driver/legacy-client-each-row.js +22 -0
- package/lib/subscribers/cassandra-driver/legacy-client-execute.js +21 -0
- package/lib/subscribers/cassandra-driver/legacy-client-shutdown.js +32 -0
- package/lib/subscribers/create-config.js +4 -6
- package/lib/subscribers/db-operation.js +12 -0
- package/lib/subscribers/db-query.js +5 -1
- package/lib/subscribers/dc-base.js +89 -0
- package/lib/subscribers/fastify/add-hook.js +46 -0
- package/lib/subscribers/fastify/common.js +121 -0
- package/lib/subscribers/fastify/config.js +39 -0
- package/lib/subscribers/fastify/decorate.js +35 -0
- package/lib/subscribers/fastify/index.js +31 -0
- package/lib/subscribers/mcp-sdk/client-request.js +1 -1
- package/lib/subscribers/mcp-sdk/config.js +2 -0
- package/lib/subscribers/message-consumer.js +146 -0
- package/lib/subscribers/message-producer.js +60 -0
- package/lib/subscribers/meta-subscriber.js +69 -0
- package/lib/subscribers/openai/base.js +27 -1
- package/lib/subscribers/openai/chat.js +0 -27
- package/lib/subscribers/openai/config.js +3 -3
- package/lib/subscribers/openai/utils.js +10 -11
- package/lib/subscribers/pg/config.js +91 -0
- package/lib/subscribers/pg/connect.js +28 -0
- package/lib/subscribers/pg/native-connect.js +25 -0
- package/lib/subscribers/pg/native-query.js +22 -0
- package/lib/subscribers/pg/query.js +66 -0
- package/lib/subscribers/pino/index.js +2 -2
- package/lib/subscribers/propagation.js +23 -0
- package/lib/subscribers/undici/config.js +11 -0
- package/lib/subscribers/undici/index.js +195 -0
- package/lib/synthetics.js +4 -4
- package/lib/timer.js +1 -1
- package/lib/tracking-packages.js +25 -0
- package/lib/transaction/dt-payload.js +1 -1
- package/lib/transaction/handle.js +8 -9
- package/lib/transaction/index.js +25 -21
- package/lib/transaction/name-state.js +11 -11
- package/lib/transaction/trace/segment.js +3 -4
- package/lib/transaction/tracer/index.js +1 -1
- package/lib/util/attribute-types.js +1 -1
- package/lib/util/byte-limit.js +1 -1
- package/lib/util/cat.js +13 -13
- package/lib/util/flatten.js +2 -3
- package/lib/util/is-absolute-path.js +1 -1
- package/lib/util/is-string.js +15 -0
- package/lib/util/llm-utils.js +3 -3
- package/lib/util/logger.js +8 -8
- package/lib/util/stream-sink.js +1 -1
- package/lib/utilization/docker-info.js +4 -3
- package/lib/utilization/ecs-info.js +1 -1
- package/lib/w3c/traceparent.js +3 -3
- package/lib/w3c/tracestate.js +1 -1
- package/package.json +2 -2
- package/lib/instrumentation/amqplib/amqplib.js +0 -122
- package/lib/instrumentation/amqplib/channel-model.js +0 -124
- package/lib/instrumentation/amqplib/channel.js +0 -72
- package/lib/instrumentation/amqplib/nr-hooks.js +0 -21
- package/lib/instrumentation/amqplib/utils.js +0 -143
- package/lib/instrumentation/cassandra-driver.js +0 -131
- package/lib/instrumentation/pg.js +0 -137
- 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) {
|
package/lib/subscribers/base.js
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
123
|
-
*
|
|
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 {
|
|
128
|
-
* @
|
|
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
|
-
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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 {
|
|
149
|
+
* @param {object} params - Parameters for creating the segment
|
|
152
150
|
* @param {string} params.name - The name of the segment
|
|
153
|
-
* @param {
|
|
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 {
|
|
215
|
+
* @returns {void} The `bindStore` function with our handler.
|
|
207
216
|
*/
|
|
208
217
|
enable() {
|
|
209
|
-
|
|
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
|