diff --git a/detectors/node/opentelemetry-resource-detector-azure/README.md b/detectors/node/opentelemetry-resource-detector-azure/README.md index 4b7ff90938..cd0a85832d 100644 --- a/detectors/node/opentelemetry-resource-detector-azure/README.md +++ b/detectors/node/opentelemetry-resource-detector-azure/README.md @@ -67,8 +67,9 @@ This package implements Semantic Convention [Version 1.19.0](https://github.com/ | cloud.region | The Azure region where the Azure Function is hosted, e.g., "East US", "West Europe", etc. Value of Process Environment Variable `REGION_NAME`. | | faas.instance | The specific instance of the Azure App Service, useful in a scaled-out configuration. Value from Process Environment Variable `WEBSITE_INSTANCE_ID`. | | faas.max_memory | The amount of memory available to the Azure Function expressed in MiB. value from Process Environment Variable `WEBSITE_MEMORY_LIMIT_MB`. | -| faas.name | The name of the Azure App Service. Value from Process Environment Variable `WEBSITE_SITE_NAME`. | -| faas.version | The version of the Azure Function being executed, e.g., "~4". value from Process Environment Variable `FUNCTIONS_EXTENSION_VERSION`. | +| service.name | The name of the service the Azure Functions runs within. Value from Process Environment Variable `WEBSITE_SITE_NAME`. | +| cloud.resource_id | The Azure Resource Manager URI uniquely identifying the Azure Virtual Machine. It typically follows this format: /subscriptions/{subscriptionId}/resourceGroups/{groupName}/providers/Microsoft.Compute/virtualMachines/{vmName}. Value from resourceId key on /metadata/instance/compute request. | +| process.pid | The process ID collected from the running process. | ## Useful links diff --git a/detectors/node/opentelemetry-resource-detector-azure/src/detectors/AzureAppServiceDetector.ts b/detectors/node/opentelemetry-resource-detector-azure/src/detectors/AzureAppServiceDetector.ts index 7811ba4fa3..72b2fc8ac1 100644 --- a/detectors/node/opentelemetry-resource-detector-azure/src/detectors/AzureAppServiceDetector.ts +++ b/detectors/node/opentelemetry-resource-detector-azure/src/detectors/AzureAppServiceDetector.ts @@ -21,8 +21,6 @@ import { WEBSITE_HOME_STAMPNAME, WEBSITE_HOSTNAME, WEBSITE_INSTANCE_ID, - WEBSITE_OWNER_NAME, - WEBSITE_RESOURCE_GROUP, WEBSITE_SITE_NAME, WEBSITE_SLOT_NAME, CLOUD_RESOURCE_ID_RESOURCE_ATTRIBUTE, @@ -39,6 +37,7 @@ import { CLOUDPROVIDERVALUES_AZURE, CLOUDPLATFORMVALUES_AZURE_APP_SERVICE, } from '@opentelemetry/semantic-conventions'; +import { getAzureResourceUri } from '../utils'; const APP_SERVICE_ATTRIBUTE_ENV_VARS = { [SEMRESATTRS_CLOUD_REGION]: REGION_NAME, @@ -71,7 +70,7 @@ class AzureAppServiceDetector implements DetectorSync { [SEMRESATTRS_CLOUD_PLATFORM]: CLOUDPLATFORMVALUES_AZURE_APP_SERVICE, }; - const azureResourceUri = this.getAzureResourceUri(websiteSiteName); + const azureResourceUri = getAzureResourceUri(websiteSiteName); if (azureResourceUri) { attributes = { ...attributes, @@ -90,22 +89,6 @@ class AzureAppServiceDetector implements DetectorSync { } return new Resource(attributes); } - - private getAzureResourceUri(websiteSiteName: string): string | undefined { - const websiteResourceGroup = process.env[WEBSITE_RESOURCE_GROUP]; - const websiteOwnerName = process.env[WEBSITE_OWNER_NAME]; - - let subscriptionId = websiteOwnerName; - if (websiteOwnerName && websiteOwnerName.indexOf('+') !== -1) { - subscriptionId = websiteOwnerName.split('+')[0]; - } - - if (!subscriptionId && !websiteOwnerName) { - return undefined; - } - - return `/subscriptions/${subscriptionId}/resourceGroups/${websiteResourceGroup}/providers/Microsoft.Web/sites/${websiteSiteName}`; - } } export const azureAppServiceDetector = new AzureAppServiceDetector(); diff --git a/detectors/node/opentelemetry-resource-detector-azure/src/detectors/AzureFunctionsDetector.ts b/detectors/node/opentelemetry-resource-detector-azure/src/detectors/AzureFunctionsDetector.ts index 38b2f62f5e..74cd9e3054 100644 --- a/detectors/node/opentelemetry-resource-detector-azure/src/detectors/AzureFunctionsDetector.ts +++ b/detectors/node/opentelemetry-resource-detector-azure/src/detectors/AzureFunctionsDetector.ts @@ -17,8 +17,6 @@ import { DetectorSync, IResource, Resource } from '@opentelemetry/resources'; import { - SEMRESATTRS_FAAS_NAME, - SEMRESATTRS_FAAS_VERSION, SEMRESATTRS_FAAS_MAX_MEMORY, SEMRESATTRS_FAAS_INSTANCE, SEMRESATTRS_CLOUD_PROVIDER, @@ -26,6 +24,8 @@ import { SEMRESATTRS_CLOUD_REGION, CLOUDPROVIDERVALUES_AZURE, CLOUDPLATFORMVALUES_AZURE_FUNCTIONS, + SEMRESATTRS_SERVICE_NAME, + SEMRESATTRS_PROCESS_PID, } from '@opentelemetry/semantic-conventions'; import { WEBSITE_SITE_NAME, @@ -33,11 +33,12 @@ import { WEBSITE_INSTANCE_ID, FUNCTIONS_MEM_LIMIT, REGION_NAME, + CLOUD_RESOURCE_ID_RESOURCE_ATTRIBUTE, } from '../types'; +import { getAzureResourceUri } from '../utils'; const AZURE_FUNCTIONS_ATTRIBUTE_ENV_VARS = { - [SEMRESATTRS_FAAS_NAME]: WEBSITE_SITE_NAME, - [SEMRESATTRS_FAAS_VERSION]: FUNCTIONS_VERSION, + [SEMRESATTRS_SERVICE_NAME]: WEBSITE_SITE_NAME, [SEMRESATTRS_FAAS_INSTANCE]: WEBSITE_INSTANCE_ID, [SEMRESATTRS_FAAS_MAX_MEMORY]: FUNCTIONS_MEM_LIMIT, }; @@ -49,9 +50,14 @@ const AZURE_FUNCTIONS_ATTRIBUTE_ENV_VARS = { class AzureFunctionsDetector implements DetectorSync { detect(): IResource { let attributes = {}; - const functionName = process.env[WEBSITE_SITE_NAME]; + const serviceName = process.env[WEBSITE_SITE_NAME]; const functionVersion = process.env[FUNCTIONS_VERSION]; - if (functionName && functionVersion) { + + /** + * Checks that we are operating within an Azure Function using the function version since WEBSITE_SITE_NAME + * will exist in Azure App Service as well and detectors should be mutually exclusive. + */ + if (serviceName && functionVersion) { const functionInstance = process.env[WEBSITE_INSTANCE_ID]; const functionMemLimit = process.env[FUNCTIONS_MEM_LIMIT]; @@ -59,18 +65,13 @@ class AzureFunctionsDetector implements DetectorSync { [SEMRESATTRS_CLOUD_PROVIDER]: CLOUDPROVIDERVALUES_AZURE, [SEMRESATTRS_CLOUD_PLATFORM]: CLOUDPLATFORMVALUES_AZURE_FUNCTIONS, [SEMRESATTRS_CLOUD_REGION]: process.env[REGION_NAME], + [SEMRESATTRS_PROCESS_PID]: process.pid, }; - if (functionName) { - attributes = { - ...attributes, - [SEMRESATTRS_FAAS_NAME]: functionName, - }; - } - if (functionVersion) { + if (serviceName) { attributes = { ...attributes, - [SEMRESATTRS_FAAS_VERSION]: functionVersion, + [SEMRESATTRS_SERVICE_NAME]: serviceName, }; } if (functionInstance) { @@ -85,6 +86,13 @@ class AzureFunctionsDetector implements DetectorSync { [SEMRESATTRS_FAAS_MAX_MEMORY]: functionMemLimit, }; } + const azureResourceUri = getAzureResourceUri(serviceName); + if (azureResourceUri) { + attributes = { + ...attributes, + ...{ [CLOUD_RESOURCE_ID_RESOURCE_ATTRIBUTE]: azureResourceUri }, + }; + } for (const [key, value] of Object.entries( AZURE_FUNCTIONS_ATTRIBUTE_ENV_VARS diff --git a/detectors/node/opentelemetry-resource-detector-azure/src/utils.ts b/detectors/node/opentelemetry-resource-detector-azure/src/utils.ts new file mode 100644 index 0000000000..2b12431bdd --- /dev/null +++ b/detectors/node/opentelemetry-resource-detector-azure/src/utils.ts @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { WEBSITE_OWNER_NAME, WEBSITE_RESOURCE_GROUP } from './types'; + +export function getAzureResourceUri( + websiteSiteName: string +): string | undefined { + const websiteResourceGroup = process.env[WEBSITE_RESOURCE_GROUP]; + const websiteOwnerName = process.env[WEBSITE_OWNER_NAME]; + + let subscriptionId = websiteOwnerName; + if (websiteOwnerName && websiteOwnerName.indexOf('+') !== -1) { + subscriptionId = websiteOwnerName.split('+')[0]; + } + + if (!subscriptionId && !websiteOwnerName) { + return undefined; + } + + return `/subscriptions/${subscriptionId}/resourceGroups/${websiteResourceGroup}/providers/Microsoft.Web/sites/${websiteSiteName}`; +} diff --git a/detectors/node/opentelemetry-resource-detector-azure/test/detectors/AzureFunctionsDetector.test.ts b/detectors/node/opentelemetry-resource-detector-azure/test/detectors/AzureFunctionsDetector.test.ts index 0a334ab66f..1effe3a996 100644 --- a/detectors/node/opentelemetry-resource-detector-azure/test/detectors/AzureFunctionsDetector.test.ts +++ b/detectors/node/opentelemetry-resource-detector-azure/test/detectors/AzureFunctionsDetector.test.ts @@ -23,9 +23,9 @@ import { SEMRESATTRS_CLOUD_REGION, SEMRESATTRS_FAAS_INSTANCE, SEMRESATTRS_FAAS_MAX_MEMORY, - SEMRESATTRS_FAAS_NAME, - SEMRESATTRS_FAAS_VERSION, + SEMRESATTRS_PROCESS_PID, SEMRESATTRS_SERVICE_INSTANCE_ID, + SEMRESATTRS_SERVICE_NAME, } from '@opentelemetry/semantic-conventions'; import { detectResourcesSync } from '@opentelemetry/resources'; import { AZURE_APP_SERVICE_STAMP_RESOURCE_ATTRIBUTE } from '../../src/types'; @@ -41,18 +41,20 @@ describe('AzureFunctionsDetector', () => { }); it('should test functions values', () => { - process.env.WEBSITE_SITE_NAME = 'test-function'; + process.env.WEBSITE_SITE_NAME = 'test-service'; process.env.REGION_NAME = 'test-region'; process.env.WEBSITE_INSTANCE_ID = 'test-instance-id'; process.env.FUNCTIONS_EXTENSION_VERSION = '~4'; process.env.WEBSITE_MEMORY_LIMIT_MB = '1000'; + process.env.WEBSITE_OWNER_NAME = 'test-owner-name'; + process.env.WEBSITE_RESOURCE_GROUP = 'test-resource-group'; const resource = detectResourcesSync({ detectors: [azureFunctionsDetector, azureAppServiceDetector], }); assert.ok(resource); const attributes = resource.attributes; - assert.strictEqual(attributes[SEMRESATTRS_FAAS_NAME], 'test-function'); + assert.strictEqual(attributes[SEMRESATTRS_SERVICE_NAME], 'test-service'); assert.strictEqual(attributes[SEMRESATTRS_CLOUD_PROVIDER], 'azure'); assert.strictEqual( attributes[SEMRESATTRS_CLOUD_PLATFORM], @@ -64,14 +66,39 @@ describe('AzureFunctionsDetector', () => { 'test-instance-id' ); assert.strictEqual(attributes[SEMRESATTRS_FAAS_MAX_MEMORY], '1000'); - assert.strictEqual(attributes[SEMRESATTRS_FAAS_VERSION], '~4'); // Should not detect app service values assert.strictEqual(attributes[SEMRESATTRS_SERVICE_INSTANCE_ID], undefined); + assert.strictEqual(attributes[SEMRESATTRS_PROCESS_PID], process.pid); + assert.strictEqual( + attributes['cloud.resource_id'], + `/subscriptions/${process.env.WEBSITE_OWNER_NAME}/resourceGroups/${process.env.WEBSITE_RESOURCE_GROUP}/providers/Microsoft.Web/sites/${process.env.WEBSITE_SITE_NAME}` + ); assert.strictEqual( attributes[AZURE_APP_SERVICE_STAMP_RESOURCE_ATTRIBUTE], undefined ); }); + + it('should get the correct cloud resource id when WEBSITE_OWNER_NAME has a +', () => { + process.env.WEBSITE_SITE_NAME = 'test-service'; + process.env.REGION_NAME = 'test-region'; + process.env.WEBSITE_INSTANCE_ID = 'test-instance-id'; + process.env.FUNCTIONS_EXTENSION_VERSION = '~4'; + process.env.WEBSITE_MEMORY_LIMIT_MB = '1000'; + process.env.WEBSITE_OWNER_NAME = 'test-owner-name+test-subscription-id'; + process.env.WEBSITE_RESOURCE_GROUP = 'test-resource-group'; + + const expectedWebsiteOwnerName = 'test-owner-name'; + const resource = detectResourcesSync({ + detectors: [azureFunctionsDetector, azureAppServiceDetector], + }); + assert.ok(resource); + const attributes = resource.attributes; + assert.strictEqual( + attributes['cloud.resource_id'], + `/subscriptions/${expectedWebsiteOwnerName}/resourceGroups/${process.env.WEBSITE_RESOURCE_GROUP}/providers/Microsoft.Web/sites/${process.env.WEBSITE_SITE_NAME}` + ); + }); });