We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Lost visibility into bulk Elasticsearch operations with 0.48.0
With upgrade to ddtrace 0.48.0, we appear to have lost visibility into Elasticsearch bulk operations.

Here's the view in APM for our indexer application that writes to ES. We seem to have lost all visibility. The first screenshot shows that APM istelf is still working, while the second screenshot makes it clear that we lost all visibility into Elasticsearch.

![image](https://user-images.githubusercontent.com/17439030/113631013-cdcb8680-9636-11eb-8699-77dc8d51a54a.png)

![image](https://user-images.githubusercontent.com/17439030/113630896-9fe64200-9636-11eb-90f7-ccdf93a0cbe7.png)

Within a trace, I can see the `http.client.request` to ES, but again, no visibility into ES as a service.


### Which version of dd-trace-py are you using?
0.48.0

### Which version of pip are you using?
18.1

### Which version of the libraries are you using?
```
elasticsearch7==7.12.0
elasticsearch7-dsl==7.2.1
```

### How can we reproduce your problem?
Setup APM on an application that writes to ES with bulk operations.

### What is the result that you get?
No span for the bulk write to ES

### What is the result that you expected?
You should get a span for the bulk write to ES

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `ddtrace/monkey.py`
Content:
```
1 """Patch libraries to be automatically instrumented.
2 
3 It can monkey patch supported standard libraries and third party modules.
4 A patched module will automatically report spans with its default configuration.
5 
6 A library instrumentation can be configured (for instance, to report as another service)
7 using Pin. For that, check its documentation.
8 """
9 import importlib
10 import os
11 import sys
12 import threading
13 from typing import Any
14 from typing import Callable
15 from typing import Dict
16 from typing import List
17 
18 from ddtrace.vendor.wrapt.importer import when_imported
19 
20 from .internal.logger import get_logger
21 from .settings import _config as config
22 from .utils import formats
23 
24 
25 log = get_logger(__name__)
26 
27 # Default set of modules to automatically patch or not
28 PATCH_MODULES = {
29     "asyncio": True,
30     "boto": True,
31     "botocore": True,
32     "bottle": False,
33     "cassandra": True,
34     "celery": True,
35     "consul": True,
36     "django": True,
37     "elasticsearch": True,
38     "algoliasearch": True,
39     "futures": True,
40     "grpc": True,
41     "mongoengine": True,
42     "mysql": True,
43     "mysqldb": True,
44     "pymysql": True,
45     "psycopg": True,
46     "pylibmc": True,
47     "pymemcache": True,
48     "pymongo": True,
49     "redis": True,
50     "rediscluster": True,
51     "requests": True,
52     "sanic": True,
53     "sqlalchemy": False,  # Prefer DB client instrumentation
54     "sqlite3": True,
55     "aiohttp": True,  # requires asyncio (Python 3.4+)
56     "aiopg": True,
57     "aiobotocore": False,
58     "httplib": False,
59     "urllib3": False,
60     "vertica": True,
61     "molten": True,
62     "jinja2": True,
63     "mako": True,
64     "flask": True,
65     "kombu": False,
66     "starlette": True,
67     # Ignore some web framework integrations that might be configured explicitly in code
68     "falcon": False,
69     "pylons": False,
70     "pyramid": False,
71     # Auto-enable logging if the environment variable DD_LOGS_INJECTION is true
72     "logging": config.logs_injection,
73     "pynamodb": True,
74     "pyodbc": True,
75     "fastapi": True,
76     "dogpile_cache": True,
77 }
78 
79 _LOCK = threading.Lock()
80 _PATCHED_MODULES = set()
81 
82 # Modules which are patched on first use
83 # DEV: These modules are patched when the user first imports them, rather than
84 #      explicitly importing and patching them on application startup `ddtrace.patch_all(module=True)`
85 # DEV: This ensures we do not patch a module until it is needed
86 # DEV: <contrib name> => <list of module names that trigger a patch>
87 _PATCH_ON_IMPORT = {
88     "aiohttp": ("aiohttp",),
89     "aiobotocore": ("aiobotocore",),
90     "celery": ("celery",),
91     "flask": ("flask",),
92     "gevent": ("gevent",),
93     "requests": ("requests",),
94     "botocore": ("botocore",),
95     "elasticsearch": (
96         "elasticsearch",
97         "elasticsearch2",
98         "elasticsearch5",
99         "elasticsearch6",
100         "elasticsearch7",
101     ),
102     "pynamodb": ("pynamodb",),
103 }
104 
105 
106 class PatchException(Exception):
107     """Wraps regular `Exception` class when patching modules"""
108 
109     pass
110 
111 
112 class ModuleNotFoundException(PatchException):
113     pass
114 
115 
116 def _on_import_factory(module, raise_errors=True):
117     # type: (str, bool) -> Callable[[Any], None]
118     """Factory to create an import hook for the provided module name"""
119 
120     def on_import(hook):
121         # Import and patch module
122         path = "ddtrace.contrib.%s" % module
123         try:
124             imported_module = importlib.import_module(path)
125         except ImportError:
126             if raise_errors:
127                 raise
128             log.error("failed to import ddtrace module %r when patching on import", path, exc_info=True)
129         else:
130             imported_module.patch()
131 
132     return on_import
133 
134 
135 def patch_all(**patch_modules):
136     # type: (Dict[str, bool]) -> None
137     """Automatically patches all available modules.
138 
139     In addition to ``patch_modules``, an override can be specified via an
140     environment variable, ``DD_TRACE_<module>_ENABLED`` for each module.
141 
142     ``patch_modules`` have the highest precedence for overriding.
143 
144     :param dict patch_modules: Override whether particular modules are patched or not.
145 
146         >>> patch_all(redis=False, cassandra=False)
147     """
148     modules = PATCH_MODULES.copy()
149 
150     # The enabled setting can be overridden by environment variables
151     for module, enabled in modules.items():
152         env_var = "DD_TRACE_%s_ENABLED" % module.upper()
153         if env_var not in os.environ:
154             continue
155 
156         override_enabled = formats.asbool(os.environ[env_var])
157         modules[module] = override_enabled
158 
159     # Arguments take precedence over the environment and the defaults.
160     modules.update(patch_modules)
161 
162     patch(raise_errors=False, **modules)
163 
164 
165 def patch(raise_errors=True, **patch_modules):
166     # type: (bool, Dict[str, bool]) -> None
167     """Patch only a set of given modules.
168 
169     :param bool raise_errors: Raise error if one patch fail.
170     :param dict patch_modules: List of modules to patch.
171 
172         >>> patch(psycopg=True, elasticsearch=True)
173     """
174     modules = [m for (m, should_patch) in patch_modules.items() if should_patch]
175     for module in modules:
176         if module in _PATCH_ON_IMPORT:
177             modules_to_poi = _PATCH_ON_IMPORT[module]
178             for m in modules_to_poi:
179                 # If the module has already been imported then patch immediately
180                 if m in sys.modules:
181                     patch_module(m, raise_errors=raise_errors)
182                 # Otherwise, add a hook to patch when it is imported for the first time
183                 else:
184                     # Use factory to create handler to close over `module` and `raise_errors` values from this loop
185                     when_imported(m)(_on_import_factory(module, raise_errors))
186 
187             # manually add module to patched modules
188             with _LOCK:
189                 _PATCHED_MODULES.add(module)
190         else:
191             patch_module(module, raise_errors=raise_errors)
192 
193     patched_modules = get_patched_modules()
194     log.info(
195         "patched %s/%s modules (%s)",
196         len(patched_modules),
197         len(modules),
198         ",".join(patched_modules),
199     )
200 
201 
202 def patch_module(module, raise_errors=True):
203     # type: (str, bool) -> bool
204     """Patch a single module
205 
206     Returns if the module got properly patched.
207     """
208     try:
209         return _patch_module(module)
210     except ModuleNotFoundException:
211         if raise_errors:
212             raise
213         return False
214     except Exception:
215         if raise_errors:
216             raise
217         log.debug("failed to patch %s", module, exc_info=True)
218         return False
219 
220 
221 def get_patched_modules():
222     # type: () -> List[str]
223     """Get the list of patched modules"""
224     with _LOCK:
225         return sorted(_PATCHED_MODULES)
226 
227 
228 def _patch_module(module):
229     # type: (str) -> bool
230     """_patch_module will attempt to monkey patch the module.
231 
232     Returns if the module got patched.
233     Can also raise errors if it fails.
234     """
235     path = "ddtrace.contrib.%s" % module
236     with _LOCK:
237         if module in _PATCHED_MODULES and module not in _PATCH_ON_IMPORT:
238             log.debug("already patched: %s", path)
239             return False
240 
241         try:
242             imported_module = importlib.import_module(path)
243         except ImportError:
244             # if the import fails, the integration is not available
245             raise PatchException("integration '%s' not available" % path)
246         else:
247             # if patch() is not available in the module, it means
248             # that the library is not installed in the environment
249             if not hasattr(imported_module, "patch"):
250                 raise ModuleNotFoundException("module '%s' not installed" % module)
251 
252             imported_module.patch()  # type: ignore
253             _PATCHED_MODULES.add(module)
254             return True
255
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/ddtrace/monkey.py b/ddtrace/monkey.py
--- a/ddtrace/monkey.py
+++ b/ddtrace/monkey.py
@@ -178,7 +178,8 @@
             for m in modules_to_poi:
                 # If the module has already been imported then patch immediately
                 if m in sys.modules:
-                    patch_module(m, raise_errors=raise_errors)
+                    patch_module(module, raise_errors=raise_errors)
+                    break
                 # Otherwise, add a hook to patch when it is imported for the first time
                 else:
                     # Use factory to create handler to close over `module` and `raise_errors` values from this loop

{"golden_diff": "diff --git a/ddtrace/monkey.py b/ddtrace/monkey.py\n--- a/ddtrace/monkey.py\n+++ b/ddtrace/monkey.py\n@@ -178,7 +178,8 @@\n             for m in modules_to_poi:\n                 # If the module has already been imported then patch immediately\n                 if m in sys.modules:\n-                    patch_module(m, raise_errors=raise_errors)\n+                    patch_module(module, raise_errors=raise_errors)\n+                    break\n                 # Otherwise, add a hook to patch when it is imported for the first time\n                 else:\n                     # Use factory to create handler to close over `module` and `raise_errors` values from this loop\n", "issue": "Lost visibility into bulk Elasticsearch operations with 0.48.0\nWith upgrade to ddtrace 0.48.0, we appear to have lost visibility into Elasticsearch bulk operations.\r\n\r\nHere's the view in APM for our indexer application that writes to ES. We seem to have lost all visibility. The first screenshot shows that APM istelf is still working, while the second screenshot makes it clear that we lost all visibility into Elasticsearch.\r\n\r\n![image](https://user-images.githubusercontent.com/17439030/113631013-cdcb8680-9636-11eb-8699-77dc8d51a54a.png)\r\n\r\n![image](https://user-images.githubusercontent.com/17439030/113630896-9fe64200-9636-11eb-90f7-ccdf93a0cbe7.png)\r\n\r\nWithin a trace, I can see the `http.client.request` to ES, but again, no visibility into ES as a service.\r\n\r\n\r\n### Which version of dd-trace-py are you using?\r\n0.48.0\r\n\r\n### Which version of pip are you using?\r\n18.1\r\n\r\n### Which version of the libraries are you using?\r\n```\r\nelasticsearch7==7.12.0\r\nelasticsearch7-dsl==7.2.1\r\n```\r\n\r\n### How can we reproduce your problem?\r\nSetup APM on an application that writes to ES with bulk operations.\r\n\r\n### What is the result that you get?\r\nNo span for the bulk write to ES\r\n\r\n### What is the result that you expected?\r\nYou should get a span for the bulk write to ES\n", "before_files": [{"content": "\"\"\"Patch libraries to be automatically instrumented.\n\nIt can monkey patch supported standard libraries and third party modules.\nA patched module will automatically report spans with its default configuration.\n\nA library instrumentation can be configured (for instance, to report as another service)\nusing Pin. For that, check its documentation.\n\"\"\"\nimport importlib\nimport os\nimport sys\nimport threading\nfrom typing import Any\nfrom typing import Callable\nfrom typing import Dict\nfrom typing import List\n\nfrom ddtrace.vendor.wrapt.importer import when_imported\n\nfrom .internal.logger import get_logger\nfrom .settings import _config as config\nfrom .utils import formats\n\n\nlog = get_logger(__name__)\n\n# Default set of modules to automatically patch or not\nPATCH_MODULES = {\n    \"asyncio\": True,\n    \"boto\": True,\n    \"botocore\": True,\n    \"bottle\": False,\n    \"cassandra\": True,\n    \"celery\": True,\n    \"consul\": True,\n    \"django\": True,\n    \"elasticsearch\": True,\n    \"algoliasearch\": True,\n    \"futures\": True,\n    \"grpc\": True,\n    \"mongoengine\": True,\n    \"mysql\": True,\n    \"mysqldb\": True,\n    \"pymysql\": True,\n    \"psycopg\": True,\n    \"pylibmc\": True,\n    \"pymemcache\": True,\n    \"pymongo\": True,\n    \"redis\": True,\n    \"rediscluster\": True,\n    \"requests\": True,\n    \"sanic\": True,\n    \"sqlalchemy\": False,  # Prefer DB client instrumentation\n    \"sqlite3\": True,\n    \"aiohttp\": True,  # requires asyncio (Python 3.4+)\n    \"aiopg\": True,\n    \"aiobotocore\": False,\n    \"httplib\": False,\n    \"urllib3\": False,\n    \"vertica\": True,\n    \"molten\": True,\n    \"jinja2\": True,\n    \"mako\": True,\n    \"flask\": True,\n    \"kombu\": False,\n    \"starlette\": True,\n    # Ignore some web framework integrations that might be configured explicitly in code\n    \"falcon\": False,\n    \"pylons\": False,\n    \"pyramid\": False,\n    # Auto-enable logging if the environment variable DD_LOGS_INJECTION is true\n    \"logging\": config.logs_injection,\n    \"pynamodb\": True,\n    \"pyodbc\": True,\n    \"fastapi\": True,\n    \"dogpile_cache\": True,\n}\n\n_LOCK = threading.Lock()\n_PATCHED_MODULES = set()\n\n# Modules which are patched on first use\n# DEV: These modules are patched when the user first imports them, rather than\n#      explicitly importing and patching them on application startup `ddtrace.patch_all(module=True)`\n# DEV: This ensures we do not patch a module until it is needed\n# DEV: <contrib name> => <list of module names that trigger a patch>\n_PATCH_ON_IMPORT = {\n    \"aiohttp\": (\"aiohttp\",),\n    \"aiobotocore\": (\"aiobotocore\",),\n    \"celery\": (\"celery\",),\n    \"flask\": (\"flask\",),\n    \"gevent\": (\"gevent\",),\n    \"requests\": (\"requests\",),\n    \"botocore\": (\"botocore\",),\n    \"elasticsearch\": (\n        \"elasticsearch\",\n        \"elasticsearch2\",\n        \"elasticsearch5\",\n        \"elasticsearch6\",\n        \"elasticsearch7\",\n    ),\n    \"pynamodb\": (\"pynamodb\",),\n}\n\n\nclass PatchException(Exception):\n    \"\"\"Wraps regular `Exception` class when patching modules\"\"\"\n\n    pass\n\n\nclass ModuleNotFoundException(PatchException):\n    pass\n\n\ndef _on_import_factory(module, raise_errors=True):\n    # type: (str, bool) -> Callable[[Any], None]\n    \"\"\"Factory to create an import hook for the provided module name\"\"\"\n\n    def on_import(hook):\n        # Import and patch module\n        path = \"ddtrace.contrib.%s\" % module\n        try:\n            imported_module = importlib.import_module(path)\n        except ImportError:\n            if raise_errors:\n                raise\n            log.error(\"failed to import ddtrace module %r when patching on import\", path, exc_info=True)\n        else:\n            imported_module.patch()\n\n    return on_import\n\n\ndef patch_all(**patch_modules):\n    # type: (Dict[str, bool]) -> None\n    \"\"\"Automatically patches all available modules.\n\n    In addition to ``patch_modules``, an override can be specified via an\n    environment variable, ``DD_TRACE_<module>_ENABLED`` for each module.\n\n    ``patch_modules`` have the highest precedence for overriding.\n\n    :param dict patch_modules: Override whether particular modules are patched or not.\n\n        >>> patch_all(redis=False, cassandra=False)\n    \"\"\"\n    modules = PATCH_MODULES.copy()\n\n    # The enabled setting can be overridden by environment variables\n    for module, enabled in modules.items():\n        env_var = \"DD_TRACE_%s_ENABLED\" % module.upper()\n        if env_var not in os.environ:\n            continue\n\n        override_enabled = formats.asbool(os.environ[env_var])\n        modules[module] = override_enabled\n\n    # Arguments take precedence over the environment and the defaults.\n    modules.update(patch_modules)\n\n    patch(raise_errors=False, **modules)\n\n\ndef patch(raise_errors=True, **patch_modules):\n    # type: (bool, Dict[str, bool]) -> None\n    \"\"\"Patch only a set of given modules.\n\n    :param bool raise_errors: Raise error if one patch fail.\n    :param dict patch_modules: List of modules to patch.\n\n        >>> patch(psycopg=True, elasticsearch=True)\n    \"\"\"\n    modules = [m for (m, should_patch) in patch_modules.items() if should_patch]\n    for module in modules:\n        if module in _PATCH_ON_IMPORT:\n            modules_to_poi = _PATCH_ON_IMPORT[module]\n            for m in modules_to_poi:\n                # If the module has already been imported then patch immediately\n                if m in sys.modules:\n                    patch_module(m, raise_errors=raise_errors)\n                # Otherwise, add a hook to patch when it is imported for the first time\n                else:\n                    # Use factory to create handler to close over `module` and `raise_errors` values from this loop\n                    when_imported(m)(_on_import_factory(module, raise_errors))\n\n            # manually add module to patched modules\n            with _LOCK:\n                _PATCHED_MODULES.add(module)\n        else:\n            patch_module(module, raise_errors=raise_errors)\n\n    patched_modules = get_patched_modules()\n    log.info(\n        \"patched %s/%s modules (%s)\",\n        len(patched_modules),\n        len(modules),\n        \",\".join(patched_modules),\n    )\n\n\ndef patch_module(module, raise_errors=True):\n    # type: (str, bool) -> bool\n    \"\"\"Patch a single module\n\n    Returns if the module got properly patched.\n    \"\"\"\n    try:\n        return _patch_module(module)\n    except ModuleNotFoundException:\n        if raise_errors:\n            raise\n        return False\n    except Exception:\n        if raise_errors:\n            raise\n        log.debug(\"failed to patch %s\", module, exc_info=True)\n        return False\n\n\ndef get_patched_modules():\n    # type: () -> List[str]\n    \"\"\"Get the list of patched modules\"\"\"\n    with _LOCK:\n        return sorted(_PATCHED_MODULES)\n\n\ndef _patch_module(module):\n    # type: (str) -> bool\n    \"\"\"_patch_module will attempt to monkey patch the module.\n\n    Returns if the module got patched.\n    Can also raise errors if it fails.\n    \"\"\"\n    path = \"ddtrace.contrib.%s\" % module\n    with _LOCK:\n        if module in _PATCHED_MODULES and module not in _PATCH_ON_IMPORT:\n            log.debug(\"already patched: %s\", path)\n            return False\n\n        try:\n            imported_module = importlib.import_module(path)\n        except ImportError:\n            # if the import fails, the integration is not available\n            raise PatchException(\"integration '%s' not available\" % path)\n        else:\n            # if patch() is not available in the module, it means\n            # that the library is not installed in the environment\n            if not hasattr(imported_module, \"patch\"):\n                raise ModuleNotFoundException(\"module '%s' not installed\" % module)\n\n            imported_module.patch()  # type: ignore\n            _PATCHED_MODULES.add(module)\n            return True\n", "path": "ddtrace/monkey.py"}], "after_files": [{"content": "\"\"\"Patch libraries to be automatically instrumented.\n\nIt can monkey patch supported standard libraries and third party modules.\nA patched module will automatically report spans with its default configuration.\n\nA library instrumentation can be configured (for instance, to report as another service)\nusing Pin. For that, check its documentation.\n\"\"\"\nimport importlib\nimport os\nimport sys\nimport threading\nfrom typing import Any\nfrom typing import Callable\nfrom typing import Dict\nfrom typing import List\n\nfrom ddtrace.vendor.wrapt.importer import when_imported\n\nfrom .internal.logger import get_logger\nfrom .settings import _config as config\nfrom .utils import formats\n\n\nlog = get_logger(__name__)\n\n# Default set of modules to automatically patch or not\nPATCH_MODULES = {\n    \"asyncio\": True,\n    \"boto\": True,\n    \"botocore\": True,\n    \"bottle\": False,\n    \"cassandra\": True,\n    \"celery\": True,\n    \"consul\": True,\n    \"django\": True,\n    \"elasticsearch\": True,\n    \"algoliasearch\": True,\n    \"futures\": True,\n    \"grpc\": True,\n    \"mongoengine\": True,\n    \"mysql\": True,\n    \"mysqldb\": True,\n    \"pymysql\": True,\n    \"psycopg\": True,\n    \"pylibmc\": True,\n    \"pymemcache\": True,\n    \"pymongo\": True,\n    \"redis\": True,\n    \"rediscluster\": True,\n    \"requests\": True,\n    \"sanic\": True,\n    \"sqlalchemy\": False,  # Prefer DB client instrumentation\n    \"sqlite3\": True,\n    \"aiohttp\": True,  # requires asyncio (Python 3.4+)\n    \"aiopg\": True,\n    \"aiobotocore\": False,\n    \"httplib\": False,\n    \"urllib3\": False,\n    \"vertica\": True,\n    \"molten\": True,\n    \"jinja2\": True,\n    \"mako\": True,\n    \"flask\": True,\n    \"kombu\": False,\n    \"starlette\": True,\n    # Ignore some web framework integrations that might be configured explicitly in code\n    \"falcon\": False,\n    \"pylons\": False,\n    \"pyramid\": False,\n    # Auto-enable logging if the environment variable DD_LOGS_INJECTION is true\n    \"logging\": config.logs_injection,\n    \"pynamodb\": True,\n    \"pyodbc\": True,\n    \"fastapi\": True,\n    \"dogpile_cache\": True,\n}\n\n_LOCK = threading.Lock()\n_PATCHED_MODULES = set()\n\n# Modules which are patched on first use\n# DEV: These modules are patched when the user first imports them, rather than\n#      explicitly importing and patching them on application startup `ddtrace.patch_all(module=True)`\n# DEV: This ensures we do not patch a module until it is needed\n# DEV: <contrib name> => <list of module names that trigger a patch>\n_PATCH_ON_IMPORT = {\n    \"aiohttp\": (\"aiohttp\",),\n    \"aiobotocore\": (\"aiobotocore\",),\n    \"celery\": (\"celery\",),\n    \"flask\": (\"flask\",),\n    \"gevent\": (\"gevent\",),\n    \"requests\": (\"requests\",),\n    \"botocore\": (\"botocore\",),\n    \"elasticsearch\": (\n        \"elasticsearch\",\n        \"elasticsearch2\",\n        \"elasticsearch5\",\n        \"elasticsearch6\",\n        \"elasticsearch7\",\n    ),\n    \"pynamodb\": (\"pynamodb\",),\n}\n\n\nclass PatchException(Exception):\n    \"\"\"Wraps regular `Exception` class when patching modules\"\"\"\n\n    pass\n\n\nclass ModuleNotFoundException(PatchException):\n    pass\n\n\ndef _on_import_factory(module, raise_errors=True):\n    # type: (str, bool) -> Callable[[Any], None]\n    \"\"\"Factory to create an import hook for the provided module name\"\"\"\n\n    def on_import(hook):\n        # Import and patch module\n        path = \"ddtrace.contrib.%s\" % module\n        try:\n            imported_module = importlib.import_module(path)\n        except ImportError:\n            if raise_errors:\n                raise\n            log.error(\"failed to import ddtrace module %r when patching on import\", path, exc_info=True)\n        else:\n            imported_module.patch()\n\n    return on_import\n\n\ndef patch_all(**patch_modules):\n    # type: (Dict[str, bool]) -> None\n    \"\"\"Automatically patches all available modules.\n\n    In addition to ``patch_modules``, an override can be specified via an\n    environment variable, ``DD_TRACE_<module>_ENABLED`` for each module.\n\n    ``patch_modules`` have the highest precedence for overriding.\n\n    :param dict patch_modules: Override whether particular modules are patched or not.\n\n        >>> patch_all(redis=False, cassandra=False)\n    \"\"\"\n    modules = PATCH_MODULES.copy()\n\n    # The enabled setting can be overridden by environment variables\n    for module, enabled in modules.items():\n        env_var = \"DD_TRACE_%s_ENABLED\" % module.upper()\n        if env_var not in os.environ:\n            continue\n\n        override_enabled = formats.asbool(os.environ[env_var])\n        modules[module] = override_enabled\n\n    # Arguments take precedence over the environment and the defaults.\n    modules.update(patch_modules)\n\n    patch(raise_errors=False, **modules)\n\n\ndef patch(raise_errors=True, **patch_modules):\n    # type: (bool, Dict[str, bool]) -> None\n    \"\"\"Patch only a set of given modules.\n\n    :param bool raise_errors: Raise error if one patch fail.\n    :param dict patch_modules: List of modules to patch.\n\n        >>> patch(psycopg=True, elasticsearch=True)\n    \"\"\"\n    modules = [m for (m, should_patch) in patch_modules.items() if should_patch]\n    for module in modules:\n        if module in _PATCH_ON_IMPORT:\n            modules_to_poi = _PATCH_ON_IMPORT[module]\n            for m in modules_to_poi:\n                # If the module has already been imported then patch immediately\n                if m in sys.modules:\n                    patch_module(module, raise_errors=raise_errors)\n                    break\n                # Otherwise, add a hook to patch when it is imported for the first time\n                else:\n                    # Use factory to create handler to close over `module` and `raise_errors` values from this loop\n                    when_imported(m)(_on_import_factory(module, raise_errors))\n\n            # manually add module to patched modules\n            with _LOCK:\n                _PATCHED_MODULES.add(module)\n        else:\n            patch_module(module, raise_errors=raise_errors)\n\n    patched_modules = get_patched_modules()\n    log.info(\n        \"patched %s/%s modules (%s)\",\n        len(patched_modules),\n        len(modules),\n        \",\".join(patched_modules),\n    )\n\n\ndef patch_module(module, raise_errors=True):\n    # type: (str, bool) -> bool\n    \"\"\"Patch a single module\n\n    Returns if the module got properly patched.\n    \"\"\"\n    try:\n        return _patch_module(module)\n    except ModuleNotFoundException:\n        if raise_errors:\n            raise\n        return False\n    except Exception:\n        if raise_errors:\n            raise\n        log.debug(\"failed to patch %s\", module, exc_info=True)\n        return False\n\n\ndef get_patched_modules():\n    # type: () -> List[str]\n    \"\"\"Get the list of patched modules\"\"\"\n    with _LOCK:\n        return sorted(_PATCHED_MODULES)\n\n\ndef _patch_module(module):\n    # type: (str) -> bool\n    \"\"\"_patch_module will attempt to monkey patch the module.\n\n    Returns if the module got patched.\n    Can also raise errors if it fails.\n    \"\"\"\n    path = \"ddtrace.contrib.%s\" % module\n    with _LOCK:\n        if module in _PATCHED_MODULES and module not in _PATCH_ON_IMPORT:\n            log.debug(\"already patched: %s\", path)\n            return False\n\n        try:\n            imported_module = importlib.import_module(path)\n        except ImportError:\n            # if the import fails, the integration is not available\n            raise PatchException(\"integration '%s' not available\" % path)\n        else:\n            # if patch() is not available in the module, it means\n            # that the library is not installed in the environment\n            if not hasattr(imported_module, \"patch\"):\n                raise ModuleNotFoundException(\"module '%s' not installed\" % module)\n\n            imported_module.patch()  # type: ignore\n            _PATCHED_MODULES.add(module)\n            return True\n", "path": "ddtrace/monkey.py"}]}