Skip to content

Commit

Permalink
App Service Resource Detector
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremydvoss committed Aug 1, 2023
1 parent 194a356 commit 27f5b9d
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 6 deletions.
21 changes: 21 additions & 0 deletions sdk/monitor/azure-monitor-opentelemetry-exporter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,25 @@ from azure.monitor.opentelemetry.exporter import AzureMonitorTraceExporter
exporter = AzureMonitorTraceExporter()
```

#### Resource Detectors

OpenTelemetry Python has an experimental feature whereby [Resource Detectors][resource_detector_docs] can be injected to Resource Attributes. This package includes a resource detector for Azure App Service. This detector fills out the following Resource Attributes:
* `service.name`
* `cloud.provider`
* `cloud.platform`
* `cloud.resource_id`
* `cloud.region`
* `deployment.environment`
* `host.id`
* `service.instance.id`
* `azure.app.service.stamp`

For more information, see the [Semantic Conventions for Cloud Resource Attributes][cloud_sem_conv]

To enable the App Service Resource Detector, add `azure_monitor_opentelemetry_app_service_resource_detector` to the `OTEL_EXPERIMENTAL_RESOURCE_DETECTORS` environment variable:

`export OTEL_EXPERIMENTAL_RESOURCE_DETECTORS=azure_monitor_opentelemetry_app_service_resource_detector`

## Key concepts

Some of the key concepts for the Azure monitor exporter include:
Expand Down Expand Up @@ -665,6 +684,7 @@ contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additio
<!-- LINKS -->
[aad_for_ai_docs]: https://learn.microsoft.com/azure/azure-monitor/app/azure-ad-authentication?tabs=python
[api_docs]: https://azuresdkdocs.blob.core.windows.net/$web/python/azure-opentelemetry-exporter-azuremonitor/1.0.0b2/index.html
[cloud_sem_conv]: https://opentelemetry.io/docs/specs/otel/resource/semantic_conventions/cloud/
[exporter_samples]: https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/monitor/azure-monitor-opentelemetry-exporter/samples
[product_docs]: https://docs.microsoft.com/azure/azure-monitor/overview
[azure_sub]: https://azure.microsoft.com/free/
Expand All @@ -691,6 +711,7 @@ contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additio
[metric_reader]:https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#metricreader
[metric_reference]: https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/metrics/_exporter.py
[ot_metrics_sdk]: https://opentelemetry-python.readthedocs.io/en/stable/sdk/metrics.html
[resource_detector_docs]: https://opentelemetry.io/docs/specs/otel/resource/sdk/#detecting-resource-information-from-the-environment
[trace_concept]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/overview.md#tracing-signal
[span]: https://opentelemetry-python.readthedocs.io/en/stable/api/trace.html?highlight=TracerProvider#opentelemetry.trace.Span
[tracer]: https://opentelemetry-python.readthedocs.io/en/stable/api/trace.html?highlight=TracerProvider#opentelemetry.trace.Tracer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,21 @@

_SAMPLE_RATE_KEY = "_MS.sampleRate"

# Resource detectors

# TODO: Remove once this resource attribute is no longer missing from SDK
_CLOUD_RESOURCE_ID_RESOURCE_ATTRIBUTE = "cloud.resource_id"
_AZURE_APP_SERVICE_STAMP_RESOURCE_ATTRIBUTE = "azure.app.service.stamp"

# App Service environment variables

_WEBSITE_SITE_NAME = "WEBSITE_SITE_NAME"
_REGION_NAME = "REGION_NAME"
_WEBSITE_SLOT_NAME = "WEBSITE_SLOT_NAME"
_WEBSITE_HOSTNAME = "WEBSITE_HOSTNAME"
_WEBSITE_INSTANCE_ID = "WEBSITE_INSTANCE_ID"
_WEBSITE_HOME_STAMPNAME = "WEBSITE_HOME_STAMPNAME"
_WEBSITE_RESOURCE_GROUP = "WEBSITE_RESOURCE_GROUP"
_WEBSITE_OWNER_NAME = "WEBSITE_OWNER_NAME"

# cSpell:disable
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@

from azure.monitor.opentelemetry.exporter._generated.models import TelemetryItem
from azure.monitor.opentelemetry.exporter._version import VERSION as ext_version
from azure.monitor.opentelemetry.exporter._constants import _INSTRUMENTATIONS_BIT_MAP
from azure.monitor.opentelemetry.exporter._constants import (
_INSTRUMENTATIONS_BIT_MAP,
_WEBSITE_SITE_NAME,
)


# Workaround for missing version file
opentelemetry_version = version("opentelemetry-sdk")


def _get_sdk_version_prefix():
is_on_app_service = "WEBSITE_SITE_NAME" in environ
is_on_app_service = _WEBSITE_SITE_NAME in environ
is_attach_enabled = isdir("/agents/python/")
sdk_version_prefix = ''
if is_on_app_service and is_attach_enabled:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
from os import environ

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

from azure.monitor.opentelemetry.exporter._constants import (
_CLOUD_RESOURCE_ID_RESOURCE_ATTRIBUTE,
_REGION_NAME,
_WEBSITE_SITE_NAME,
_WEBSITE_HOME_STAMPNAME,
_WEBSITE_HOSTNAME,
_WEBSITE_INSTANCE_ID,
_WEBSITE_OWNER_NAME,
_WEBSITE_RESOURCE_GROUP,
_WEBSITE_SLOT_NAME,
_AZURE_APP_SERVICE_STAMP_RESOURCE_ATTRIBUTE,
)


_APP_SERVICE_ATTRIBUTE_ENV_VARS = {
ResourceAttributes.CLOUD_REGION: _REGION_NAME,
ResourceAttributes.DEPLOYMENT_ENVIRONMENT: _WEBSITE_SLOT_NAME,
ResourceAttributes.HOST_ID: _WEBSITE_HOSTNAME,
ResourceAttributes.SERVICE_INSTANCE_ID: _WEBSITE_INSTANCE_ID,
_AZURE_APP_SERVICE_STAMP_RESOURCE_ATTRIBUTE: _WEBSITE_HOME_STAMPNAME,
}

class AzureAppServiceResourceDetector(ResourceDetector):
# pylint: disable=no-self-use
def detect(self) -> "Resource":
attributes = {}
website_site_name = environ.get(_WEBSITE_SITE_NAME)
if website_site_name:
print(_WEBSITE_SITE_NAME)
attributes[ResourceAttributes.SERVICE_NAME] = website_site_name
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)
if azure_resource_uri:
attributes[_CLOUD_RESOURCE_ID_RESOURCE_ATTRIBUTE] = azure_resource_uri
for attribute_key in _APP_SERVICE_ATTRIBUTE_ENV_VARS:
attribute_value = environ.get(_APP_SERVICE_ATTRIBUTE_ENV_VARS[attribute_key])
if attribute_value:
attributes[attribute_key] = attribute_value

print(attributes)
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
Expand Up @@ -23,6 +23,16 @@
_REQ_RETRY_NAME,
_REQ_SUCCESS_NAME,
_REQ_THROTTLE_NAME,
_CLOUD_RESOURCE_ID_RESOURCE_ATTRIBUTE,
_WEBSITE_SITE_NAME,
_REGION_NAME,
_WEBSITE_SITE_NAME,
_WEBSITE_HOME_STAMPNAME,
_WEBSITE_HOSTNAME,
_WEBSITE_INSTANCE_ID,
_WEBSITE_OWNER_NAME,
_WEBSITE_RESOURCE_GROUP,
_WEBSITE_SLOT_NAME,
)
from azure.monitor.opentelemetry.exporter.statsbeat._state import (
_REQUESTS_MAP_LOCK,
Expand Down Expand Up @@ -142,17 +152,17 @@ def _get_attach_metric(self, options: CallbackOptions) -> Iterable[Observation]:
rpId = ''
os_type = platform.system()
# rp, rpId
if os.environ.get("WEBSITE_SITE_NAME") is not None:
if os.environ.get(_WEBSITE_SITE_NAME) is not None:
# Web apps
rp = _RP_NAMES[0]
rpId = '{}/{}'.format(
os.environ.get("WEBSITE_SITE_NAME"),
os.environ.get("WEBSITE_HOME_STAMPNAME", '')
os.environ.get(_WEBSITE_SITE_NAME),
os.environ.get(_WEBSITE_HOME_STAMPNAME, '')
)
elif os.environ.get("FUNCTIONS_WORKER_RUNTIME") is not None:
# Function apps
rp = _RP_NAMES[1]
rpId = os.environ.get("WEBSITE_HOSTNAME")
rpId = os.environ.get(_WEBSITE_HOSTNAME)
elif self._vm_retry and self._get_azure_compute_metadata():
# VM
rp = _RP_NAMES[2]
Expand Down
3 changes: 3 additions & 0 deletions sdk/monitor/azure-monitor-opentelemetry-exporter/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@
],
"opentelemetry_traces_sampler": [
"azure_monitor_opentelemetry_sampler = azure.monitor.opentelemetry.exporter.export.trace._sampling:azure_monitor_opentelemetry_sampler_factory"
],
"opentelemetry_resource_detector": [
"azure_monitor_opentelemetry_app_service_resource_detector = azure.monitor.opentelemetry.exporter.resource.app_service_resource_detector:AzureAppServiceResourceDetector",
]
}
)

0 comments on commit 27f5b9d

Please sign in to comment.