newrelic
Version:
New Relic agent
152 lines (133 loc) • 4.51 kB
JavaScript
/*
* Copyright 2026 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
const Transaction = require('#agentlib/transaction/index.js')
const { DESTINATIONS: DESTS } = Transaction
module.exports = class AzureHandler {
constructor(subscriber) {
this.subscriber = subscriber
this.agent = subscriber.agent
}
handleColdStart(transaction) {
if (this.subscriber.coldStart === true) {
transaction.trace.attributes.addAttribute(DESTS.TRANS_COMMON, 'faas.coldStart', true)
this.subscriber.coldStart = false
}
}
createTransaction({ handlerArgs, ctx }) {
const [, context] = handlerArgs
const transaction = new Transaction(this.agent)
transaction.type = this.type
transaction.setPartialName(`AzureFunction/${context.functionName}`)
ctx = ctx.enterTransaction(transaction)
const newCtx = this.createSegment({ handlerArgs, ctx })
transaction.baseSegment = newCtx.segment
return newCtx
}
async handle({ thisArg, originalHandler, handlerArgs }) {
const ctx = this.agent.tracer.getContext()
if (ctx.transaction != null) {
return originalHandler.apply(thisArg, handlerArgs)
}
const [, context] = handlerArgs
const newCtx = this.createTransaction({ handlerArgs, ctx })
const transaction = newCtx?.transaction
this.addFaasAttributes(transaction, context)
// We need to wrap up all of our operations into a function that we
// can execute within the same context. If we only run the original handler
// in the context, then when we try to finalize the transaction we will
// not have access to the segment.
const wrappedHandler = async (...args) => {
const result = await originalHandler.apply(thisArg, args)
this.handleColdStart(transaction)
return this.finalizeTransaction({ result, transaction })
}
return await this.runHandlerInContext({
originalHandler: wrappedHandler,
ctx: newCtx,
thisArg,
handlerArgs
})
}
runHandlerInContext({ originalHandler, ctx, thisArg, handlerArgs }) {
return this.agent.tracer.runInContext({ handler: originalHandler, context: ctx, thisArg, args: handlerArgs })
}
finalizeTransaction({ result, transaction }) {
transaction.end()
return result
}
addFaasAttributes(transaction, functionContext) {
transaction.trace.attributes.addAttribute(
DESTS.TRANS_COMMON,
'faas.invocation_id',
functionContext.invocationId ?? 'unknown'
)
transaction.trace.attributes.addAttribute(
DESTS.TRANS_COMMON,
'faas.name',
functionContext.functionName ?? 'unknown'
)
transaction.trace.attributes.addAttribute(
DESTS.TRANS_COMMON,
'faas.trigger',
this.mapTriggerType(functionContext)
)
transaction.trace.attributes.addAttribute(
DESTS.TRANS_COMMON,
'cloud.resource_id',
this.buildCloudResourceId(functionContext)
)
}
mapTriggerType(functionContext) {
const input = functionContext.options?.trigger?.type
// Input types are found at:
// https://github.com/Azure/azure-functions-nodejs-library/blob/138c021/src/trigger.ts
// https://learn.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings?tabs=isolated-process%2Cnode-v4%2Cpython-v2&pivots=programming-language-javascript#supported-bindings
switch (input) {
case 'httpTrigger': {
return 'http'
}
case 'timerTrigger': {
return 'timer'
}
case 'blobTrigger':
case 'cosmosDBTrigger':
case 'daprBindingTrigger':
case 'mysqlTrigger':
case 'queueTrigger':
case 'sqlTrigger': {
return 'datasource'
}
case 'daprTopicTrigger':
case 'eventGridTrigger':
case 'eventHubTrigger':
case 'kafkaTrigger':
case 'rabbitMQTrigger':
case 'redisListTrigger':
case 'redisPubSubTrigger':
case 'redisStreamTrigger':
case 'serviceBusTrigger':
case 'signalRTrigger':
case 'webPubSubTrigger': {
return 'pubsub'
}
default: {
return 'other'
}
}
}
buildCloudResourceId(functionContext) {
return [
'/subscriptions/',
this.subscriber.subscriptionId,
'/resourceGroups/',
this.subscriber.resourceGroup,
'/providers/Microsoft.Web/sites/',
this.subscriber.azureFunctionAppName,
'/functions/',
functionContext.functionName
].join('')
}
}