Skip to content

Commit

Permalink
fix: Update Azure Functions Detector Collected Attributes (#2233)
Browse files Browse the repository at this point in the history
* Update azure functions detector collected attributes.

* Clean up comments.

* Fix lint.

* Add tests for cloud resource id parsing.

* Add comment to explain usage of the functionVersion env var.

---------

Co-authored-by: Hector Hernandez <[email protected]>
  • Loading branch information
JacksonWeber and hectorhdzg authored May 31, 2024
1 parent 0078d0c commit 7272ca8
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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();
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,28 @@
import { DetectorSync, IResource, Resource } from '@opentelemetry/resources';

import {
SEMRESATTRS_FAAS_NAME,
SEMRESATTRS_FAAS_VERSION,
SEMRESATTRS_FAAS_MAX_MEMORY,
SEMRESATTRS_FAAS_INSTANCE,
SEMRESATTRS_CLOUD_PROVIDER,
SEMRESATTRS_CLOUD_PLATFORM,
SEMRESATTRS_CLOUD_REGION,
CLOUDPROVIDERVALUES_AZURE,
CLOUDPLATFORMVALUES_AZURE_FUNCTIONS,
SEMRESATTRS_SERVICE_NAME,
SEMRESATTRS_PROCESS_PID,
} from '@opentelemetry/semantic-conventions';
import {
WEBSITE_SITE_NAME,
FUNCTIONS_VERSION,
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,
};
Expand All @@ -49,28 +50,28 @@ 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];

attributes = {
[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) {
Expand All @@ -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
Expand Down
35 changes: 35 additions & 0 deletions detectors/node/opentelemetry-resource-detector-azure/src/utils.ts
Original file line number Diff line number Diff line change
@@ -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}`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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],
Expand All @@ -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}`
);
});
});

0 comments on commit 7272ca8

Please sign in to comment.