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:
Riccardo Magliocchetti 2025-08-29 20:52:35 +02:00 committed by GitHub
parent 86d26ce1b8
commit 5f0d4ff4ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 73 additions and 1 deletions

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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
"""

View File

@ -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

View File

@ -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()