From acde88b9793080cab4739f5e83c9c1f275b00e06 Mon Sep 17 00:00:00 2001 From: Johannes Tax Date: Tue, 16 Jul 2024 15:05:08 +0200 Subject: [PATCH 1/8] Add a resource detector for populating `service.instance.id` --- CHANGELOG.md | 2 ++ opentelemetry-sdk/pyproject.toml | 1 + .../opentelemetry/sdk/resources/__init__.py | 7 +++++ .../tests/resources/test_resources.py | 27 +++++++++++++++++++ 4 files changed, 37 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44938228ca3..12856c2c73c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,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)) +- Added a `opentelemetry.sdk.resources.ServiceInstanceIdResourceDetector` that + adds the 'service.instance.id' resource attribute ## Version 1.25.0/0.46b0 (2024-05-30) diff --git a/opentelemetry-sdk/pyproject.toml b/opentelemetry-sdk/pyproject.toml index a606f47f1d6..fbdd1cdec2e 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 1fed32c0be5..76095ac8f3f 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -64,6 +64,7 @@ from json import dumps from os import environ from urllib import parse +import uuid from opentelemetry.attributes import BoundedAttributes from opentelemetry.sdk.environment_variables import ( @@ -371,6 +372,12 @@ def detect(self) -> "Resource": return Resource(resource_info) +class ServiceInstanceIdResourceDetector(ResourceDetector): + # pylint: disable=no-self-use + def detect(self) -> "Resource": + return Resource({SERVICE_INSTANCE_ID: str(uuid.uuid4())}) + + 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 8a42d0c6d0f..df5679e790c 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -42,6 +42,7 @@ PROCESS_RUNTIME_NAME, PROCESS_RUNTIME_VERSION, SERVICE_NAME, + SERVICE_INSTANCE_ID, TELEMETRY_SDK_LANGUAGE, TELEMETRY_SDK_NAME, TELEMETRY_SDK_VERSION, @@ -49,6 +50,7 @@ ProcessResourceDetector, Resource, ResourceDetector, + ServiceInstanceIdResourceDetector, get_aggregated_resources, ) @@ -611,6 +613,19 @@ def test_process_detector(self): tuple(sys.argv), ) + def test_service_instance_id_detector(self): + initial_resource = Resource({}) + aggregated_resource = get_aggregated_resources( + [ServiceInstanceIdResourceDetector()], initial_resource + ) + + self.assertIn( + SERVICE_INSTANCE_ID, + aggregated_resource.attributes.keys()) + + # This throws if service instance id is not a valid UUID. + uuid.UUID(aggregated_resource.attributes[SERVICE_INSTANCE_ID]) + def test_resource_detector_entry_points_default(self): resource = Resource({}).create() @@ -723,3 +738,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(aggregated_resource.attributes[SERVICE_INSTANCE_ID]) From 79daf5c426c2c264402763499ecf1373274a60ec Mon Sep 17 00:00:00 2001 From: Johannes Tax Date: Tue, 16 Jul 2024 15:25:15 +0200 Subject: [PATCH 2/8] Fix linter issue --- opentelemetry-sdk/tests/resources/test_resources.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py index df5679e790c..adbb4051ca6 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -620,8 +620,7 @@ def test_service_instance_id_detector(self): ) self.assertIn( - SERVICE_INSTANCE_ID, - aggregated_resource.attributes.keys()) + SERVICE_INSTANCE_ID, aggregated_resource.attributes.keys()) # This throws if service instance id is not a valid UUID. uuid.UUID(aggregated_resource.attributes[SERVICE_INSTANCE_ID]) From 0348f951d8ed20f623f90b93c20a88963b2d0724 Mon Sep 17 00:00:00 2001 From: Johannes Tax Date: Tue, 16 Jul 2024 15:28:56 +0200 Subject: [PATCH 3/8] Linter fixes --- opentelemetry-sdk/tests/resources/test_resources.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py index adbb4051ca6..99809ebd39f 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -620,7 +620,8 @@ def test_service_instance_id_detector(self): ) self.assertIn( - SERVICE_INSTANCE_ID, aggregated_resource.attributes.keys()) + SERVICE_INSTANCE_ID, aggregated_resource.attributes.keys() + ) # This throws if service instance id is not a valid UUID. uuid.UUID(aggregated_resource.attributes[SERVICE_INSTANCE_ID]) From b9d364fb56a594c8a91b4ae4a1a3cc81d9bf0cae Mon Sep 17 00:00:00 2001 From: Johannes Tax Date: Tue, 16 Jul 2024 15:37:49 +0200 Subject: [PATCH 4/8] Linter fixes --- opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py | 2 +- opentelemetry-sdk/tests/resources/test_resources.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index 76095ac8f3f..a2db0866556 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -61,10 +61,10 @@ import os import sys import typing +import uuid from json import dumps from os import environ from urllib import parse -import uuid from opentelemetry.attributes import BoundedAttributes from opentelemetry.sdk.environment_variables import ( diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py index 99809ebd39f..2b666d93981 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -41,8 +41,8 @@ PROCESS_RUNTIME_DESCRIPTION, PROCESS_RUNTIME_NAME, PROCESS_RUNTIME_VERSION, - SERVICE_NAME, SERVICE_INSTANCE_ID, + SERVICE_NAME, TELEMETRY_SDK_LANGUAGE, TELEMETRY_SDK_NAME, TELEMETRY_SDK_VERSION, From 10e2a44771aab066961e67a48ef89f0e7d1dd85c Mon Sep 17 00:00:00 2001 From: Johannes Tax Date: Tue, 16 Jul 2024 15:47:31 +0200 Subject: [PATCH 5/8] Fix unit test --- opentelemetry-sdk/tests/resources/test_resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py index 2b666d93981..cf33290bd03 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -749,4 +749,4 @@ def test_resource_detector_entry_points_otel(self): self.assertIn(SERVICE_INSTANCE_ID, resource.attributes.keys()) # This throws if service instance id is not a valid UUID. - uuid.UUID(aggregated_resource.attributes[SERVICE_INSTANCE_ID]) + uuid.UUID(resource.attributes[SERVICE_INSTANCE_ID]) From 463a3775c7c0adaa3f59e9358cac0d23c93a6d6a Mon Sep 17 00:00:00 2001 From: Johannes Tax Date: Wed, 17 Jul 2024 10:08:47 +0200 Subject: [PATCH 6/8] Ensure a stable instance id per process --- .../opentelemetry/sdk/resources/__init__.py | 4 +++- .../tests/resources/test_resources.py | 22 +++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index a2db0866556..05f28c666ed 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -374,8 +374,10 @@ def detect(self) -> "Resource": class ServiceInstanceIdResourceDetector(ResourceDetector): # pylint: disable=no-self-use + _instance_id = str(uuid.uuid4()) + def detect(self) -> "Resource": - return Resource({SERVICE_INSTANCE_ID: str(uuid.uuid4())}) + return Resource({SERVICE_INSTANCE_ID: self._instance_id}) def get_aggregated_resources( diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py index cf33290bd03..3a81ab8ae75 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -614,17 +614,25 @@ def test_process_detector(self): ) def test_service_instance_id_detector(self): - initial_resource = Resource({}) - aggregated_resource = get_aggregated_resources( - [ServiceInstanceIdResourceDetector()], initial_resource + resource = get_aggregated_resources( + [ServiceInstanceIdResourceDetector()], Resource({}) ) - self.assertIn( - SERVICE_INSTANCE_ID, aggregated_resource.attributes.keys() - ) + self.assertIn(SERVICE_INSTANCE_ID, resource.attributes.keys()) # This throws if service instance id is not a valid UUID. - uuid.UUID(aggregated_resource.attributes[SERVICE_INSTANCE_ID]) + 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() From 9088541dbbccdac68fb732763bfd7778674658da Mon Sep 17 00:00:00 2001 From: Johannes Tax Date: Wed, 17 Jul 2024 11:05:30 +0200 Subject: [PATCH 7/8] PR comments --- .../src/opentelemetry/sdk/resources/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index 05f28c666ed..11af001ab7d 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -374,10 +374,13 @@ def detect(self) -> "Resource": class ServiceInstanceIdResourceDetector(ResourceDetector): # pylint: disable=no-self-use - _instance_id = str(uuid.uuid4()) + _instance_id_cache = {} def detect(self) -> "Resource": - return Resource({SERVICE_INSTANCE_ID: self._instance_id}) + instance_id = self._instance_id_cache.setdefault( + os.getpid(), str(uuid.uuid4()) + ) + return Resource({SERVICE_INSTANCE_ID: instance_id}) def get_aggregated_resources( From 829be01618c476c196037f92b2dabba63f42a9b9 Mon Sep 17 00:00:00 2001 From: Johannes Tax Date: Wed, 17 Jul 2024 14:37:48 +0200 Subject: [PATCH 8/8] Update CHANGELOG.md Co-authored-by: Riccardo Magliocchetti --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12856c2c73c..9b48011b3a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,8 +46,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)) -- Added a `opentelemetry.sdk.resources.ServiceInstanceIdResourceDetector` that - adds the 'service.instance.id' resource attribute +- 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)