Skip to content

Commit 80ab93e

Browse files
feat: Azure Function utilization (#3017)
1 parent eaa3db0 commit 80ab93e

File tree

5 files changed

+148
-0
lines changed

5 files changed

+148
-0
lines changed

lib/config/default.js

+8
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,14 @@ defaultConfig.definition = () => ({
492492
formatter: boolean,
493493
default: true
494494
},
495+
/**
496+
* This flag dictates whether the agent attempts to read environment variables
497+
* and invocation context to get info about the Azure Function called.
498+
*/
499+
detect_azurefunction: {
500+
formatter: boolean,
501+
default: true
502+
},
495503
/**
496504
* This flag dictates whether the agent attempts to read files
497505
* to get info about the container the process is running in.

lib/utilization/azurefunction-info.js

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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 logger = require('../logger.js').child({ component: 'azurefunction-info' })
9+
const NAMES = require('../metrics/names.js')
10+
let results = null
11+
12+
module.exports = fetchAzureFunctionInfo
13+
module.exports.clearCache = function clearAzureFunctionCache() {
14+
results = null
15+
}
16+
17+
function fetchAzureFunctionInfo(agent, callback) {
18+
if (!agent.config.utilization || !agent.config.utilization.detect_azurefunction) {
19+
return setImmediate(callback, null, null)
20+
}
21+
22+
if (results) {
23+
return setImmediate(callback, null, results)
24+
}
25+
26+
// Detect if Azure Function.
27+
const { REGION_NAME, WEBSITE_OWNER_NAME, WEBSITE_SITE_NAME, WEBSITE_RESOURCE_GROUP } = process.env
28+
if (!REGION_NAME || !WEBSITE_OWNER_NAME || !WEBSITE_SITE_NAME) {
29+
logger.debug('Azure Function metadata was invalid.')
30+
agent.metrics.getOrCreateMetric(NAMES.UTILIZATION.AZURE_ERROR).incrementCallCount()
31+
return setImmediate(callback, null, null)
32+
}
33+
34+
// Derive vendor metadata from environment variables.
35+
const subscriptionId = WEBSITE_OWNER_NAME.split('+')[0]
36+
const resourceGroupName = WEBSITE_RESOURCE_GROUP ?? WEBSITE_OWNER_NAME.split('+').pop().split('-Linux').shift() ?? 'unknown'
37+
const azureFunctionAppName = WEBSITE_SITE_NAME
38+
results = {
39+
'faas.app_name': `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.Web/sites/${azureFunctionAppName}`,
40+
'cloud.region': REGION_NAME
41+
}
42+
43+
callback(null, results)
44+
}

lib/utilization/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const logger = require('../logger').child({ component: 'utilization' })
1010
const VENDOR_METHODS = {
1111
aws: require('./aws-info'),
1212
azure: require('./azure-info'),
13+
azurefunction: require('./azurefunction-info'),
1314
docker: require('./docker-info').getVendorInfo,
1415
ecs: require('./ecs-info'),
1516
gcp: require('./gcp-info'),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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 test = require('node:test')
9+
const assert = require('node:assert')
10+
const helper = require('#testlib/agent_helper.js')
11+
const fetchAzureFunctionInfo = require('#agentlib/utilization/azurefunction-info.js')
12+
13+
test.beforeEach((ctx) => {
14+
const agent = helper.loadMockedAgent({
15+
config: {
16+
utilization: {
17+
detect_azurefunction: true
18+
}
19+
}
20+
})
21+
ctx.nr = { agent }
22+
})
23+
24+
test.afterEach((ctx) => {
25+
helper.unloadAgent(ctx.nr.agent)
26+
ctx.nr.agent = null
27+
fetchAzureFunctionInfo.clearCache()
28+
})
29+
30+
test('should return null if detect_azurefunction is disabled', (ctx, end) => {
31+
const agent = ctx.nr.agent
32+
agent.config.utilization.detect_azurefunction = false
33+
34+
fetchAzureFunctionInfo(agent, (err, result) => {
35+
assert.strictEqual(err, null)
36+
assert.strictEqual(result, null)
37+
end()
38+
})
39+
})
40+
41+
test('should increment error metric if required environment variables are missing', (ctx, end) => {
42+
const agent = ctx.nr.agent
43+
const azureErrorMetric = agent.metrics.getOrCreateMetric('Supportability/utilization/azure/error')
44+
45+
const initialCallCount = azureErrorMetric.callCount
46+
47+
fetchAzureFunctionInfo(agent, (err, result) => {
48+
assert.strictEqual(err, null)
49+
assert.strictEqual(result, null)
50+
assert.strictEqual(azureErrorMetric.callCount, initialCallCount + 1)
51+
end()
52+
})
53+
})
54+
55+
test('should derive metadata from required environment variables', (ctx, end) => {
56+
const agent = ctx.nr.agent
57+
58+
process.env.REGION_NAME = 'Central US'
59+
process.env.WEBSITE_OWNER_NAME = '12345+resource-group'
60+
process.env.WEBSITE_SITE_NAME = 'my-function-app'
61+
62+
fetchAzureFunctionInfo(agent, (err, result) => {
63+
assert.strictEqual(err, null)
64+
assert.deepStrictEqual(result, {
65+
'faas.app_name': '/subscriptions/12345/resourceGroups/resource-group/providers/Microsoft.Web/sites/my-function-app',
66+
'cloud.region': 'Central US'
67+
})
68+
end()
69+
})
70+
})
71+
72+
test('should derive metadata from required and optional environment variables', (ctx, end) => {
73+
const agent = ctx.nr.agent
74+
75+
process.env.REGION_NAME = 'Central US'
76+
process.env.WEBSITE_RESOURCE_GROUP = 'resource-group-actual'
77+
process.env.WEBSITE_OWNER_NAME = '12345+resource-group'
78+
process.env.WEBSITE_SITE_NAME = 'my-function-app'
79+
80+
fetchAzureFunctionInfo(agent, (err, result) => {
81+
assert.strictEqual(err, null)
82+
assert.deepStrictEqual(result, {
83+
'faas.app_name': '/subscriptions/12345/resourceGroups/resource-group-actual/providers/Microsoft.Web/sites/my-function-app',
84+
'cloud.region': 'Central US'
85+
})
86+
end()
87+
})
88+
})

test/unit/utilization/main.test.js

+7
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ test('getVendors', async function (t) {
1616
ctx.nr.agent.config.utilization = {
1717
detect_aws: true,
1818
detect_azure: true,
19+
detect_azurefunction: true,
1920
detect_gcp: true,
2021
detect_docker: true,
2122
detect_kubernetes: true,
@@ -31,6 +32,7 @@ test('getVendors', async function (t) {
3132
const { agent } = ctx.nr
3233
let awsCalled = false
3334
let azureCalled = false
35+
let azureFunctionCalled = false
3436
let gcpCalled = false
3537
let dockerCalled = false
3638
let ecsCalled = false
@@ -46,6 +48,10 @@ test('getVendors', async function (t) {
4648
azureCalled = true
4749
cb()
4850
},
51+
'./azurefunction-info': function (agentArg, cb) {
52+
azureFunctionCalled = true
53+
cb()
54+
},
4955
'./gcp-info': function (agentArg, cb) {
5056
gcpCalled = true
5157
cb()
@@ -74,6 +80,7 @@ test('getVendors', async function (t) {
7480
assert.ifError(err)
7581
assert.ok(awsCalled)
7682
assert.ok(azureCalled)
83+
assert.ok(azureFunctionCalled)
7784
assert.ok(gcpCalled)
7885
assert.ok(dockerCalled)
7986
assert.ok(ecsCalled)

0 commit comments

Comments
 (0)