From f7040e41a0f5be23cd1acb80f2540d97d245d7e5 Mon Sep 17 00:00:00 2001 From: Zirak Date: Sun, 23 Jun 2024 09:08:08 +0300 Subject: [PATCH 01/13] sdk: Implement basic os resource detector Based on OS resource semantics: https://opentelemetry.io/docs/specs/semconv/resource/os/ Currently implements `os.type` and `os.version`, attempting to be in line with what's reported by other runtimes (like java and node). --- .../opentelemetry/sdk/resources/__init__.py | 70 ++++++++++++++++++- .../tests/resources/test_resources.py | 34 +++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index 0cdd70d97c..97d3bc0f44 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -59,6 +59,7 @@ import concurrent.futures import logging import os +import platform import sys import typing from json import dumps @@ -119,8 +120,9 @@ KUBERNETES_JOB_NAME = ResourceAttributes.K8S_JOB_NAME KUBERNETES_CRON_JOB_UID = ResourceAttributes.K8S_CRONJOB_UID KUBERNETES_CRON_JOB_NAME = ResourceAttributes.K8S_CRONJOB_NAME -OS_TYPE = ResourceAttributes.OS_TYPE OS_DESCRIPTION = ResourceAttributes.OS_DESCRIPTION +OS_TYPE = ResourceAttributes.OS_TYPE +OS_VERSION = ResourceAttributes.OS_VERSION PROCESS_PID = ResourceAttributes.PROCESS_PID PROCESS_PARENT_PID = ResourceAttributes.PROCESS_PARENT_PID PROCESS_EXECUTABLE_NAME = ResourceAttributes.PROCESS_EXECUTABLE_NAME @@ -371,6 +373,72 @@ def detect(self) -> "Resource": return Resource(resource_info) +class OsResourceDetector(ResourceDetector): + """Detect os resources based on `Operating System conventions "Resource": + """Returns a resource with with `os.type` and `os.version`. Example of + return values for `platform` calls in different systems: + + Linux: + >>> platform.system() + 'Linux' + >>> platform.release() + '6.5.0-35-generic' + >>> platform.version() + '#35~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue May 7 09:00:52 UTC 2' + + MacOS: + >>> platform.system() + 'Darwin' + >>> platform.release() + '23.0.0' + >>> platform.version() + 'Darwin Kernel Version 23.0.0: Fri Sep 15 14:42:57 PDT 2023; root:xnu-10002.1.13~1/RELEASE_ARM64_T8112' + + Windows: + >>> platform.system() + 'Windows' + >>> platform.release() + '2022Server' + >>> platform.version() + '10.0.20348' + + FreeBSD: + >>> platform.system() + 'FreeBSD' + >>> platform.release() + '14.1-RELEASE' + >>> platform.version() + 'FreeBSD 14.1-RELEASE releng/14.1-n267679-10e31f0946d8 GENERIC' + + Solaris: + >>> platform.system() + 'SunOS' + >>> platform.release() + '5.11' + >>> platform.version() + '11.4.0.15.0' + """ + + os_type = platform.system().lower() + os_version = platform.release() + + # See docstring + if os_type == "windows": + os_version = platform.version() + # Align SunOS with conventions + if os_type == "sunos": + os_type = "solaris" + + return Resource( + { + OS_TYPE: os_type, + OS_VERSION: os_version, + } + ) + + 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 a0c6159010..961b512af6 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -28,6 +28,8 @@ _DEFAULT_RESOURCE, _EMPTY_RESOURCE, _OPENTELEMETRY_SDK_VERSION, + OS_TYPE, + OS_VERSION, OTEL_RESOURCE_ATTRIBUTES, OTEL_SERVICE_NAME, PROCESS_COMMAND, @@ -45,6 +47,7 @@ TELEMETRY_SDK_LANGUAGE, TELEMETRY_SDK_NAME, TELEMETRY_SDK_VERSION, + OsResourceDetector, OTELResourceDetector, ProcessResourceDetector, Resource, @@ -723,3 +726,34 @@ def test_resource_detector_entry_points_otel(self): ) self.assertIn(PROCESS_RUNTIME_VERSION, resource.attributes.keys()) self.assertEqual(resource.schema_url, "") + + @patch("platform.system", lambda: "Linux") + @patch("platform.release", lambda: "666.5.0-35-generic") + def test_os_detector_linux(self): + resource = get_aggregated_resources( + [OsResourceDetector()], + Resource({}), + ) + + self.assertEqual(resource.attributes[OS_TYPE], "linux") + self.assertEqual(resource.attributes[OS_VERSION], "666.5.0-35-generic") + + @patch("platform.system", lambda: "Windows") + @patch("platform.version", lambda: "10.0.666") + def test_os_detector_windows(self): + resource = get_aggregated_resources( + [OsResourceDetector()], + Resource({}), + ) + + self.assertEqual(resource.attributes[OS_TYPE], "windows") + self.assertEqual(resource.attributes[OS_VERSION], "10.0.666") + + @patch("platform.system", lambda: "SunOS") + def test_os_detector_solaris(self): + resource = get_aggregated_resources( + [OsResourceDetector()], + Resource({}), + ) + + self.assertEqual(resource.attributes[OS_TYPE], "solaris") From 8680918997ac3be93893bb7e8a4b5e3725a224e7 Mon Sep 17 00:00:00 2001 From: Zirak Date: Tue, 25 Jun 2024 09:01:44 +0300 Subject: [PATCH 02/13] sdk: Expose OsResourceDetector in resource detector entrypoint --- opentelemetry-sdk/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/opentelemetry-sdk/pyproject.toml b/opentelemetry-sdk/pyproject.toml index 30b1c00de0..862da5820c 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" +os = "opentelemetry.sdk.resources:OsResourceDetector" [project.urls] Homepage = "https://github.com/open-telemetry/opentelemetry-python/tree/main/opentelemetry-sdk" From fec2aac8d97c7aaf1a8d18a5e8d11efbb629532c Mon Sep 17 00:00:00 2001 From: Zirak Date: Tue, 25 Jun 2024 09:18:52 +0300 Subject: [PATCH 03/13] sdk: Make OsResourceDetector a default resource detector --- .../opentelemetry/sdk/resources/__init__.py | 4 +- .../tests/resources/test_resources.py | 46 ++++++++++++++----- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index 97d3bc0f44..89aee3315d 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -180,11 +180,13 @@ def create( resource = _DEFAULT_RESOURCE otel_experimental_resource_detectors = environ.get( - OTEL_EXPERIMENTAL_RESOURCE_DETECTORS, "otel" + OTEL_EXPERIMENTAL_RESOURCE_DETECTORS, "otel,os" ).split(",") if "otel" not in otel_experimental_resource_detectors: otel_experimental_resource_detectors.append("otel") + if "os" not in otel_experimental_resource_detectors: + otel_experimental_resource_detectors.append("os") for resource_detector in otel_experimental_resource_detectors: resource_detectors.append( diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py index 961b512af6..e9bb6a91ad 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import os +import platform import sys import unittest import uuid @@ -61,10 +62,31 @@ psutil = None +@patch( + "platform.uname", + lambda: platform.uname_result( + system="Linux", + node="node", + release="1.2.3", + version="4.5.6", + machine="x86_64", + ), +) class TestResources(unittest.TestCase): def setUp(self) -> None: environ[OTEL_RESOURCE_ATTRIBUTES] = "" + # OsResourceDetector calls into `platform` functions to grab its info, + # which is not part of the default resource. Provide the mock values + # and an extended default resource to be used throughout tests. + self.mock_platform = { + OS_TYPE: "linux", + OS_VERSION: "1.2.3", + } + self.default_resource = _DEFAULT_RESOURCE.merge( + Resource(self.mock_platform) + ) + def tearDown(self) -> None: environ.pop(OTEL_RESOURCE_ATTRIBUTES) @@ -86,6 +108,7 @@ def test_create(self): TELEMETRY_SDK_VERSION: _OPENTELEMETRY_SDK_VERSION, SERVICE_NAME: "unknown_service", } + expected_attributes.update(self.mock_platform) resource = Resource.create(attributes) self.assertIsInstance(resource, Resource) @@ -113,7 +136,7 @@ def test_create(self): resource = Resource.create(None) self.assertEqual( resource, - _DEFAULT_RESOURCE.merge( + self.default_resource.merge( Resource({SERVICE_NAME: "unknown_service"}, "") ), ) @@ -122,7 +145,7 @@ def test_create(self): resource = Resource.create(None, None) self.assertEqual( resource, - _DEFAULT_RESOURCE.merge( + self.default_resource.merge( Resource({SERVICE_NAME: "unknown_service"}, "") ), ) @@ -131,7 +154,7 @@ def test_create(self): resource = Resource.create({}) self.assertEqual( resource, - _DEFAULT_RESOURCE.merge( + self.default_resource.merge( Resource({SERVICE_NAME: "unknown_service"}, "") ), ) @@ -140,7 +163,7 @@ def test_create(self): resource = Resource.create({}, None) self.assertEqual( resource, - _DEFAULT_RESOURCE.merge( + self.default_resource.merge( Resource({SERVICE_NAME: "unknown_service"}, "") ), ) @@ -209,6 +232,7 @@ def test_immutability(self): TELEMETRY_SDK_VERSION: _OPENTELEMETRY_SDK_VERSION, SERVICE_NAME: "unknown_service", } + default_attributes.update(self.mock_platform) attributes_copy = attributes.copy() attributes_copy.update(default_attributes) @@ -261,7 +285,7 @@ def test_aggregated_resources_no_detectors(self): aggregated_resources = get_aggregated_resources([]) self.assertEqual( aggregated_resources, - _DEFAULT_RESOURCE.merge( + self.default_resource.merge( Resource({SERVICE_NAME: "unknown_service"}, "") ), ) @@ -312,7 +336,7 @@ def test_aggregated_resources_multiple_detectors(self): get_aggregated_resources( [resource_detector1, resource_detector2, resource_detector3] ), - _DEFAULT_RESOURCE.merge( + self.default_resource.merge( Resource({SERVICE_NAME: "unknown_service"}, "") ).merge( Resource( @@ -355,7 +379,7 @@ def test_aggregated_resources_different_schema_urls(self): ) self.assertEqual( get_aggregated_resources([resource_detector1, resource_detector2]), - _DEFAULT_RESOURCE.merge( + self.default_resource.merge( Resource({SERVICE_NAME: "unknown_service"}, "") ).merge( Resource( @@ -369,7 +393,7 @@ def test_aggregated_resources_different_schema_urls(self): get_aggregated_resources( [resource_detector2, resource_detector3] ), - _DEFAULT_RESOURCE.merge( + self.default_resource.merge( Resource({SERVICE_NAME: "unknown_service"}, "") ).merge( Resource({"key2": "value2", "key3": "value3"}, "url1") @@ -387,7 +411,7 @@ def test_aggregated_resources_different_schema_urls(self): resource_detector1, ] ), - _DEFAULT_RESOURCE.merge( + self.default_resource.merge( Resource({SERVICE_NAME: "unknown_service"}, "") ).merge( Resource( @@ -411,7 +435,7 @@ def test_resource_detector_ignore_error(self): with self.assertLogs(level=WARNING): self.assertEqual( get_aggregated_resources([resource_detector]), - _DEFAULT_RESOURCE.merge( + self.default_resource.merge( Resource({SERVICE_NAME: "unknown_service"}, "") ), ) @@ -431,7 +455,7 @@ def test_resource_detector_timeout(self, mock_logger): resource_detector.raise_on_error = False self.assertEqual( get_aggregated_resources([resource_detector]), - _DEFAULT_RESOURCE.merge( + self.default_resource.merge( Resource({SERVICE_NAME: "unknown_service"}, "") ), ) From b62533d91c085e0f2415b62740819f3a998829cc Mon Sep 17 00:00:00 2001 From: Zirak Date: Tue, 25 Jun 2024 19:39:22 +0300 Subject: [PATCH 04/13] sdk: Remove unnecessary variable See https://github.com/open-telemetry/opentelemetry-python/pull/3992#discussion_r1652178029 --- opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index 89aee3315d..c01f47ab6e 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -177,8 +177,6 @@ def create( resource_detectors = [] - resource = _DEFAULT_RESOURCE - otel_experimental_resource_detectors = environ.get( OTEL_EXPERIMENTAL_RESOURCE_DETECTORS, "otel,os" ).split(",") From 73d6e31d65ee6fa715a4f86ef18cd75062df4c3c Mon Sep 17 00:00:00 2001 From: Zirak Date: Sat, 29 Jun 2024 17:29:54 +0300 Subject: [PATCH 05/13] sdk: Normalise Solaris even harder See: https://github.com/open-telemetry/opentelemetry-python/pull/3992#discussion_r1659843581 https://github.com/open-telemetry/opentelemetry-python/pull/3992#discussion_r1654971677 --- opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py | 3 ++- opentelemetry-sdk/tests/resources/test_resources.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index c01f47ab6e..172638ee22 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -428,8 +428,9 @@ def detect(self) -> "Resource": if os_type == "windows": os_version = platform.version() # Align SunOS with conventions - if os_type == "sunos": + elif os_type == "sunos": os_type = "solaris" + os_version = platform.version() return Resource( { diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py index e9bb6a91ad..dbac01707c 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -774,6 +774,7 @@ def test_os_detector_windows(self): self.assertEqual(resource.attributes[OS_VERSION], "10.0.666") @patch("platform.system", lambda: "SunOS") + @patch("platform.version", lambda: "666.4.0.15.0") def test_os_detector_solaris(self): resource = get_aggregated_resources( [OsResourceDetector()], @@ -781,3 +782,4 @@ def test_os_detector_solaris(self): ) self.assertEqual(resource.attributes[OS_TYPE], "solaris") + self.assertEqual(resource.attributes[OS_VERSION], "666.4.0.15.0") From a32a6058ad13dd7d432c8629d87dfdb6f8cc074c Mon Sep 17 00:00:00 2001 From: Zirak Date: Sat, 29 Jun 2024 17:40:40 +0300 Subject: [PATCH 06/13] sdk: Collect resource detectors into a set See https://github.com/open-telemetry/opentelemetry-python/pull/3992#discussion_r1657882556 Co-Authored-By: Diego Hurtado --- .../opentelemetry/sdk/resources/__init__.py | 22 ++++++++++--------- .../tests/resources/test_resources.py | 9 ++++++++ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index 172638ee22..247f8b632a 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -175,17 +175,19 @@ def create( if not attributes: attributes = {} - resource_detectors = [] - - otel_experimental_resource_detectors = environ.get( - OTEL_EXPERIMENTAL_RESOURCE_DETECTORS, "otel,os" - ).split(",") - - if "otel" not in otel_experimental_resource_detectors: - otel_experimental_resource_detectors.append("otel") - if "os" not in otel_experimental_resource_detectors: - otel_experimental_resource_detectors.append("os") + otel_experimental_resource_detectors = {"otel", "os"}.union( + set( + [ + otel_experimental_resource_detector.strip() + for otel_experimental_resource_detector in environ.get( + "OTEL_EXPERIMENTAL_RESOURCE_DETECTORS", "" + ).split(",") + if otel_experimental_resource_detector + ] + ) + ) + resource_detectors = [] for resource_detector in otel_experimental_resource_detectors: resource_detectors.append( next( diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py index dbac01707c..375e2799c1 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -700,6 +700,15 @@ def test_resource_detector_entry_points_non_default(self): self.assertEqual(resource.attributes["a"], "b") self.assertEqual(resource.schema_url, "") + @patch.dict( + environ, {OTEL_EXPERIMENTAL_RESOURCE_DETECTORS: ""}, clear=True + ) + def test_resource_detector_entry_points_empty(self): + resource = Resource({}).create() + self.assertEqual( + resource.attributes["telemetry.sdk.language"], "python" + ) + def test_resource_detector_entry_points_otel(self): """ Test that OTELResourceDetector-resource-generated attributes are From 2b4181b374d7bfbeb8e55a2b60300422f29570cc Mon Sep 17 00:00:00 2001 From: Zirak Date: Wed, 3 Jul 2024 18:49:32 +0300 Subject: [PATCH 07/13] Changelog entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32698e9436..8812bda401 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#3964](https://github.com/open-telemetry/opentelemetry-python/pull/3964)) - Use semconv exception attributes for record exceptions in spans ([#3979](https://github.com/open-telemetry/opentelemetry-python/pull/3979)) +- sdk: Add an OS resource detector as a default detector + ([#3992](https://github.com/open-telemetry/opentelemetry-python/pull/3992)) ## Version 1.25.0/0.46b0 (2024-05-30) From 80ec3ed2fa17c0d6cbe7a855fd3784182540db19 Mon Sep 17 00:00:00 2001 From: Zirak Date: Sun, 14 Jul 2024 21:03:53 +0300 Subject: [PATCH 08/13] sdk: Ensure tests are compatible with python 3.8 The signature for `uname_result` changed between 3.8 and 3.9. --- .../tests/resources/test_resources.py | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py index 375e2799c1..08ab43c407 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -62,15 +62,26 @@ psutil = None +def get_mock_uname(): + """Generate a mock uname. There are different signatures in different python + versions.""" + + kwargs = { + "system": "Linux", + "node": "node", + "release": "1.2.3", + "version": "4.5.6", + "machine": "x86_64", + } + + if sys.version_info < (3, 9): + kwargs["processor"] = "x86_64" + + return platform.uname_result(**kwargs) + @patch( "platform.uname", - lambda: platform.uname_result( - system="Linux", - node="node", - release="1.2.3", - version="4.5.6", - machine="x86_64", - ), + get_mock_uname, ) class TestResources(unittest.TestCase): def setUp(self) -> None: From 777dd99de0929aff360729c0d9c264d1c5d2c692 Mon Sep 17 00:00:00 2001 From: Zirak Date: Sun, 14 Jul 2024 21:22:27 +0300 Subject: [PATCH 09/13] sdk: Make the robots happy --- .../opentelemetry/sdk/resources/__init__.py | 18 ++++++++---------- .../tests/resources/test_resources.py | 1 + 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index f4a96f4839..e37cdbd2b4 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -176,15 +176,13 @@ def create( attributes = {} otel_experimental_resource_detectors = {"otel", "os"}.union( - set( - [ - otel_experimental_resource_detector.strip() - for otel_experimental_resource_detector in environ.get( - "OTEL_EXPERIMENTAL_RESOURCE_DETECTORS", "" - ).split(",") - if otel_experimental_resource_detector - ] - ) + { + otel_experimental_resource_detector.strip() + for otel_experimental_resource_detector in environ.get( + OTEL_EXPERIMENTAL_RESOURCE_DETECTORS, "" + ).split(",") + if otel_experimental_resource_detector + } ) resource_detectors = [] @@ -376,7 +374,7 @@ def detect(self) -> "Resource": class OsResourceDetector(ResourceDetector): - """Detect os resources based on `Operating System conventions `_.""" def detect(self) -> "Resource": """Returns a resource with with `os.type` and `os.version`. Example of diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py index 69481c0199..71deec4670 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -79,6 +79,7 @@ def get_mock_uname(): return platform.uname_result(**kwargs) + @patch( "platform.uname", get_mock_uname, From 500b11df96ead73ed29469d1ce59a0de447bf043 Mon Sep 17 00:00:00 2001 From: Zirak Date: Sun, 14 Jul 2024 22:01:24 +0300 Subject: [PATCH 10/13] sdk: Turn the documentation into real rst --- .../opentelemetry/sdk/resources/__init__.py | 101 ++++++++++-------- 1 file changed, 59 insertions(+), 42 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index e37cdbd2b4..62c6ad0e83 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -377,48 +377,65 @@ class OsResourceDetector(ResourceDetector): """Detect os resources based on `Operating System conventions `_.""" def detect(self) -> "Resource": - """Returns a resource with with `os.type` and `os.version`. Example of - return values for `platform` calls in different systems: - - Linux: - >>> platform.system() - 'Linux' - >>> platform.release() - '6.5.0-35-generic' - >>> platform.version() - '#35~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue May 7 09:00:52 UTC 2' - - MacOS: - >>> platform.system() - 'Darwin' - >>> platform.release() - '23.0.0' - >>> platform.version() - 'Darwin Kernel Version 23.0.0: Fri Sep 15 14:42:57 PDT 2023; root:xnu-10002.1.13~1/RELEASE_ARM64_T8112' - - Windows: - >>> platform.system() - 'Windows' - >>> platform.release() - '2022Server' - >>> platform.version() - '10.0.20348' - - FreeBSD: - >>> platform.system() - 'FreeBSD' - >>> platform.release() - '14.1-RELEASE' - >>> platform.version() - 'FreeBSD 14.1-RELEASE releng/14.1-n267679-10e31f0946d8 GENERIC' - - Solaris: - >>> platform.system() - 'SunOS' - >>> platform.release() - '5.11' - >>> platform.version() - '11.4.0.15.0' + """Returns a resource with with ``os.type`` and ``os.version``. + + Python's platform library + ~~~~~~~~~~~~~~~~~~~~~~~~~ + + To grab this information, Python's ``platform`` does not return what a + user might expect it to. Below is a breakdown of its return values in + different operating systems. + + .. code-block:: python + :caption: Linux + + >>> platform.system() + 'Linux' + >>> platform.release() + '6.5.0-35-generic' + >>> platform.version() + '#35~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue May 7 09:00:52 UTC 2' + + .. code-block:: python + :caption: MacOS + + >>> platform.system() + 'Darwin' + >>> platform.release() + '23.0.0' + >>> platform.version() + 'Darwin Kernel Version 23.0.0: Fri Sep 15 14:42:57 PDT 2023; root:xnu-10002.1.13~1/RELEASE_ARM64_T8112' + + .. code-block:: python + :caption: Windows + + >>> platform.system() + 'Windows' + >>> platform.release() + '2022Server' + >>> platform.version() + '10.0.20348' + + .. code-block:: python + :caption: FreeBSD + + >>> platform.system() + 'FreeBSD' + >>> platform.release() + '14.1-RELEASE' + >>> platform.version() + 'FreeBSD 14.1-RELEASE releng/14.1-n267679-10e31f0946d8 GENERIC' + + .. code-block:: python + :caption: Solaris + + >>> platform.system() + 'SunOS' + >>> platform.release() + '5.11' + >>> platform.version() + '11.4.0.15.0' + """ os_type = platform.system().lower() From 1f3becf470e6f17ce3cb3e094832ed6fb97905d7 Mon Sep 17 00:00:00 2001 From: Zirak Date: Sun, 21 Jul 2024 10:01:10 +0300 Subject: [PATCH 11/13] sdk: Take OS out of the default resource detectors list --- .../opentelemetry/sdk/resources/__init__.py | 2 +- .../tests/resources/test_resources.py | 58 ++++--------------- 2 files changed, 12 insertions(+), 48 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index 62c6ad0e83..99d2f96ef3 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -175,7 +175,7 @@ def create( if not attributes: attributes = {} - otel_experimental_resource_detectors = {"otel", "os"}.union( + otel_experimental_resource_detectors = {"otel"}.union( { otel_experimental_resource_detector.strip() for otel_experimental_resource_detector in environ.get( diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py index 71deec4670..952069e870 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. import os -import platform import sys import unittest import uuid @@ -62,43 +61,10 @@ psutil = None -def get_mock_uname(): - """Generate a mock uname. There are different signatures in different python - versions.""" - - kwargs = { - "system": "Linux", - "node": "node", - "release": "1.2.3", - "version": "4.5.6", - "machine": "x86_64", - } - - if sys.version_info < (3, 9): - kwargs["processor"] = "x86_64" - - return platform.uname_result(**kwargs) - - -@patch( - "platform.uname", - get_mock_uname, -) class TestResources(unittest.TestCase): def setUp(self) -> None: environ[OTEL_RESOURCE_ATTRIBUTES] = "" - # OsResourceDetector calls into `platform` functions to grab its info, - # which is not part of the default resource. Provide the mock values - # and an extended default resource to be used throughout tests. - self.mock_platform = { - OS_TYPE: "linux", - OS_VERSION: "1.2.3", - } - self.default_resource = _DEFAULT_RESOURCE.merge( - Resource(self.mock_platform) - ) - def tearDown(self) -> None: environ.pop(OTEL_RESOURCE_ATTRIBUTES) @@ -120,7 +86,6 @@ def test_create(self): TELEMETRY_SDK_VERSION: _OPENTELEMETRY_SDK_VERSION, SERVICE_NAME: "unknown_service", } - expected_attributes.update(self.mock_platform) resource = Resource.create(attributes) self.assertIsInstance(resource, Resource) @@ -148,7 +113,7 @@ def test_create(self): resource = Resource.create(None) self.assertEqual( resource, - self.default_resource.merge( + _DEFAULT_RESOURCE.merge( Resource({SERVICE_NAME: "unknown_service"}, "") ), ) @@ -157,7 +122,7 @@ def test_create(self): resource = Resource.create(None, None) self.assertEqual( resource, - self.default_resource.merge( + _DEFAULT_RESOURCE.merge( Resource({SERVICE_NAME: "unknown_service"}, "") ), ) @@ -166,7 +131,7 @@ def test_create(self): resource = Resource.create({}) self.assertEqual( resource, - self.default_resource.merge( + _DEFAULT_RESOURCE.merge( Resource({SERVICE_NAME: "unknown_service"}, "") ), ) @@ -175,7 +140,7 @@ def test_create(self): resource = Resource.create({}, None) self.assertEqual( resource, - self.default_resource.merge( + _DEFAULT_RESOURCE.merge( Resource({SERVICE_NAME: "unknown_service"}, "") ), ) @@ -244,7 +209,6 @@ def test_immutability(self): TELEMETRY_SDK_VERSION: _OPENTELEMETRY_SDK_VERSION, SERVICE_NAME: "unknown_service", } - default_attributes.update(self.mock_platform) attributes_copy = attributes.copy() attributes_copy.update(default_attributes) @@ -297,7 +261,7 @@ def test_aggregated_resources_no_detectors(self): aggregated_resources = get_aggregated_resources([]) self.assertEqual( aggregated_resources, - self.default_resource.merge( + _DEFAULT_RESOURCE.merge( Resource({SERVICE_NAME: "unknown_service"}, "") ), ) @@ -348,7 +312,7 @@ def test_aggregated_resources_multiple_detectors(self): get_aggregated_resources( [resource_detector1, resource_detector2, resource_detector3] ), - self.default_resource.merge( + _DEFAULT_RESOURCE.merge( Resource({SERVICE_NAME: "unknown_service"}, "") ).merge( Resource( @@ -391,7 +355,7 @@ def test_aggregated_resources_different_schema_urls(self): ) self.assertEqual( get_aggregated_resources([resource_detector1, resource_detector2]), - self.default_resource.merge( + _DEFAULT_RESOURCE.merge( Resource({SERVICE_NAME: "unknown_service"}, "") ).merge( Resource( @@ -405,7 +369,7 @@ def test_aggregated_resources_different_schema_urls(self): get_aggregated_resources( [resource_detector2, resource_detector3] ), - self.default_resource.merge( + _DEFAULT_RESOURCE.merge( Resource({SERVICE_NAME: "unknown_service"}, "") ).merge( Resource({"key2": "value2", "key3": "value3"}, "url1") @@ -423,7 +387,7 @@ def test_aggregated_resources_different_schema_urls(self): resource_detector1, ] ), - self.default_resource.merge( + _DEFAULT_RESOURCE.merge( Resource({SERVICE_NAME: "unknown_service"}, "") ).merge( Resource( @@ -447,7 +411,7 @@ def test_resource_detector_ignore_error(self): with self.assertLogs(level=WARNING): self.assertEqual( get_aggregated_resources([resource_detector]), - self.default_resource.merge( + _DEFAULT_RESOURCE.merge( Resource({SERVICE_NAME: "unknown_service"}, "") ), ) @@ -467,7 +431,7 @@ def test_resource_detector_timeout(self, mock_logger): resource_detector.raise_on_error = False self.assertEqual( get_aggregated_resources([resource_detector]), - self.default_resource.merge( + _DEFAULT_RESOURCE.merge( Resource({SERVICE_NAME: "unknown_service"}, "") ), ) From 617e39738a52c6357e0b3aca5df9095350c572cb Mon Sep 17 00:00:00 2001 From: Zirak Date: Tue, 23 Jul 2024 09:24:14 +0300 Subject: [PATCH 12/13] sdk: Test providing OS resource detector through env --- opentelemetry-sdk/tests/resources/test_resources.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py index 952069e870..70e90864a5 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -685,6 +685,15 @@ def test_resource_detector_entry_points_empty(self): resource.attributes["telemetry.sdk.language"], "python" ) + @patch.dict( + environ, {OTEL_EXPERIMENTAL_RESOURCE_DETECTORS: "os"}, clear=True + ) + def test_resource_detector_entry_points_os(self): + resource = Resource({}).create() + + self.assertIn(OS_TYPE, resource.attributes) + self.assertIn(OS_VERSION, resource.attributes) + def test_resource_detector_entry_points_otel(self): """ Test that OTELResourceDetector-resource-generated attributes are From 99e13d3a5a81a3dbfb9321a54fb33e1e44f76dd0 Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Thu, 1 Aug 2024 13:35:42 -0700 Subject: [PATCH 13/13] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c15054a8d9..f4fb7ec489 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Drop Final annotation from Enum in semantic conventions ([#4085](https://github.com/open-telemetry/opentelemetry-python/pull/4085)) - Update log export example to not use root logger ([#4090](https://github.com/open-telemetry/opentelemetry-python/pull/4090)) +- sdk: Add OS resource detector + ([#3992](https://github.com/open-telemetry/opentelemetry-python/pull/3992)) ## Version 1.26.0/0.47b0 (2024-07-25) @@ -66,8 +68,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#4015](https://github.com/open-telemetry/opentelemetry-python/pull/4015)) - Fix inaccessible `SCHEMA_URL` constants in `opentelemetry-semantic-conventions` ([#4069](https://github.com/open-telemetry/opentelemetry-python/pull/4069)) -- sdk: Add an OS resource detector - ([#3992](https://github.com/open-telemetry/opentelemetry-python/pull/3992)) ## Version 1.25.0/0.46b0 (2024-05-30)