From 5f0d4ff4ced34be1852dd8d23c95a37355606c00 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Fri, 29 Aug 2025 20:52:35 +0200 Subject: [PATCH] opentelemetry-instrumentation: teach opentelemetry-instrument about gevent (#3699) * 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() * Update CHANGELOG * Please pylint * Apply suggestions from code review Co-authored-by: Tammy Baylis <96076570+tammy-baylis-swi@users.noreply.github.com> * Move environment variable to proper module --------- Co-authored-by: Tammy Baylis <96076570+tammy-baylis-swi@users.noreply.github.com> --- CHANGELOG.md | 2 ++ opentelemetry-instrumentation/README.rst | 8 ++++- .../auto_instrumentation/__init__.py | 29 +++++++++++++++++++ .../instrumentation/environment_variables.py | 7 +++++ .../test-requirements.txt | 1 + .../auto_instrumentation/test_initialize.py | 27 +++++++++++++++++ 6 files changed, 73 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c0f78819..2483db9db 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`: 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/README.rst b/opentelemetry-instrumentation/README.rst index c8731fda2..a342aa188 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: str | None = environ.get( + OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH + ) + if gevent_patch is not None: + if gevent_patch != "patch_all": + _logger.error( + "%s value must be `patch_all`", + OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH, + ) + else: + try: + # pylint: disable=import-outside-toplevel + from gevent import monkey + + getattr(monkey, gevent_patch)() + except ImportError: + _logger.exception( + "Failed to monkey patch with gevent because gevent is not available" + ) + if not swallow_exceptions: + raise + try: distro = _load_distro() distro.configure() diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/environment_variables.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/environment_variables.py index 788677963..ee95bb358 100644 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/environment_variables.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/environment_variables.py @@ -26,3 +26,10 @@ OTEL_PYTHON_CONFIGURATOR = "OTEL_PYTHON_CONFIGURATOR" """ .. 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/test-requirements.txt b/opentelemetry-instrumentation/test-requirements.txt index 943a45c8f..58c09a950 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 715da0d2f..c2bf7116d 100644 --- a/opentelemetry-instrumentation/tests/auto_instrumentation/test_initialize.py +++ b/opentelemetry-instrumentation/tests/auto_instrumentation/test_initialize.py @@ -72,3 +72,30 @@ class TestInitialize(TestCase): ) 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 value 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()