From c1c2d7bea46818f5e0ed03d8dc1883672ab91492 Mon Sep 17 00:00:00 2001 From: Ali Date: Mon, 13 Apr 2026 23:26:00 +0500 Subject: [PATCH 1/4] fix(sdk): use sys.orig_argv for process.command to handle python -m invocations ProcessResourceDetector populated process.command, process.command_line, and process.command_args from sys.argv. For applications launched via `python -m `, the interpreter rewrites sys.argv[0] to the resolved module path, so the ``-m `` portion of the original invocation is lost and the detector emits misleading telemetry. Python 3.10+ exposes sys.orig_argv which preserves the original arguments received by the interpreter. Since the SDK already requires Python >= 3.10, switch to sys.orig_argv (with a getattr fallback for safety). This also aligns with the OTel semantic conventions that reference /proc//cmdline for these attributes. Fixes #4518 Signed-off-by: Ali --- CHANGELOG.md | 2 ++ .../opentelemetry/sdk/resources/__init__.py | 13 +++++++-- .../tests/resources/test_resources.py | 29 +++++++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afec89b73f4..4a1630eae52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#5076](https://github.com/open-telemetry/opentelemetry-python/pull/5076)) - `opentelemetry-semantic-conventions`: use `X | Y` union annotation ([#5096](https://github.com/open-telemetry/opentelemetry-python/pull/5096)) +- `opentelemetry-sdk`: Fix `ProcessResourceDetector` to use `sys.orig_argv` when available so that `process.command`, `process.command_line`, and `process.command_args` reflect the original invocation for `python -m ` runs (where `sys.argv[0]` is rewritten to the module path) + ([#4518](https://github.com/open-telemetry/opentelemetry-python/issues/4518)) ## Version 1.41.0/0.62b0 (2026-04-09) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index a04d27e9ab1..c79ed83f36d 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -380,9 +380,16 @@ def detect(self) -> "Resource": _process_pid = os.getpid() _process_executable_name = sys.executable _process_executable_path = os.path.dirname(_process_executable_name) - _process_command = sys.argv[0] - _process_command_line = " ".join(sys.argv) - _process_command_args = sys.argv + # Prefer sys.orig_argv (Python 3.10+), 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. sys.orig_argv also aligns with /proc//cmdline, which + # the OTel semantic conventions reference for these attributes. + _process_argv = list(getattr(sys, "orig_argv", sys.argv)) + _process_command = _process_argv[0] if _process_argv else "" + _process_command_line = " ".join(_process_argv) + _process_command_args = _process_argv resource_info = { PROCESS_RUNTIME_DESCRIPTION: sys.version, PROCESS_RUNTIME_NAME: sys.implementation.name, diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py index c083eff1460..9f75f7c99fc 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -564,6 +564,10 @@ def test_service_name_env_precedence(self): "sys.argv", ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"], ) + @patch( + "sys.orig_argv", + ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"], + ) def test_process_detector(self): initial_resource = Resource({"foo": "bar"}) aggregated_resource = get_aggregated_resources( @@ -618,6 +622,31 @@ def test_process_detector(self): tuple(sys.argv), ) + @patch("sys.argv", ["/path/to/myapp/__main__.py"]) + @patch("sys.orig_argv", ["/usr/bin/python", "-m", "myapp"]) + def test_process_detector_uses_orig_argv_for_python_m(self): + """For ``python -m `` invocations sys.argv[0] is rewritten to + the resolved module path, losing the ``-m `` information. + sys.orig_argv preserves the original invocation and must be preferred. + See https://github.com/open-telemetry/opentelemetry-python/issues/4518. + """ + aggregated_resource = get_aggregated_resources( + [ProcessResourceDetector()], Resource({"foo": "bar"}) + ) + + self.assertEqual( + aggregated_resource.attributes[PROCESS_COMMAND], + "/usr/bin/python", + ) + self.assertEqual( + aggregated_resource.attributes[PROCESS_COMMAND_LINE], + "/usr/bin/python -m myapp", + ) + self.assertEqual( + aggregated_resource.attributes[PROCESS_COMMAND_ARGS], + ("/usr/bin/python", "-m", "myapp"), + ) + def test_resource_detector_entry_points_default(self): resource = Resource({}).create() From d110a9c97f5eb964ca76981bbbfd3d80c1a785dd Mon Sep 17 00:00:00 2001 From: Ali Date: Tue, 14 Apr 2026 00:26:33 +0500 Subject: [PATCH 2/4] nit: drop sys.orig_argv fallback and use PR number in changelog Two review nits from MikeGoldsmith: - sys.orig_argv has been available since Python 3.10; the SDK now requires 3.10+ so the getattr fallback is dead code. Use sys.orig_argv directly and update the comment. - CHANGELOG entries should reference the PR number, not the issue number. Signed-off-by: Ali --- CHANGELOG.md | 4 ++-- .../src/opentelemetry/sdk/resources/__init__.py | 14 +++++++------- .../tests/resources/test_resources.py | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a1630eae52..60b761112b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,8 +24,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#5076](https://github.com/open-telemetry/opentelemetry-python/pull/5076)) - `opentelemetry-semantic-conventions`: use `X | Y` union annotation ([#5096](https://github.com/open-telemetry/opentelemetry-python/pull/5096)) -- `opentelemetry-sdk`: Fix `ProcessResourceDetector` to use `sys.orig_argv` when available so that `process.command`, `process.command_line`, and `process.command_args` reflect the original invocation for `python -m ` runs (where `sys.argv[0]` is rewritten to the module path) - ([#4518](https://github.com/open-telemetry/opentelemetry-python/issues/4518)) +- `opentelemetry-sdk`: Fix `ProcessResourceDetector` to use `sys.orig_argv` so that `process.command`, `process.command_line`, and `process.command_args` reflect the original invocation for `python -m ` runs (where `sys.argv[0]` is rewritten to the module path) + ([#5083](https://github.com/open-telemetry/opentelemetry-python/pull/5083)) ## Version 1.41.0/0.62b0 (2026-04-09) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index c79ed83f36d..f8d1df68211 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -380,13 +380,13 @@ def detect(self) -> "Resource": _process_pid = os.getpid() _process_executable_name = sys.executable _process_executable_path = os.path.dirname(_process_executable_name) - # Prefer sys.orig_argv (Python 3.10+), 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. sys.orig_argv also aligns with /proc//cmdline, which - # the OTel semantic conventions reference for these attributes. - _process_argv = list(getattr(sys, "orig_argv", sys.argv)) + # 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. sys.orig_argv also + # aligns with /proc//cmdline, which the OTel semantic + # conventions reference for these attributes. + _process_argv = list(sys.orig_argv) _process_command = _process_argv[0] if _process_argv else "" _process_command_line = " ".join(_process_argv) _process_command_args = _process_argv diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py index 9f75f7c99fc..cc96a5752d6 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -611,15 +611,15 @@ def test_process_detector(self): os.path.dirname(sys.executable), ) self.assertEqual( - aggregated_resource.attributes[PROCESS_COMMAND], sys.argv[0] + aggregated_resource.attributes[PROCESS_COMMAND], sys.orig_argv[0] ) self.assertEqual( aggregated_resource.attributes[PROCESS_COMMAND_LINE], - " ".join(sys.argv), + " ".join(sys.orig_argv), ) self.assertEqual( aggregated_resource.attributes[PROCESS_COMMAND_ARGS], - tuple(sys.argv), + tuple(sys.orig_argv), ) @patch("sys.argv", ["/path/to/myapp/__main__.py"]) From 04d2125c6796ca8723698a0df1d009227e6475bc Mon Sep 17 00:00:00 2001 From: Ali Date: Fri, 17 Apr 2026 01:53:29 +0500 Subject: [PATCH 3/4] test: remove redundant sys.argv patch from test_process_detector Signed-off-by: Ali --- opentelemetry-sdk/tests/resources/test_resources.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py index cc96a5752d6..85289b4e5c6 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -560,10 +560,6 @@ def test_service_name_env_precedence(self): Resource({"service.name": "from-service-name"}), ) - @patch( - "sys.argv", - ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"], - ) @patch( "sys.orig_argv", ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"], From 46afba5a5de52e15bb753164bcd3bcbc6a043d8f Mon Sep 17 00:00:00 2001 From: Ali Date: Fri, 17 Apr 2026 16:39:00 +0500 Subject: [PATCH 4/4] test: drop superfluous sys.argv mock in python-m test The detector reads only sys.orig_argv, so patching sys.argv had no effect on the test's outcome. Keep only the sys.orig_argv patch. Signed-off-by: Ali --- opentelemetry-sdk/tests/resources/test_resources.py | 1 - 1 file changed, 1 deletion(-) diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py index 85289b4e5c6..38ec5c1a702 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -618,7 +618,6 @@ def test_process_detector(self): tuple(sys.orig_argv), ) - @patch("sys.argv", ["/path/to/myapp/__main__.py"]) @patch("sys.orig_argv", ["/usr/bin/python", "-m", "myapp"]) def test_process_detector_uses_orig_argv_for_python_m(self): """For ``python -m `` invocations sys.argv[0] is rewritten to