diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a0aa36d4f..d3781c7fb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- `opentelemetry-sdk`: Add `process` resource detector support to declarative file configuration via `detection_development.detectors[].process` + ([#5001](https://github.com/open-telemetry/opentelemetry-python/pull/5001)) - `opentelemetry-sdk`: Add shared `_parse_headers` helper for declarative config OTLP exporters ([#5021](https://github.com/open-telemetry/opentelemetry-python/pull/5021)) - `opentelemetry-api`: Replace a broad exception in attribute cleaning tests to satisfy pylint in the `lint-opentelemetry-api` CI job diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_resource.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_resource.py index d58bd4d31d..df219de973 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_resource.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_resource.py @@ -29,6 +29,7 @@ from opentelemetry.sdk.resources import ( _DEFAULT_RESOURCE, SERVICE_NAME, + ProcessResourceDetector, Resource, ) @@ -149,6 +150,8 @@ def _run_detectors( is updated in-place; later detectors overwrite earlier ones for the same key. """ + if detector_config.process is not None: + detected_attrs.update(ProcessResourceDetector().detect().attributes) def _filter_attributes( diff --git a/opentelemetry-sdk/tests/_configuration/test_resource.py b/opentelemetry-sdk/tests/_configuration/test_resource.py index b50bc03fff..bc8b3f69bf 100644 --- a/opentelemetry-sdk/tests/_configuration/test_resource.py +++ b/opentelemetry-sdk/tests/_configuration/test_resource.py @@ -13,6 +13,7 @@ # limitations under the License. import os +import sys import unittest from unittest.mock import patch @@ -20,9 +21,13 @@ from opentelemetry.sdk._configuration.models import ( AttributeNameValue, AttributeType, + ExperimentalResourceDetection, + ExperimentalResourceDetector, ) from opentelemetry.sdk._configuration.models import Resource as ResourceConfig from opentelemetry.sdk.resources import ( + PROCESS_PID, + PROCESS_RUNTIME_NAME, SERVICE_NAME, TELEMETRY_SDK_LANGUAGE, TELEMETRY_SDK_NAME, @@ -295,3 +300,70 @@ def test_attributes_list_invalid_pair_skipped(self): self.assertEqual(resource.attributes["foo"], "bar") self.assertNotIn("no-equals", resource.attributes) self.assertTrue(any("no-equals" in msg for msg in cm.output)) + + +class TestProcessResourceDetector(unittest.TestCase): + @staticmethod + def _config_with_process() -> ResourceConfig: + return ResourceConfig( + detection_development=ExperimentalResourceDetection( + detectors=[ExperimentalResourceDetector(process={})] + ) + ) + + def test_process_detector_adds_process_attributes(self): + resource = create_resource(self._config_with_process()) + self.assertIn(PROCESS_PID, resource.attributes) + self.assertEqual(resource.attributes[PROCESS_PID], os.getpid()) + self.assertEqual( + resource.attributes[PROCESS_RUNTIME_NAME], + sys.implementation.name, + ) + + def test_process_detector_also_includes_sdk_defaults(self): + resource = create_resource(self._config_with_process()) + self.assertEqual(resource.attributes[TELEMETRY_SDK_LANGUAGE], "python") + self.assertIn(TELEMETRY_SDK_VERSION, resource.attributes) + + def test_process_detector_not_run_when_absent(self): + resource = create_resource(ResourceConfig()) + self.assertNotIn(PROCESS_PID, resource.attributes) + + def test_process_detector_not_run_when_detection_development_is_none(self): + resource = create_resource(ResourceConfig(detection_development=None)) + self.assertNotIn(PROCESS_PID, resource.attributes) + + def test_process_detector_not_run_when_detectors_list_empty(self): + config = ResourceConfig( + detection_development=ExperimentalResourceDetection(detectors=[]) + ) + resource = create_resource(config) + self.assertNotIn(PROCESS_PID, resource.attributes) + + def test_explicit_attributes_override_process_detector(self): + """Config attributes win over detector-provided values.""" + config = ResourceConfig( + attributes=[ + AttributeNameValue( + name="process.pid", value=99999, type=AttributeType.int + ) + ], + detection_development=ExperimentalResourceDetection( + detectors=[ExperimentalResourceDetector(process={})] + ), + ) + resource = create_resource(config) + self.assertEqual(resource.attributes[PROCESS_PID], 99999) + + def test_multiple_detector_entries_run_process_once(self): + """Multiple detector list entries each with process={} should still work.""" + config = ResourceConfig( + detection_development=ExperimentalResourceDetection( + detectors=[ + ExperimentalResourceDetector(process={}), + ExperimentalResourceDetector(process={}), + ] + ) + ) + resource = create_resource(config) + self.assertEqual(resource.attributes[PROCESS_PID], os.getpid())