Skip to content

Commit

Permalink
Add support for Azure App Services tags in profiler (#4803)
Browse files Browse the repository at this point in the history
  • Loading branch information
szegedi authored and rochdev committed Oct 30, 2024
1 parent 7469e74 commit d63df3e
Show file tree
Hide file tree
Showing 3 changed files with 232 additions and 1 deletion.
120 changes: 120 additions & 0 deletions packages/dd-trace/src/azure_metadata.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
'use strict'

// eslint-disable-next-line max-len
// Modeled after https://github.com/DataDog/libdatadog/blob/f3994857a59bb5679a65967138c5a3aec418a65f/ddcommon/src/azure_app_services.rs

const os = require('os')
const { getIsAzureFunction } = require('./serverless')

function extractSubscriptionID (ownerName) {
if (ownerName !== undefined) {
const subId = ownerName.split('+')[0].trim()
if (subId.length > 0) {
return subId
}
}
return undefined
}

function extractResourceGroup (ownerName) {
return /.+\+(.+)-.+webspace(-Linux)?/.exec(ownerName)?.[1]
}

function buildResourceID (subscriptionID, siteName, resourceGroup) {
if (subscriptionID === undefined || siteName === undefined || resourceGroup === undefined) {
return undefined
}
return `/subscriptions/${subscriptionID}/resourcegroups/${resourceGroup}/providers/microsoft.web/sites/${siteName}`
.toLowerCase()
}

function trimObject (obj) {
Object.entries(obj)
.filter(([_, value]) => value === undefined)
.forEach(([key, _]) => { delete obj[key] })
return obj
}

function buildMetadata () {
const {
COMPUTERNAME,
DD_AAS_DOTNET_EXTENSION_VERSION,
FUNCTIONS_EXTENSION_VERSION,
FUNCTIONS_WORKER_RUNTIME,
FUNCTIONS_WORKER_RUNTIME_VERSION,
WEBSITE_INSTANCE_ID,
WEBSITE_OWNER_NAME,
WEBSITE_OS,
WEBSITE_RESOURCE_GROUP,
WEBSITE_SITE_NAME
} = process.env

const subscriptionID = extractSubscriptionID(WEBSITE_OWNER_NAME)

const siteName = WEBSITE_SITE_NAME

const [siteKind, siteType] = getIsAzureFunction()
? ['functionapp', 'function']
: ['app', 'app']

const resourceGroup = WEBSITE_RESOURCE_GROUP ?? extractResourceGroup(WEBSITE_OWNER_NAME)

return trimObject({
extensionVersion: DD_AAS_DOTNET_EXTENSION_VERSION,
functionRuntimeVersion: FUNCTIONS_EXTENSION_VERSION,
instanceID: WEBSITE_INSTANCE_ID,
instanceName: COMPUTERNAME,
operatingSystem: WEBSITE_OS ?? os.platform(),
resourceGroup,
resourceID: buildResourceID(subscriptionID, siteName, resourceGroup),
runtime: FUNCTIONS_WORKER_RUNTIME,
runtimeVersion: FUNCTIONS_WORKER_RUNTIME_VERSION,
siteKind,
siteName,
siteType,
subscriptionID
})
}

function getAzureAppMetadata () {
// DD_AZURE_APP_SERVICES is an environment variable introduced by the .NET APM team and is set automatically for
// anyone using the Datadog APM Extensions (.NET, Java, or Node) for Windows Azure App Services
// eslint-disable-next-line max-len
// See: https://github.com/DataDog/datadog-aas-extension/blob/01f94b5c28b7fa7a9ab264ca28bd4e03be603900/node/src/applicationHost.xdt#L20-L21
return process.env.DD_AZURE_APP_SERVICES !== undefined ? buildMetadata() : undefined
}

function getAzureFunctionMetadata () {
return getIsAzureFunction() ? buildMetadata() : undefined
}

// eslint-disable-next-line max-len
// Modeled after https://github.com/DataDog/libdatadog/blob/92272e90a7919f07178f3246ef8f82295513cfed/profiling/src/exporter/mod.rs#L187
// eslint-disable-next-line max-len
// and https://github.com/DataDog/libdatadog/blob/f3994857a59bb5679a65967138c5a3aec418a65f/trace-utils/src/trace_utils.rs#L533
function getAzureTagsFromMetadata (metadata) {
if (metadata === undefined) {
return {}
}
return trimObject({
'aas.environment.extension_version': metadata.extensionVersion,
'aas.environment.function_runtime': metadata.functionRuntimeVersion,
'aas.environment.instance_id': metadata.instanceID,
'aas.environment.instance_name': metadata.instanceName,
'aas.environment.os': metadata.operatingSystem,
'aas.environment.runtime': metadata.runtime,
'aas.environment.runtime_version': metadata.runtimeVersion,
'aas.resource.group': metadata.resourceGroup,
'aas.resource.id': metadata.resourceID,
'aas.site.kind': metadata.siteKind,
'aas.site.name': metadata.siteName,
'aas.site.type': metadata.siteType,
'aas.subscription.id': metadata.subscriptionID
})
}

module.exports = {
getAzureAppMetadata,
getAzureFunctionMetadata,
getAzureTagsFromMetadata
}
4 changes: 3 additions & 1 deletion packages/dd-trace/src/profiling/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const { oomExportStrategies, snapshotKinds } = require('./constants')
const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags')
const { tagger } = require('./tagger')
const { isFalse, isTrue } = require('../util')
const { getAzureTagsFromMetadata, getAzureAppMetadata } = require('../azure_metadata')

class Config {
constructor (options = {}) {
Expand Down Expand Up @@ -71,7 +72,8 @@ class Config {
this.tags = Object.assign(
tagger.parse(DD_TAGS),
tagger.parse(options.tags),
tagger.parse({ env, host, service, version, functionname })
tagger.parse({ env, host, service, version, functionname }),
getAzureTagsFromMetadata(getAzureAppMetadata())
)

// Add source code integration tags if available
Expand Down
109 changes: 109 additions & 0 deletions packages/dd-trace/test/azure_metadata.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
'use strict'

require('./setup/tap')

const os = require('os')
const { getAzureAppMetadata, getAzureTagsFromMetadata } = require('../src/azure_metadata')

describe('Azure metadata', () => {
describe('for apps is', () => {
it('not provided without DD_AZURE_APP_SERVICES', () => {
delete process.env.DD_AZURE_APP_SERVICES
expect(getAzureAppMetadata()).to.be.undefined
})

it('provided with DD_AZURE_APP_SERVICES', () => {
delete process.env.COMPUTERNAME // actually defined on Windows
process.env.DD_AZURE_APP_SERVICES = '1'
delete process.env.WEBSITE_SITE_NAME
expect(getAzureAppMetadata()).to.deep.equal({ operatingSystem: os.platform(), siteKind: 'app', siteType: 'app' })
})
})

it('provided completely with minimum vars', () => {
delete process.env.WEBSITE_RESOURCE_GROUP
delete process.env.WEBSITE_OS
delete process.env.FUNCTIONS_EXTENSION_VERSION
delete process.env.FUNCTIONS_WORKER_RUNTIME
delete process.env.FUNCTIONS_WORKER_RUNTIME_VERSION
process.env.COMPUTERNAME = 'boaty_mcboatface'
process.env.DD_AZURE_APP_SERVICES = '1'
process.env.WEBSITE_SITE_NAME = 'website_name'
process.env.WEBSITE_OWNER_NAME = 'subscription_id+resource_group-regionwebspace'
process.env.WEBSITE_INSTANCE_ID = 'instance_id'
process.env.DD_AAS_DOTNET_EXTENSION_VERSION = '1.0'
const expected = {
extensionVersion: '1.0',
instanceID: 'instance_id',
instanceName: 'boaty_mcboatface',
operatingSystem: os.platform(),
resourceGroup: 'resource_group',
resourceID:
'/subscriptions/subscription_id/resourcegroups/resource_group/providers/microsoft.web/sites/website_name',
siteKind: 'app',
siteName: 'website_name',
siteType: 'app',
subscriptionID: 'subscription_id'
}
expect(getAzureAppMetadata()).to.deep.equal(expected)
})

it('provided completely with complete vars', () => {
process.env.COMPUTERNAME = 'boaty_mcboatface'
process.env.DD_AZURE_APP_SERVICES = '1'
process.env.WEBSITE_SITE_NAME = 'website_name'
process.env.WEBSITE_RESOURCE_GROUP = 'resource_group'
process.env.WEBSITE_OWNER_NAME = 'subscription_id+foo-regionwebspace'
process.env.WEBSITE_OS = 'windows'
process.env.WEBSITE_INSTANCE_ID = 'instance_id'
process.env.FUNCTIONS_EXTENSION_VERSION = '20'
process.env.FUNCTIONS_WORKER_RUNTIME = 'node'
process.env.FUNCTIONS_WORKER_RUNTIME_VERSION = '14'
process.env.DD_AAS_DOTNET_EXTENSION_VERSION = '1.0'
const expected = {
extensionVersion: '1.0',
functionRuntimeVersion: '20',
instanceID: 'instance_id',
instanceName: 'boaty_mcboatface',
operatingSystem: 'windows',
resourceGroup: 'resource_group',
resourceID:
'/subscriptions/subscription_id/resourcegroups/resource_group/providers/microsoft.web/sites/website_name',
runtime: 'node',
runtimeVersion: '14',
siteKind: 'functionapp',
siteName: 'website_name',
siteType: 'function',
subscriptionID: 'subscription_id'
}
expect(getAzureAppMetadata()).to.deep.equal(expected)
})

it('tags are correctly generated from vars', () => {
delete process.env.WEBSITE_RESOURCE_GROUP
delete process.env.WEBSITE_OS
delete process.env.FUNCTIONS_EXTENSION_VERSION
delete process.env.FUNCTIONS_WORKER_RUNTIME
delete process.env.FUNCTIONS_WORKER_RUNTIME_VERSION
process.env.COMPUTERNAME = 'boaty_mcboatface'
process.env.DD_AZURE_APP_SERVICES = '1'
process.env.WEBSITE_SITE_NAME = 'website_name'
process.env.WEBSITE_OWNER_NAME = 'subscription_id+resource_group-regionwebspace'
process.env.WEBSITE_INSTANCE_ID = 'instance_id'
process.env.DD_AAS_DOTNET_EXTENSION_VERSION = '1.0'
const expected = {
'aas.environment.extension_version': '1.0',
'aas.environment.instance_id': 'instance_id',
'aas.environment.instance_name': 'boaty_mcboatface',
'aas.environment.os': os.platform(),
'aas.resource.group': 'resource_group',
'aas.resource.id':
'/subscriptions/subscription_id/resourcegroups/resource_group/providers/microsoft.web/sites/website_name',
'aas.site.kind': 'app',
'aas.site.name': 'website_name',
'aas.site.type': 'app',
'aas.subscription.id': 'subscription_id'
}
expect(getAzureTagsFromMetadata(getAzureAppMetadata())).to.deep.equal(expected)
})
})

0 comments on commit d63df3e

Please sign in to comment.