newrelic 13.3.2 → 13.4.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 +43 -0
- package/THIRD_PARTY_NOTICES.md +11 -11
- package/esm-loader.mjs +2 -2
- package/lib/attributes.js +4 -4
- 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/config/formatters.js +2 -3
- package/lib/config/index.js +6 -6
- package/lib/harvester.js +1 -1
- package/lib/header-processing.js +3 -3
- package/lib/instrumentation/@node-redis/client.js +1 -1
- package/lib/instrumentation/aws-sdk/v2/instrumentation-helper.js +2 -2
- package/lib/instrumentation/aws-sdk/v3/common.js +5 -5
- package/lib/instrumentation/aws-sdk/v3/dynamodb.js +4 -4
- package/lib/instrumentation/aws-sdk/v3/sns.js +9 -9
- package/lib/instrumentation/aws-sdk/v3/sqs.js +6 -6
- package/lib/instrumentation/core/http-outbound.js +1 -2
- package/lib/instrumentation/fastify/spec-builders.js +11 -11
- package/lib/instrumentation/fastify.js +4 -4
- package/lib/instrumentation/memcached.js +4 -4
- package/lib/instrumentation/mongodb.js +2 -2
- package/lib/instrumentation/undici.js +3 -3
- package/lib/instrumentations.js +0 -2
- package/lib/llm-events/aws-bedrock/bedrock-response.js +6 -6
- 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/message-transaction.js +3 -3
- package/lib/otel/context-manager.js +4 -4
- package/lib/otel/metrics/index.js +3 -3
- package/lib/otel/setup.js +6 -0
- package/lib/otel/trace-propagator.js +1 -1
- package/lib/otel/traces/index.js +1 -1
- package/lib/prioritized-attributes.js +2 -2
- package/lib/serverless/aws-lambda.js +4 -4
- package/lib/shim/datastore-shim.js +1 -1
- package/lib/shim/message-shim/consume.js +1 -1
- package/lib/shim/message-shim/subscribe-consume.js +1 -2
- package/lib/shim/promise-shim.js +1 -3
- package/lib/shim/shim.js +3 -4
- package/lib/shim/transaction-shim.js +0 -1
- package/lib/shim/webframework-shim/common.js +2 -4
- package/lib/shim/webframework-shim/middleware.js +8 -8
- 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 +1 -2
- package/lib/stats/index.js +1 -1
- package/lib/subscriber-configs.js +2 -0
- 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 +695 -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 +20 -0
- package/lib/subscribers/amqplib/utils.js +53 -0
- package/lib/subscribers/application-logs.js +1 -1
- package/lib/subscribers/base.js +167 -9
- 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 +2 -4
- package/lib/subscribers/db-query.js +5 -1
- 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/config.js +6 -6
- package/lib/subscribers/openai/utils.js +9 -11
- package/lib/subscribers/propagation.js +43 -0
- package/lib/timer.js +1 -1
- package/lib/transaction/dt-payload.js +1 -1
- package/lib/transaction/handle.js +8 -9
- package/lib/transaction/name-state.js +11 -11
- package/lib/transaction/trace/segment.js +2 -3
- 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/logger.js +6 -6
- package/lib/util/stream-sink.js +1 -1
- package/lib/utilization/docker-info.js +4 -3
- package/lib/w3c/traceparent.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
|
@@ -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,20 @@
|
|
|
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
|
+
* It attaches transaction because `propagateTx` is true
|
|
12
|
+
*
|
|
13
|
+
*/
|
|
14
|
+
class SendOrEnqueueSubscriber extends PropagationSubscriber {
|
|
15
|
+
constructor({ agent, logger }) {
|
|
16
|
+
super({ agent, logger, packageName: 'amqplib', channelName: 'nr_sendOrEnqueue', callback: -1 })
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
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
|
@@ -7,7 +7,14 @@
|
|
|
7
7
|
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
8
8
|
const { tracingChannel } = require('node:diagnostics_channel')
|
|
9
9
|
const semver = require('semver')
|
|
10
|
-
const NAMES = require('
|
|
10
|
+
const NAMES = require('#agentlib/metrics/names.js')
|
|
11
|
+
const cat = require('#agentlib/util/cat.js')
|
|
12
|
+
// Used for the `traceCallback` work.
|
|
13
|
+
// This can be removed when we add true support into orchestrion
|
|
14
|
+
const makeCall = (fn) => (...args) => fn.call(...args)
|
|
15
|
+
const ArrayPrototypeAt = makeCall(Array.prototype.at)
|
|
16
|
+
const ArrayPrototypeSplice = makeCall(Array.prototype.splice)
|
|
17
|
+
// End temp work
|
|
11
18
|
|
|
12
19
|
/**
|
|
13
20
|
* The baseline parameters available to all subscribers.
|
|
@@ -15,9 +22,8 @@ const NAMES = require('../metrics/names')
|
|
|
15
22
|
* @typedef {object} SubscriberParams
|
|
16
23
|
* @property {object} agent A New Relic Node.js agent instance.
|
|
17
24
|
* @property {object} logger An agent logger instance.
|
|
18
|
-
* @property {string} packageName The
|
|
19
|
-
*
|
|
20
|
-
* function.
|
|
25
|
+
* @property {string} packageName The package name being instrumented.
|
|
26
|
+
* This is what a developer would provide to the `require` function.
|
|
21
27
|
* @property {string} channelName A unique name for the diagnostics channel
|
|
22
28
|
* that will be created and monitored.
|
|
23
29
|
*/
|
|
@@ -41,9 +47,11 @@ const NAMES = require('../metrics/names')
|
|
|
41
47
|
* channel event names. This provides a namespace for the events we are
|
|
42
48
|
* injecting into a module.
|
|
43
49
|
* @property {boolean} [requireActiveTx=true] If true, the subscriber will only handle events when there is an active transaction.
|
|
50
|
+
* @property {boolean} [propagateTx=false] If true, it will assign the transaction from the `start.bindStore` to the context that is passed to all tracing channel events
|
|
44
51
|
* @property {string} id A unique identifier for the subscriber, combining the prefix, package name, and channel name.
|
|
45
52
|
* @property {TracingChannel} channel The tracing channel instance this subscriber will be monitoring.
|
|
46
53
|
* @property {AsyncLocalStorage} store The async local storage instance used for context management.
|
|
54
|
+
* @property {number} callback position of callback if it needs to be wrapped for instrumentation. -1 means last argument
|
|
47
55
|
*/
|
|
48
56
|
class Subscriber {
|
|
49
57
|
constructor({ agent, logger, packageName, channelName }) {
|
|
@@ -57,9 +65,11 @@ class Subscriber {
|
|
|
57
65
|
this.internal = false
|
|
58
66
|
this.prefix = 'orchestrion:'
|
|
59
67
|
this.requireActiveTx = true
|
|
68
|
+
this.propagateTx = false
|
|
60
69
|
this.id = `${this.prefix}${this.packageName}:${this.channelName}`
|
|
61
70
|
this.channel = tracingChannel(this.id)
|
|
62
71
|
this.store = agent.tracer._contextManager._asyncLocalStorage
|
|
72
|
+
this.callback = null
|
|
63
73
|
}
|
|
64
74
|
|
|
65
75
|
shouldCreateSegment(parent) {
|
|
@@ -68,6 +78,49 @@ class Subscriber {
|
|
|
68
78
|
)
|
|
69
79
|
}
|
|
70
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Note: This is a temporary patch until we can get the correct implementation
|
|
83
|
+
* of `tracingChannel.traceCallback` into orchestrion-js.
|
|
84
|
+
*
|
|
85
|
+
* This will wrap a callback at a given position and reassign the callback argument to the wrapped one
|
|
86
|
+
*
|
|
87
|
+
* @param {number} position index of the callback, you can specify -1 to be the last
|
|
88
|
+
* @param {object} context the event passed to the tracing channel hooks
|
|
89
|
+
*/
|
|
90
|
+
traceCallback(position, context) {
|
|
91
|
+
this.logger.trace('Wrapping the callback at position %s', position)
|
|
92
|
+
const { asyncStart, asyncEnd, error } = this.channel
|
|
93
|
+
function wrappedCallback(err, res) {
|
|
94
|
+
// assigning a boolean to the context so we know that the
|
|
95
|
+
// `error`, `asyncStart`, and `asyncEnd` are coming from the wrapped callback
|
|
96
|
+
context.callback = true
|
|
97
|
+
if (err) {
|
|
98
|
+
context.error = err
|
|
99
|
+
error.publish(context)
|
|
100
|
+
} else {
|
|
101
|
+
context.result = res
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Using runStores here enables manual context failure recovery
|
|
105
|
+
asyncStart.runStores(context, () => {
|
|
106
|
+
try {
|
|
107
|
+
if (callback) {
|
|
108
|
+
return Reflect.apply(callback, this, arguments)
|
|
109
|
+
}
|
|
110
|
+
} finally {
|
|
111
|
+
asyncEnd.publish(context)
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const callback = ArrayPrototypeAt(context.arguments, position)
|
|
117
|
+
if (typeof callback !== 'function') {
|
|
118
|
+
this.logger.trace('Callback is not present, not wrapping')
|
|
119
|
+
} else {
|
|
120
|
+
ArrayPrototypeSplice(context.arguments, position, 1, wrappedCallback)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
71
124
|
/**
|
|
72
125
|
* Creates the `Supportability/Features/Instrumentation/OnRequire/<packageName>`
|
|
73
126
|
* and `Supportability/Features/Instrumentation/OnRequire/<packageName>/Version/<majorVersion>`
|
|
@@ -123,12 +176,12 @@ class Subscriber {
|
|
|
123
176
|
segment.opaque = this.opaque
|
|
124
177
|
segment.shimId = this.packageName
|
|
125
178
|
segment.start()
|
|
126
|
-
this.logger.trace('Created segment %s', name)
|
|
179
|
+
this.logger.trace('Created segment %s, returning new context', name)
|
|
127
180
|
this.addAttributes(segment)
|
|
128
181
|
const newCtx = ctx.enterSegment({ segment })
|
|
129
182
|
return newCtx
|
|
130
183
|
} else {
|
|
131
|
-
this.logger.trace('Failed to create segment for %s', name)
|
|
184
|
+
this.logger.trace('Failed to create segment for %s, returning existing context', name)
|
|
132
185
|
return ctx
|
|
133
186
|
}
|
|
134
187
|
}
|
|
@@ -142,8 +195,20 @@ class Subscriber {
|
|
|
142
195
|
|
|
143
196
|
}
|
|
144
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Not all subscribers need change the context on `start`.
|
|
200
|
+
* This is defined on base to fulfill those use cases
|
|
201
|
+
* @param {object} data event passed to handler
|
|
202
|
+
* @param {Context} ctx context passed to handler
|
|
203
|
+
* @returns {Context} either new context or existing
|
|
204
|
+
*/
|
|
205
|
+
handler(data, ctx) {
|
|
206
|
+
return ctx
|
|
207
|
+
}
|
|
208
|
+
|
|
145
209
|
/**
|
|
146
210
|
* Checks if the subscriber is enabled based on the agent's configuration.
|
|
211
|
+
* @returns boolean if subscriber is enabled
|
|
147
212
|
*/
|
|
148
213
|
get enabled() {
|
|
149
214
|
return this.config.instrumentation[this.packageName].enabled === true
|
|
@@ -156,14 +221,31 @@ class Subscriber {
|
|
|
156
221
|
*/
|
|
157
222
|
enable() {
|
|
158
223
|
this.channel.start.bindStore(this.store, (data) => {
|
|
224
|
+
// only wrap the callback if a subscriber has a callback property defined
|
|
225
|
+
if (this.callback !== null) {
|
|
226
|
+
this.traceCallback(this.callback, data)
|
|
227
|
+
}
|
|
159
228
|
this.trackInstrumentationUsage(data?.moduleVersion)
|
|
160
229
|
const ctx = this.agent.tracer.getContext()
|
|
161
230
|
if (this.requireActiveTx && !ctx?.transaction?.isActive()) {
|
|
162
|
-
this.logger.
|
|
231
|
+
this.logger.trace('Not recording event for %s, transaction is not active', this.package)
|
|
163
232
|
return ctx
|
|
164
233
|
}
|
|
165
234
|
|
|
166
|
-
|
|
235
|
+
const result = this.handler(data, ctx)
|
|
236
|
+
// we cannot rely on the context manager to obtain the active segment
|
|
237
|
+
// in the `asyncStart` and `asyncEnd` events. This is because other instrumented
|
|
238
|
+
// functions are being executed at times. so we assign the active segment on the data
|
|
239
|
+
// so it can be used later to properly touch the segment in `asyncStart` and `asyncEnd`
|
|
240
|
+
if (this.callback !== null) {
|
|
241
|
+
data.segment = result?.segment
|
|
242
|
+
this.logger.trace('Adding segment %s to event context', data?.segment?.name)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (this.propagateTx) {
|
|
246
|
+
data.transaction = result?.transaction
|
|
247
|
+
}
|
|
248
|
+
return result
|
|
167
249
|
})
|
|
168
250
|
}
|
|
169
251
|
|
|
@@ -174,15 +256,91 @@ class Subscriber {
|
|
|
174
256
|
this.channel.start.unbindStore(this.store)
|
|
175
257
|
}
|
|
176
258
|
|
|
259
|
+
/**
|
|
260
|
+
* This should only be used for callback based functions to touch the segment for the function
|
|
261
|
+
* that implements a callback.
|
|
262
|
+
* @param {object} data event passed to asyncStart hook
|
|
263
|
+
*/
|
|
264
|
+
asyncStart(data) {
|
|
265
|
+
const ctx = this.agent.tracer.getContext()
|
|
266
|
+
if (data.callback !== true || this.internal === true || (this.requireActiveTx && !ctx?.transaction?.isActive())) {
|
|
267
|
+
this.logger.trace('Not touching parent in asyncStart for %s, transaction is not active? %s, segment is internal? %s, or no callback to bind', this.id, ctx?.transaction?.isActive(), this.internal)
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
this.logger.trace('touching segment %s, in asyncStart', data?.segment?.name)
|
|
272
|
+
data?.segment?.touch()
|
|
273
|
+
}
|
|
274
|
+
|
|
177
275
|
/**
|
|
178
276
|
* Common handler for when async events end.
|
|
179
277
|
* It gets the context and touches the segment if it exists.
|
|
278
|
+
* @param {object} data event passed to asyncEnd hook
|
|
180
279
|
*/
|
|
181
|
-
asyncEnd() {
|
|
280
|
+
asyncEnd(data) {
|
|
281
|
+
const ctx = this.agent.tracer.getContext()
|
|
282
|
+
if (this.internal === true) {
|
|
283
|
+
this.logger.trace('asyncEnd occurring for %s internal event, not touching segment', this.id)
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (data?.callback === true) {
|
|
288
|
+
this.logger.trace('touching callback segment %s, in asyncEnd', data?.segment?.name)
|
|
289
|
+
data?.segment?.touch()
|
|
290
|
+
} else {
|
|
291
|
+
this.logger.trace('touching segment %s, in asyncEnd', ctx?.segment?.name)
|
|
292
|
+
ctx?.segment?.touch()
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
end() {
|
|
182
297
|
const ctx = this.agent.tracer.getContext()
|
|
183
298
|
ctx?.segment?.touch()
|
|
184
299
|
}
|
|
185
300
|
|
|
301
|
+
/**
|
|
302
|
+
* Handles injecting w3c tracecontext in outgoing headers. If DT is disabled, and CAT is enabled
|
|
303
|
+
* it properly handles CAT.
|
|
304
|
+
*
|
|
305
|
+
* **Note**: This passes in the trace, segment and trace flags manually because this is called in the `start`
|
|
306
|
+
* right before a function is bound to context but segment is created for the function.
|
|
307
|
+
*
|
|
308
|
+
* @param {object} params to function
|
|
309
|
+
* @param {Context} params.ctx current context, not yet bound to context manager
|
|
310
|
+
* @param {object} params.headers headers for outgoing call
|
|
311
|
+
* @param {boolean} params.useMqNames flag to indicate use the MQ specific CAT header names
|
|
312
|
+
* @returns {void}
|
|
313
|
+
*/
|
|
314
|
+
insertDTHeaders({ ctx, headers, useMqNames } = {}) {
|
|
315
|
+
const crossAppTracingEnabled = this.config.cross_application_tracer.enabled
|
|
316
|
+
const distributedTracingEnabled = this.config.distributed_tracing.enabled
|
|
317
|
+
|
|
318
|
+
if (!distributedTracingEnabled && !crossAppTracingEnabled) {
|
|
319
|
+
this.logger.trace('Distributed Tracing and CAT are both disabled, not adding headers.')
|
|
320
|
+
return
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (!headers) {
|
|
324
|
+
this.logger.debug('Missing headers object, not adding headers!')
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const tx = ctx?.transaction
|
|
329
|
+
if (!tx?.isActive()) {
|
|
330
|
+
this.logger.trace('No active transaction found, not adding headers.')
|
|
331
|
+
return
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (distributedTracingEnabled) {
|
|
335
|
+
// we have to pass in traceId, segment id, and hard code traceFlags to 1
|
|
336
|
+
// because we're inserting headers right before the original function is bound.
|
|
337
|
+
const traceFlags = tx.sampled === true ? 1 : 0
|
|
338
|
+
tx.insertDistributedTraceHeaders(headers, null, { traceId: tx.traceId, spanId: ctx?.segment?.id, traceFlags })
|
|
339
|
+
} else {
|
|
340
|
+
cat.addCatHeaders(this.config, tx, headers, useMqNames)
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
186
344
|
/*
|
|
187
345
|
* Subscribes to the events defined in the `events` array.
|
|
188
346
|
*/
|
|
@@ -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
|