Skip to content

Commit

Permalink
Implement functions resource detector (#2523)
Browse files Browse the repository at this point in the history
* Update .pylintrc

* fn

* Update CHANGELOG.md

* commments

* Add deployment.environment to functions detector

* Revert "Add deployment.environment to functions detector"

This reverts commit 5411759.

* Remove deployment.environment from readme

* Release 0.1.5

---------

Co-authored-by: jeremydvoss <[email protected]>
  • Loading branch information
lzchen and jeremydvoss authored May 16, 2024
1 parent 460fc33 commit f8758c6
Show file tree
Hide file tree
Showing 11 changed files with 274 additions and 29 deletions.
4 changes: 3 additions & 1 deletion resource/opentelemetry-resource-detector-azure/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased
## Version 0.1.5 (2024-05-16)

- Ignore vm detector if already in other rps
([#2456](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2456))
- Implement functions resource detector
([#2523](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2523))

## Version 0.1.4 (2024-04-05)

Expand Down
12 changes: 11 additions & 1 deletion resource/opentelemetry-resource-detector-azure/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,17 @@ The Azure App Service Resource Detector sets the following Resource Attributes:
* ``service.instance.id`` set to the value of the ``WEBSITE_INSTANCE_ID`` environment variable.
* ``azure.app.service.stamp`` set to the value of the ``WEBSITE_HOME_STAMPNAME`` environment variable.

The Azure VM Resource Detector sets the following Resource Attributes according to the response from the `Azure Metadata Service <https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service?tabs=windows>`_:
The Azure Functions Resource Detector sets the following Resource Attributes:
* ``service.name`` set to the value of the ``WEBSITE_SITE_NAME`` environment variable.
* ``process.id`` set to the process ID collected from the running process.
* ``cloud.platform`` set to ``azure_functions``.
* ``cloud.provider`` set to ``azure``.
* ``cloud.resource_id`` set using the ``WEBSITE_RESOURCE_GROUP``, ``WEBSITE_OWNER_NAME``, and ``WEBSITE_SITE_NAME`` environment variables.
* ``cloud.region`` set to the value of the ``REGION_NAME`` environment variable.
* ``faas.instance`` set to the value of the ``WEBSITE_INSTANCE_ID`` environment variable.
* ``faas.max_memory`` set to the value of the ``WEBSITE_MEMORY_LIMIT_MB`` environment variable.

The Azure VM Resource Detector sets the following Resource Attributes according to the response from the `Azure Metadata Service <https://learn.microsoft.com/azure/virtual-machines/instance-metadata-service?tabs=windows>`_:
* ``azure.vm.scaleset.name`` set to the value of the ``vmScaleSetName`` field.
* ``azure.vm.sku`` set to the value of the ``sku`` field.
* ``cloud.platform`` set to the value of the ``azure_vm``.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dependencies = [

[project.entry-points.opentelemetry_resource_detector]
azure_app_service = "opentelemetry.resource.detector.azure.app_service:AzureAppServiceResourceDetector"
azure_functions = "opentelemetry.resource.detector.azure.functions:AzureFunctionsResourceDetector"
azure_vm = "opentelemetry.resource.detector.azure.vm:AzureVMResourceDetector"

[project.urls]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
# pylint: disable=import-error

from .app_service import AzureAppServiceResourceDetector
from .functions import AzureFunctionsResourceDetector
from .version import __version__
from .vm import AzureVMResourceDetector

__all__ = [
"AzureAppServiceResourceDetector",
"AzureFunctionsResourceDetector",
"AzureVMResourceDetector",
"__version__",
]
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@
# Functions

_FUNCTIONS_WORKER_RUNTIME = "FUNCTIONS_WORKER_RUNTIME"
_WEBSITE_MEMORY_LIMIT_MB = "WEBSITE_MEMORY_LIMIT_MB"

_FUNCTIONS_ATTRIBUTE_ENV_VARS = {
ResourceAttributes.FAAS_INSTANCE: _WEBSITE_INSTANCE_ID,
ResourceAttributes.FAAS_MAX_MEMORY: _WEBSITE_MEMORY_LIMIT_MB,
}

# Vm

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,44 @@
# 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 os
from os import environ
from typing import Optional

from ._constants import (
_AKS_ARM_NAMESPACE_ID,
_FUNCTIONS_WORKER_RUNTIME,
_WEBSITE_OWNER_NAME,
_WEBSITE_RESOURCE_GROUP,
_WEBSITE_SITE_NAME,
)


def _is_on_aks() -> bool:
return os.environ.get(_AKS_ARM_NAMESPACE_ID) is not None
return environ.get(_AKS_ARM_NAMESPACE_ID) is not None


def _is_on_app_service() -> bool:
return os.environ.get(_WEBSITE_SITE_NAME) is not None
return environ.get(_WEBSITE_SITE_NAME) is not None


def _is_on_functions() -> bool:
return os.environ.get(_FUNCTIONS_WORKER_RUNTIME) is not None
return environ.get(_FUNCTIONS_WORKER_RUNTIME) is not None


def _can_ignore_vm_detect() -> bool:
return _is_on_aks() or _is_on_app_service() or _is_on_functions()


def _get_azure_resource_uri() -> Optional[str]:
website_site_name = environ.get(_WEBSITE_SITE_NAME)
website_resource_group = environ.get(_WEBSITE_RESOURCE_GROUP)
website_owner_name = environ.get(_WEBSITE_OWNER_NAME)

subscription_id = website_owner_name
if website_owner_name and "+" in website_owner_name:
subscription_id = website_owner_name[0 : website_owner_name.index("+")]

if not (website_site_name and website_resource_group and subscription_id):
return None

return f"/subscriptions/{subscription_id}/resourceGroups/{website_resource_group}/providers/Microsoft.Web/sites/{website_site_name}"
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Optional
from os import environ

from opentelemetry.sdk.resources import Resource, ResourceDetector
Expand All @@ -20,29 +21,32 @@
CloudProviderValues,
ResourceAttributes,
)
from opentelemetry.resource.detector.azure._utils import _get_azure_resource_uri

from ._constants import (
_APP_SERVICE_ATTRIBUTE_ENV_VARS,
_WEBSITE_OWNER_NAME,
_WEBSITE_RESOURCE_GROUP,
_WEBSITE_SITE_NAME,
)

from opentelemetry.resource.detector.azure._utils import _is_on_functions


class AzureAppServiceResourceDetector(ResourceDetector):
def detect(self) -> Resource:
attributes = {}
website_site_name = environ.get(_WEBSITE_SITE_NAME)
if website_site_name:
attributes[ResourceAttributes.SERVICE_NAME] = website_site_name
# Functions resource detector takes priority with `service.name` and `cloud.platform`
if not _is_on_functions():
attributes[ResourceAttributes.SERVICE_NAME] = website_site_name
attributes[ResourceAttributes.CLOUD_PLATFORM] = (
CloudPlatformValues.AZURE_APP_SERVICE.value
)
attributes[ResourceAttributes.CLOUD_PROVIDER] = (
CloudProviderValues.AZURE.value
)
attributes[ResourceAttributes.CLOUD_PLATFORM] = (
CloudPlatformValues.AZURE_APP_SERVICE.value
)

azure_resource_uri = _get_azure_resource_uri(website_site_name)
azure_resource_uri = _get_azure_resource_uri()
if azure_resource_uri:
attributes[ResourceAttributes.CLOUD_RESOURCE_ID] = (
azure_resource_uri
Expand All @@ -53,17 +57,3 @@ def detect(self) -> Resource:
attributes[key] = value

return Resource(attributes)


def _get_azure_resource_uri(website_site_name):
website_resource_group = environ.get(_WEBSITE_RESOURCE_GROUP)
website_owner_name = environ.get(_WEBSITE_OWNER_NAME)

subscription_id = website_owner_name
if website_owner_name and "+" in website_owner_name:
subscription_id = website_owner_name[0 : website_owner_name.index("+")]

if not (website_resource_group and subscription_id):
return None

return f"/subscriptions/{subscription_id}/resourceGroups/{website_resource_group}/providers/Microsoft.Web/sites/{website_site_name}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# 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
#
# http://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.

from os import environ, getpid

from opentelemetry.sdk.resources import Resource, ResourceDetector
from opentelemetry.semconv.resource import (
CloudPlatformValues,
CloudProviderValues,
ResourceAttributes,
)

from ._constants import (
_FUNCTIONS_ATTRIBUTE_ENV_VARS,
_REGION_NAME,
_WEBSITE_SITE_NAME,
)
from opentelemetry.resource.detector.azure._utils import (
_get_azure_resource_uri,
_is_on_functions,
)


class AzureFunctionsResourceDetector(ResourceDetector):
def detect(self) -> Resource:
attributes = {}
if _is_on_functions():
website_site_name = environ.get(_WEBSITE_SITE_NAME)
if website_site_name:
attributes[ResourceAttributes.SERVICE_NAME] = website_site_name
attributes[ResourceAttributes.PROCESS_PID] = getpid()
attributes[ResourceAttributes.CLOUD_PROVIDER] = (
CloudProviderValues.AZURE.value
)
attributes[ResourceAttributes.CLOUD_PLATFORM] = (
CloudPlatformValues.AZURE_FUNCTIONS.value
)
cloud_region = environ.get(_REGION_NAME)
if cloud_region:
attributes[ResourceAttributes.CLOUD_REGION] = cloud_region
azure_resource_uri = _get_azure_resource_uri()
if azure_resource_uri:
attributes[ResourceAttributes.CLOUD_RESOURCE_ID] = (
azure_resource_uri
)
for key, env_var in _FUNCTIONS_ATTRIBUTE_ENV_VARS.items():
value = environ.get(env_var)
if value:
if key == ResourceAttributes.FAAS_MAX_MEMORY:
try:
value = int(value)
except ValueError:
continue
attributes[key] = value

return Resource(attributes)

Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.

__version__ = "0.1.4"
__version__ = "0.1.5"
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,45 @@ def test_on_app_service(self):
self.assertEqual(
attributes["azure.app.service.stamp"], TEST_WEBSITE_HOME_STAMPNAME
)

@patch.dict(
"os.environ",
{
"FUNCTIONS_WORKER_RUNTIME": "1",
"WEBSITE_SITE_NAME": TEST_WEBSITE_SITE_NAME,
"REGION_NAME": TEST_REGION_NAME,
"WEBSITE_SLOT_NAME": TEST_WEBSITE_SLOT_NAME,
"WEBSITE_HOSTNAME": TEST_WEBSITE_HOSTNAME,
"WEBSITE_INSTANCE_ID": TEST_WEBSITE_INSTANCE_ID,
"WEBSITE_HOME_STAMPNAME": TEST_WEBSITE_HOME_STAMPNAME,
"WEBSITE_RESOURCE_GROUP": TEST_WEBSITE_RESOURCE_GROUP,
"WEBSITE_OWNER_NAME": TEST_WEBSITE_OWNER_NAME,
},
clear=True,
)
def test_on_app_service_with_functions(self):
resource = AzureAppServiceResourceDetector().detect()
attributes = resource.attributes
self.assertIsNone(attributes.get("service.name"))
self.assertEqual(attributes["cloud.provider"], "azure")
self.assertIsNone(attributes.get("cloud.platform"))

self.assertEqual(
attributes["cloud.resource_id"],
f"/subscriptions/{TEST_WEBSITE_OWNER_NAME}/resourceGroups/{TEST_WEBSITE_RESOURCE_GROUP}/providers/Microsoft.Web/sites/{TEST_WEBSITE_SITE_NAME}",
)

self.assertEqual(attributes["cloud.region"], TEST_REGION_NAME)
self.assertEqual(
attributes["deployment.environment"], TEST_WEBSITE_SLOT_NAME
)
self.assertEqual(attributes["host.id"], TEST_WEBSITE_HOSTNAME)
self.assertEqual(
attributes["service.instance.id"], TEST_WEBSITE_INSTANCE_ID
)
self.assertEqual(
attributes["azure.app.service.stamp"], TEST_WEBSITE_HOME_STAMPNAME
)

@patch.dict(
"os.environ",
Expand Down
Loading

0 comments on commit f8758c6

Please sign in to comment.