newrelic
Version:
New Relic agent
162 lines (148 loc) • 5.05 kB
JavaScript
/*
* Copyright 2026 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
// This file exists specifically so that the `kafkajs` instrumentation, and
// possibly others, can reuse the core logic of the message consumer
// abstraction. Unfortunately, the design of that module requires this.
module.exports = {
addConsumeParametersToTransaction,
handleResult,
initiateTransaction,
startConsumeSegment
}
const Transaction = require('#agentlib/transaction/index.js')
const messageTransactionRecorder = require('#agentlib/metrics/recorders/message-transaction.js')
const isString = require('#agentlib/util/is-string.js')
/**
* Adds parameters used during the execution of the consumer function to the
* given transaction.
*
* @param {object} params Function parameters.
* @param {ConsumerParameters} params.parameters Key value has of parameters
* to add.
* @param {Transaction} params.tx The transaction to add the parameters to.
* @param {object} params.agentConfig The current configuration for the agent.
*/
function addConsumeParametersToTransaction({
parameters = {},
tx,
agentConfig
}) {
for (const [key, value] of Object.entries(parameters)) {
if (['host', 'port'].includes(key)) {
tx.baseSegment.addAttribute(key, value)
} else {
tx.trace.attributes.addAttribute(
Transaction.DESTINATIONS.NONE,
`message.parameters.${key}`,
value
)
tx.baseSegment.attributes.addAttribute(
Transaction.DESTINATIONS.NONE,
`message.parameters.${key}`,
value
)
}
if (key === 'routing_key' && agentConfig.high_security === false) {
tx.trace.attributes.addAttribute(
Transaction.DESTINATIONS.TRANS_COMMON,
'message.routingKey',
value
)
tx.baseSegment.addSpanAttribute('message.routingKey', value)
}
}
if (isString(parameters.queue) && agentConfig.high_security === false) {
const { queue } = parameters
tx.trace.attributes.addAttribute(
Transaction.DESTINATIONS.TRANS_COMMON,
'message.queueName',
queue
)
tx.baseSegment.addSpanAttribute('message.queueName', queue)
}
}
/**
* When a consumer method finishes, it returns either a promise or some
* traditional object. We need to determine which one we have, and then
* end the transaction associated with the consumer method. This utility
* function does this work.
*
* @param {Promise|*} result The consumer method's returned entity.
* @param {Transaction} transaction The transaction encapsulating the
* consumer method operation.
*
* @returns {Promise|Transaction} The result of ending the transaction.
*/
function handleResult(result, transaction) {
if (typeof result?.then === 'function') {
const localPromise = Promise.resolve(result)
return localPromise.finally(() => transaction?.end())
}
return transaction.end()
}
/**
* Creates a new message {@link Transaction}, activates it, and returns
* the context associated with the transaction.
*
* @param {Agent} agent The current agent instance.
*
* @returns {Context} A new context instance.
*/
function initiateTransaction(agent) {
const ctx = agent.tracer.getContext()
const tx = new Transaction(agent)
tx.type = 'message'
return ctx.enterTransaction(tx)
}
/**
* When the consumer function is invoked, this function should be used to
* start the {@link TraceSegment} for the operations of the consumer.
*
* @param {object} params Function parameters.
* @param {Agent} params.agent The current agent instance.
* @param {ConsumerParameters} params.consumerParameters The parameters the
* consumer function is using for communicating with the remote system.
* @param {object} params.headers Set of request headers the consumer
* function uses during its operation. May be empty.
* @param {string} params.txName The fully resolved transaction name for
* the current segment, e.g.
* `OtherTransaction/Message/Kafka/Topic/Consume/Named/topic-whatever`.
* @param {string} params.transport The target transport name as defined
* by {@link Transaction#TRANSPORT_TYPES}.
*
* @returns {Context} The current context.
*/
function startConsumeSegment({
agent,
consumerParameters,
headers,
txName,
transport
}) {
const ctx = agent.tracer.getContext()
const tx = ctx.transaction
tx.setPartialName(txName)
// Note: this is not using `Subscriber.createSegment`.
// This is because `Subscriber.createSegment` enters the segment and
// returns a new ctx. Since this isn't in bindStore, entering a new
// context doesn't bind it to the store.
tx.baseSegment = agent.tracer.createSegment({
name: tx.getFullName(),
recorder: messageTransactionRecorder,
parent: tx.trace.root,
transaction: tx
})
tx.baseSegment.start()
addConsumeParametersToTransaction({
parameters: consumerParameters,
agentConfig: agent.config,
tx
})
if (headers != null) {
tx.addDtHeaders({ headers, transport })
}
return ctx
}