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>
This commit is contained in:
		
							parent
							
								
									86d26ce1b8
								
							
						
					
					
						commit
						5f0d4ff4ce
					
				|  | @ -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)) |   ([#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 | - `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)) |   ([#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) | ## Version 1.36.0/0.57b0 (2025-07-29) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -104,7 +104,13 @@ check `here <https://opentelemetry-python.readthedocs.io/en/stable/index.html#in | ||||||
| * ``OTEL_PYTHON_DISABLED_INSTRUMENTATIONS`` | * ``OTEL_PYTHON_DISABLED_INSTRUMENTATIONS`` | ||||||
| 
 | 
 | ||||||
| If set by the user, opentelemetry-instrument will read this environment variable to disable specific instrumentations. | If set by the user, opentelemetry-instrument will read this environment variable to disable specific instrumentations. | ||||||
| e.g OTEL_PYTHON_DISABLED_INSTRUMENTATIONS = "requests,django" | e.g OTEL_PYTHON_DISABLED_INSTRUMENTATIONS="requests,django" | ||||||
|  | 
 | ||||||
|  | * ``OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH`` | ||||||
|  | 
 | ||||||
|  | If set by the user to `patch_all` , opentelemetry instrument will call the gevent monkeypatching method ``patch_all``. | ||||||
|  | This is considered experimental but can be useful to instrument gevent applications. | ||||||
|  | e.g OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH=patch_all | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| Examples | Examples | ||||||
|  |  | ||||||
|  | @ -12,6 +12,8 @@ | ||||||
| # See the License for the specific language governing permissions and | # See the License for the specific language governing permissions and | ||||||
| # limitations under the License. | # limitations under the License. | ||||||
| 
 | 
 | ||||||
|  | from __future__ import annotations | ||||||
|  | 
 | ||||||
| from argparse import REMAINDER, ArgumentParser | from argparse import REMAINDER, ArgumentParser | ||||||
| from logging import getLogger | from logging import getLogger | ||||||
| from os import environ, execl, getcwd | from os import environ, execl, getcwd | ||||||
|  | @ -24,6 +26,9 @@ from opentelemetry.instrumentation.auto_instrumentation._load import ( | ||||||
|     _load_distro, |     _load_distro, | ||||||
|     _load_instrumentors, |     _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.utils import _python_path_without_directory | ||||||
| from opentelemetry.instrumentation.version import __version__ | from opentelemetry.instrumentation.version import __version__ | ||||||
| from opentelemetry.util._importlib_metadata import entry_points | from opentelemetry.util._importlib_metadata import entry_points | ||||||
|  | @ -130,6 +135,30 @@ def initialize(*, swallow_exceptions: bool = True) -> None: | ||||||
|             environ["PYTHONPATH"], dirname(abspath(__file__)), pathsep |             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: |     try: | ||||||
|         distro = _load_distro() |         distro = _load_distro() | ||||||
|         distro.configure() |         distro.configure() | ||||||
|  |  | ||||||
|  | @ -26,3 +26,10 @@ OTEL_PYTHON_CONFIGURATOR = "OTEL_PYTHON_CONFIGURATOR" | ||||||
| """ | """ | ||||||
| .. envvar:: 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 | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| asgiref==3.8.1 | asgiref==3.8.1 | ||||||
| Deprecated==1.2.14 | Deprecated==1.2.14 | ||||||
|  | gevent==25.5.1 | ||||||
| iniconfig==2.0.0 | iniconfig==2.0.0 | ||||||
| packaging==24.0 | packaging==24.0 | ||||||
| pluggy==1.5.0 | pluggy==1.5.0 | ||||||
|  |  | ||||||
|  | @ -72,3 +72,30 @@ class TestInitialize(TestCase): | ||||||
|             ) |             ) | ||||||
| 
 | 
 | ||||||
|         self.assertEqual("inner exception", str(em.exception)) |         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() | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue