diff --git a/.changelog/5535.fixed b/.changelog/5535.fixed new file mode 100644 index 0000000000..47bf1c1adc --- /dev/null +++ b/.changelog/5535.fixed @@ -0,0 +1 @@ +`opentelemetry-sdk`: fix values for `process.executable.name` and `process.executable.path` to match semantic conventions. diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index d5b2bdf9f4..a84c9f25fe 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -367,21 +367,20 @@ def detect(self) -> "Resource": ) ) _process_pid = os.getpid() - _process_executable_name = sys.executable - _process_executable_path = os.path.dirname(_process_executable_name) # Use sys.orig_argv, which preserves the original arguments received # by the interpreter. This correctly captures ``python -m `` # invocations where sys.argv is rewritten to the resolved module path # and the ``-m `` information is lost. Only read argv[0] by # default because full command arguments are opt-in. _process_command = sys.orig_argv[0] if sys.orig_argv else "" + executable = sys.executable or "" resource_info: dict[str, AttributeValue] = { PROCESS_RUNTIME_DESCRIPTION: sys.version, PROCESS_RUNTIME_NAME: sys.implementation.name, PROCESS_RUNTIME_VERSION: _runtime_version, PROCESS_PID: _process_pid, - PROCESS_EXECUTABLE_NAME: _process_executable_name, - PROCESS_EXECUTABLE_PATH: _process_executable_path, + PROCESS_EXECUTABLE_NAME: os.path.basename(executable), + PROCESS_EXECUTABLE_PATH: executable, PROCESS_COMMAND: _process_command, } if self._include_command_args: diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py index 12f6beb552..282611dd1e 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -630,6 +630,7 @@ def test_service_name_env_precedence(self): "sys.orig_argv", ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"], ) + @patch("sys.executable", "/usr/bin/uvicorn") def test_process_detector(self): initial_resource = Resource({"foo": "bar"}) aggregated_resource = get_aggregated_resources([ProcessResourceDetector()], initial_resource) @@ -662,13 +663,13 @@ def test_process_detector(self): self.assertEqual( aggregated_resource.attributes[PROCESS_EXECUTABLE_NAME], - sys.executable, + "uvicorn", ) self.assertEqual( aggregated_resource.attributes[PROCESS_EXECUTABLE_PATH], - os.path.dirname(sys.executable), + "/usr/bin/uvicorn", ) - self.assertEqual(aggregated_resource.attributes[PROCESS_COMMAND], sys.orig_argv[0]) + self.assertEqual(aggregated_resource.attributes[PROCESS_COMMAND], "uvicorn") self.assertNotIn( PROCESS_COMMAND_LINE, aggregated_resource.attributes, @@ -769,6 +770,20 @@ def test_process_detector_uses_orig_argv_for_python_m_on_opt_in(self): ("/usr/bin/python", "-m", "myapp"), ) + @patch("sys.executable", None) + def test_process_detector_handles_missing_executable(self): + initial_resource = Resource({"foo": "bar"}) + aggregated_resource = get_aggregated_resources([ProcessResourceDetector()], initial_resource) + + self.assertEqual( + aggregated_resource.attributes[PROCESS_EXECUTABLE_NAME], + "", + ) + self.assertEqual( + aggregated_resource.attributes[PROCESS_EXECUTABLE_PATH], + "", + ) + def test_resource_detector_entry_points_default(self): resource = Resource({}).create()