Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Azure App Services tags in profiler #4803

Merged
merged 3 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
})
})
Loading