From e407185c1a01bca090f9502acb9fa7365458280d Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 19 Aug 2025 11:05:47 +0200 Subject: [PATCH 1/5] opentelemetry-instrumentation: teach opentelemetry-instrument about gevent Introduce OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH=patch_all environment variable that calls gevent monkey module patch_all method before starting up the distro and sdk. The environment variable should useful also for apps instrumented via the opentelemetry-operator. The flag removes the following warning (and hang) when running locust: $ opentelemetry-instrument locust /lib/python3.10/site-packages/locust/__init__.py:16: MonkeyPatchWarning: Monkey-patching ssl after ssl has already been imported may lead to errors, including RecursionError on Python 3.6. It may also silently lead to incorrect behaviour on Python 3.7. Please monkey-patch earlier. See https://github.com/gevent/gevent/issues/1016. Modules that had direct imports (NOT patched): ['urllib3.util (/lib/python3.10/site-packages/urllib3/util/__init__.py)', 'urllib3.util.ssl_ (/lib/python3.10/site-packages/urllib3/util/ssl_.py)']. monkey.patch_all() --- opentelemetry-instrumentation/README.rst | 8 +++++- .../auto_instrumentation/__init__.py | 25 +++++++++++++++++ .../test-requirements.txt | 1 + .../auto_instrumentation/test_initialize.py | 27 +++++++++++++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/opentelemetry-instrumentation/README.rst b/opentelemetry-instrumentation/README.rst index c8731fda2e..a342aa188c 100644 --- a/opentelemetry-instrumentation/README.rst +++ b/opentelemetry-instrumentation/README.rst @@ -104,7 +104,13 @@ check `here None: environ["PYTHONPATH"], dirname(abspath(__file__)), pathsep ) + # handle optional gevent monkey patching. This is done via environment variables so it may be used from the + # opentelemetry operator + gevent_patch_env_variable_name = ( + "OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH" + ) + gevent_patch: str | None = environ.get(gevent_patch_env_variable_name) + if gevent_patch is not None: + if gevent_patch != "patch_all": + _logger.error( + "%s values must be `patch_all`", gevent_patch_env_variable_name + ) + else: + try: + from gevent import monkey + + getattr(monkey, gevent_patch)() + except ImportError: + _logger.error( + "Requested to monkey patch with gevent but gevent is not available" + ) + if not swallow_exceptions: + raise + try: distro = _load_distro() distro.configure() diff --git a/opentelemetry-instrumentation/test-requirements.txt b/opentelemetry-instrumentation/test-requirements.txt index 943a45c8f4..58c09a950b 100644 --- a/opentelemetry-instrumentation/test-requirements.txt +++ b/opentelemetry-instrumentation/test-requirements.txt @@ -1,5 +1,6 @@ asgiref==3.8.1 Deprecated==1.2.14 +gevent==25.5.1 iniconfig==2.0.0 packaging==24.0 pluggy==1.5.0 diff --git a/opentelemetry-instrumentation/tests/auto_instrumentation/test_initialize.py b/opentelemetry-instrumentation/tests/auto_instrumentation/test_initialize.py index 715da0d2f6..57e3907814 100644 --- a/opentelemetry-instrumentation/tests/auto_instrumentation/test_initialize.py +++ b/opentelemetry-instrumentation/tests/auto_instrumentation/test_initialize.py @@ -72,3 +72,30 @@ def test_reraises_exceptions(self, load_distro_mock, logger_mock): ) self.assertEqual("inner exception", str(em.exception)) + + @patch.dict( + "os.environ", + { + "OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH": "patch_foo" + }, + ) + @patch("opentelemetry.instrumentation.auto_instrumentation._logger") + def test_handles_invalid_gevent_monkeypatch(self, logger_mock): + # pylint:disable=no-self-use + auto_instrumentation.initialize() + logger_mock.error.assert_called_once_with( + "%s values must be `patch_all`", + "OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH", + ) + + @patch.dict( + "os.environ", + { + "OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH": "patch_all" + }, + ) + @patch("opentelemetry.instrumentation.auto_instrumentation._logger") + def test_handles_patch_all_gevent_monkeypatch(self, logger_mock): + # pylint:disable=no-self-use + auto_instrumentation.initialize() + logger_mock.error.assert_not_called() From 6a6739ab6f88195e12dc60d9db3374b7038b3d1c Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 19 Aug 2025 11:27:41 +0200 Subject: [PATCH 2/5] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c0f788197..3c2b92492d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#3666](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3666)) - `opentelemetry-sdk-extension-aws` Add AWS X-Ray Remote Sampler with initial Rules Poller implementation ([#3366](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3366)) +- `opentelemetry-instrumentation`: teach opentelemetry-instrument about gevent monkeypatching + ([#3699](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3699)) ## Version 1.36.0/0.57b0 (2025-07-29) From 0862ab5ec7a9b1839bc3661e3d284ba352913f65 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 19 Aug 2025 11:39:20 +0200 Subject: [PATCH 3/5] Please pylint --- .../instrumentation/auto_instrumentation/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py index 9e16b94a74..3cd374d24e 100644 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py @@ -145,6 +145,7 @@ def initialize(*, swallow_exceptions: bool = True) -> None: ) else: try: + # pylint: disable=import-outside-toplevel from gevent import monkey getattr(monkey, gevent_patch)() From 2a84ef06562b9b593e0255c9203869046546dead Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Fri, 29 Aug 2025 09:40:56 +0200 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: Tammy Baylis <96076570+tammy-baylis-swi@users.noreply.github.com> --- CHANGELOG.md | 2 +- .../instrumentation/auto_instrumentation/__init__.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c2b92492d..2483db9db2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#3666](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3666)) - `opentelemetry-sdk-extension-aws` Add AWS X-Ray Remote Sampler with initial Rules Poller implementation ([#3366](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3366)) -- `opentelemetry-instrumentation`: teach opentelemetry-instrument about gevent monkeypatching +- `opentelemetry-instrumentation`: add support for `OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH` to inform opentelemetry-instrument about gevent monkeypatching ([#3699](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3699)) ## Version 1.36.0/0.57b0 (2025-07-29) diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py index 3cd374d24e..5ba914e147 100644 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py @@ -150,8 +150,8 @@ def initialize(*, swallow_exceptions: bool = True) -> None: getattr(monkey, gevent_patch)() except ImportError: - _logger.error( - "Requested to monkey patch with gevent but gevent is not available" + _logger.exception( + "Failed to monkey patch with gevent because gevent is not available" ) if not swallow_exceptions: raise From 90c9af74c6780cab3bdc93e09d3ca6cb580d3c4e Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Fri, 29 Aug 2025 09:48:54 +0200 Subject: [PATCH 5/5] Move environment variable to proper module --- .../instrumentation/auto_instrumentation/__init__.py | 11 +++++++---- .../instrumentation/environment_variables.py | 7 +++++++ .../tests/auto_instrumentation/test_initialize.py | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py index 5ba914e147..1c87d2c327 100644 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py @@ -26,6 +26,9 @@ _load_distro, _load_instrumentors, ) +from opentelemetry.instrumentation.environment_variables import ( + OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH, +) from opentelemetry.instrumentation.utils import _python_path_without_directory from opentelemetry.instrumentation.version import __version__ from opentelemetry.util._importlib_metadata import entry_points @@ -134,14 +137,14 @@ def initialize(*, swallow_exceptions: bool = True) -> None: # handle optional gevent monkey patching. This is done via environment variables so it may be used from the # opentelemetry operator - gevent_patch_env_variable_name = ( - "OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH" + gevent_patch: str | None = environ.get( + OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH ) - gevent_patch: str | None = environ.get(gevent_patch_env_variable_name) if gevent_patch is not None: if gevent_patch != "patch_all": _logger.error( - "%s values must be `patch_all`", gevent_patch_env_variable_name + "%s value must be `patch_all`", + OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH, ) else: try: diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/environment_variables.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/environment_variables.py index 7886779632..ee95bb3587 100644 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/environment_variables.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/environment_variables.py @@ -26,3 +26,10 @@ """ .. envvar:: OTEL_PYTHON_CONFIGURATOR """ + +OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH = ( + "OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH" +) +""" +.. envvar:: OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH +""" diff --git a/opentelemetry-instrumentation/tests/auto_instrumentation/test_initialize.py b/opentelemetry-instrumentation/tests/auto_instrumentation/test_initialize.py index 57e3907814..c2bf7116d9 100644 --- a/opentelemetry-instrumentation/tests/auto_instrumentation/test_initialize.py +++ b/opentelemetry-instrumentation/tests/auto_instrumentation/test_initialize.py @@ -84,7 +84,7 @@ def test_handles_invalid_gevent_monkeypatch(self, logger_mock): # pylint:disable=no-self-use auto_instrumentation.initialize() logger_mock.error.assert_called_once_with( - "%s values must be `patch_all`", + "%s value must be `patch_all`", "OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH", )