diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d0a605580..ebdb8ca8c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#3991](https://github.com/open-telemetry/opentelemetry-python/pull/3991)) - Add attributes field in `MeterProvider.get_meter` and `InstrumentationScope` ([#4015](https://github.com/open-telemetry/opentelemetry-python/pull/4015)) +- Add `ServiceInstanceIdResourceDetector` that adds the 'service.instance.id' resource attribute + ([#4061](https://github.com/open-telemetry/opentelemetry-python/pull/4061)) ## Version 1.25.0/0.46b0 (2024-05-30) diff --git a/opentelemetry-sdk/pyproject.toml b/opentelemetry-sdk/pyproject.toml index a606f47f1d..fbdd1cdec2 100644 --- a/opentelemetry-sdk/pyproject.toml +++ b/opentelemetry-sdk/pyproject.toml @@ -67,6 +67,7 @@ console = "opentelemetry.sdk.trace.export:ConsoleSpanExporter" [project.entry-points.opentelemetry_resource_detector] otel = "opentelemetry.sdk.resources:OTELResourceDetector" process = "opentelemetry.sdk.resources:ProcessResourceDetector" +serviceinstanceid = "opentelemetry.sdk.resources:ServiceInstanceIdResourceDetector" [project.urls] Homepage = "https://github.com/open-telemetry/opentelemetry-python/tree/main/opentelemetry-sdk" diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index 1fed32c0be..11af001ab7 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -61,6 +61,7 @@ import os import sys import typing +import uuid from json import dumps from os import environ from urllib import parse @@ -371,6 +372,17 @@ def detect(self) -> "Resource": return Resource(resource_info) +class ServiceInstanceIdResourceDetector(ResourceDetector): + # pylint: disable=no-self-use + _instance_id_cache = {} + + def detect(self) -> "Resource": + instance_id = self._instance_id_cache.setdefault( + os.getpid(), str(uuid.uuid4()) + ) + return Resource({SERVICE_INSTANCE_ID: instance_id}) + + def get_aggregated_resources( detectors: typing.List["ResourceDetector"], initial_resource: typing.Optional[Resource] = None, diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py index 8a42d0c6d0..3a81ab8ae7 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -41,6 +41,7 @@ PROCESS_RUNTIME_DESCRIPTION, PROCESS_RUNTIME_NAME, PROCESS_RUNTIME_VERSION, + SERVICE_INSTANCE_ID, SERVICE_NAME, TELEMETRY_SDK_LANGUAGE, TELEMETRY_SDK_NAME, @@ -49,6 +50,7 @@ ProcessResourceDetector, Resource, ResourceDetector, + ServiceInstanceIdResourceDetector, get_aggregated_resources, ) @@ -611,6 +613,27 @@ def test_process_detector(self): tuple(sys.argv), ) + def test_service_instance_id_detector(self): + resource = get_aggregated_resources( + [ServiceInstanceIdResourceDetector()], Resource({}) + ) + + self.assertIn(SERVICE_INSTANCE_ID, resource.attributes.keys()) + + # This throws if service instance id is not a valid UUID. + uuid.UUID(resource.attributes[SERVICE_INSTANCE_ID]) + + other_resource = get_aggregated_resources( + [ServiceInstanceIdResourceDetector()], Resource({}) + ) + + # The instance id should be stable across invocations of the detector + # in the same process. + self.assertEqual( + resource.attributes[SERVICE_INSTANCE_ID], + other_resource.attributes[SERVICE_INSTANCE_ID], + ) + def test_resource_detector_entry_points_default(self): resource = Resource({}).create() @@ -723,3 +746,15 @@ def test_resource_detector_entry_points_otel(self): ) self.assertIn(PROCESS_RUNTIME_VERSION, resource.attributes.keys()) self.assertEqual(resource.schema_url, "") + with patch.dict( + environ, + { + OTEL_EXPERIMENTAL_RESOURCE_DETECTORS: "serviceinstanceid", + }, + clear=True, + ): + resource = Resource({}).create() + self.assertIn(SERVICE_INSTANCE_ID, resource.attributes.keys()) + + # This throws if service instance id is not a valid UUID. + uuid.UUID(resource.attributes[SERVICE_INSTANCE_ID])