We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Misleading error message when patching boto3 instead of boto2

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

0.41.0

### How can we reproduce your problem?

This logic of only exporting the `patch` method if there are no missing modules:

https://github.com/DataDog/dd-trace-py/blob/20ca41bf398f9dcd753cf40943de78f124ec9516/ddtrace/contrib/boto/__init__.py#L18-L24


causes this code:

https://github.com/DataDog/dd-trace-py/blob/20ca41bf398f9dcd753cf40943de78f124ec9516/ddtrace/monkey.py#L202

to fail with this misleading error:
```
AttributeError: module 'ddtrace.contrib.boto' has no attribute 'patch'
```

### What is the result that you expected?

A message that doesn't make me think something is wrong with my configuration.


--- 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 
14 from ddtrace.vendor.wrapt.importer import when_imported
15 
16 from .internal.logger import get_logger
17 from .settings import config
18 from .utils import formats
19 
20 
21 log = get_logger(__name__)
22 
23 # Default set of modules to automatically patch or not
24 PATCH_MODULES = {
25     "asyncio": False,
26     "boto": True,
27     "botocore": True,
28     "bottle": False,
29     "cassandra": True,
30     "celery": True,
31     "consul": True,
32     "django": True,
33     "elasticsearch": True,
34     "algoliasearch": True,
35     "futures": False,  # experimental propagation
36     "grpc": True,
37     "mongoengine": True,
38     "mysql": True,
39     "mysqldb": True,
40     "pymysql": True,
41     "psycopg": True,
42     "pylibmc": True,
43     "pymemcache": True,
44     "pymongo": True,
45     "redis": True,
46     "rediscluster": True,
47     "requests": True,
48     "sanic": True,
49     "sqlalchemy": False,  # Prefer DB client instrumentation
50     "sqlite3": True,
51     "aiohttp": True,  # requires asyncio (Python 3.4+)
52     "aiopg": True,
53     "aiobotocore": False,
54     "httplib": False,
55     "vertica": True,
56     "molten": True,
57     "jinja2": True,
58     "mako": True,
59     "flask": True,
60     "kombu": False,
61     # Ignore some web framework integrations that might be configured explicitly in code
62     "falcon": False,
63     "pylons": False,
64     "pyramid": False,
65     # Auto-enable logging if the environment variable DD_LOGS_INJECTION is true
66     "logging": config.logs_injection,
67 }
68 
69 _LOCK = threading.Lock()
70 _PATCHED_MODULES = set()
71 
72 # Modules which are patched on first use
73 # DEV: These modules are patched when the user first imports them, rather than
74 #      explicitly importing and patching them on application startup `ddtrace.patch_all(module=True)`
75 # DEV: This ensures we do not patch a module until it is needed
76 # DEV: <contrib name> => <list of module names that trigger a patch>
77 _PATCH_ON_IMPORT = {
78     "aiohttp": ("aiohttp",),
79     "aiobotocore": ("aiobotocore",),
80     "celery": ("celery",),
81     "flask": ("flask, "),
82     "gevent": ("gevent",),
83     "requests": ("requests",),
84     "botocore": ("botocore",),
85     "elasticsearch": ("elasticsearch",),
86 }
87 
88 
89 class PatchException(Exception):
90     """Wraps regular `Exception` class when patching modules"""
91 
92     pass
93 
94 
95 def _on_import_factory(module, raise_errors=True):
96     """Factory to create an import hook for the provided module name"""
97 
98     def on_import(hook):
99         # Import and patch module
100         path = "ddtrace.contrib.%s" % module
101         imported_module = importlib.import_module(path)
102         imported_module.patch()
103 
104     return on_import
105 
106 
107 def patch_all(**patch_modules):
108     """Automatically patches all available modules.
109 
110     In addition to ``patch_modules``, an override can be specified via an
111     environment variable, ``DD_TRACE_<module>_ENABLED`` for each module.
112 
113     ``patch_modules`` have the highest precedence for overriding.
114 
115     :param dict patch_modules: Override whether particular modules are patched or not.
116 
117         >>> patch_all(redis=False, cassandra=False)
118     """
119     modules = PATCH_MODULES.copy()
120 
121     # The enabled setting can be overridden by environment variables
122     for module, enabled in modules.items():
123         env_var = "DD_TRACE_%s_ENABLED" % module.upper()
124         if env_var not in os.environ:
125             continue
126 
127         override_enabled = formats.asbool(os.environ[env_var])
128         modules[module] = override_enabled
129 
130     # Arguments take precedence over the environment and the defaults.
131     modules.update(patch_modules)
132 
133     patch(raise_errors=False, **modules)
134 
135 
136 def patch(raise_errors=True, **patch_modules):
137     """Patch only a set of given modules.
138 
139     :param bool raise_errors: Raise error if one patch fail.
140     :param dict patch_modules: List of modules to patch.
141 
142         >>> patch(psycopg=True, elasticsearch=True)
143     """
144     modules = [m for (m, should_patch) in patch_modules.items() if should_patch]
145     for module in modules:
146         if module in _PATCH_ON_IMPORT:
147             # If the module has already been imported then patch immediately
148             if module in sys.modules:
149                 patch_module(module, raise_errors=raise_errors)
150 
151             # Otherwise, add a hook to patch when it is imported for the first time
152             else:
153                 # Use factory to create handler to close over `module` and `raise_errors` values from this loop
154                 when_imported(module)(_on_import_factory(module, raise_errors))
155 
156                 # manually add module to patched modules
157                 with _LOCK:
158                     _PATCHED_MODULES.add(module)
159         else:
160             patch_module(module, raise_errors=raise_errors)
161 
162     patched_modules = get_patched_modules()
163     log.info(
164         "patched %s/%s modules (%s)", len(patched_modules), len(modules), ",".join(patched_modules),
165     )
166 
167 
168 def patch_module(module, raise_errors=True):
169     """Patch a single module
170 
171     Returns if the module got properly patched.
172     """
173     try:
174         return _patch_module(module)
175     except Exception:
176         if raise_errors:
177             raise
178         log.debug("failed to patch %s", module, exc_info=True)
179         return False
180 
181 
182 def get_patched_modules():
183     """Get the list of patched modules"""
184     with _LOCK:
185         return sorted(_PATCHED_MODULES)
186 
187 
188 def _patch_module(module):
189     """_patch_module will attempt to monkey patch the module.
190 
191     Returns if the module got patched.
192     Can also raise errors if it fails.
193     """
194     path = "ddtrace.contrib.%s" % module
195     with _LOCK:
196         if module in _PATCHED_MODULES and module not in _PATCH_ON_IMPORT:
197             log.debug("already patched: %s", path)
198             return False
199 
200         try:
201             imported_module = importlib.import_module(path)
202             imported_module.patch()
203         except ImportError:
204             # if the import fails, the integration is not available
205             raise PatchException("integration '%s' not available" % path)
206         except AttributeError:
207             # if patch() is not available in the module, it means
208             # that the library is not installed in the environment
209             raise PatchException("module '%s' not installed" % module)
210 
211         _PATCHED_MODULES.add(module)
212         return True
213
```

Path: `ddtrace/internal/runtime/container.py`
Content:
```
1 import re
2 
3 from ..logger import get_logger
4 
5 log = get_logger(__name__)
6 
7 
8 class CGroupInfo(object):
9     """
10     CGroup class for container information parsed from a group cgroup file
11     """
12 
13     __slots__ = ("id", "groups", "path", "container_id", "controllers", "pod_id")
14 
15     UUID_SOURCE_PATTERN = r"[0-9a-f]{8}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{12}"
16     CONTAINER_SOURCE_PATTERN = r"[0-9a-f]{64}"
17 
18     LINE_RE = re.compile(r"^(\d+):([^:]*):(.+)$")
19     POD_RE = re.compile(r"pod({0})(?:\.slice)?$".format(UUID_SOURCE_PATTERN))
20     CONTAINER_RE = re.compile(r"({0}|{1})(?:\.scope)?$".format(UUID_SOURCE_PATTERN, CONTAINER_SOURCE_PATTERN))
21 
22     def __init__(self, **kwargs):
23         # Initialize all attributes in __slots__ to `None`
24         # DEV: Otherwise we'll get `AttributeError` when trying to access if they are unset
25         for attr in self.__slots__:
26             setattr(self, attr, kwargs.get(attr))
27 
28     @classmethod
29     def from_line(cls, line):
30         """
31         Parse a new :class:`CGroupInfo` from the provided line
32 
33         :param line: A line from a cgroup file (e.g. /proc/self/cgroup) to parse information from
34         :type line: str
35         :returns: A :class:`CGroupInfo` object with all parsed data, if the line is valid, otherwise `None`
36         :rtype: :class:`CGroupInfo` | None
37 
38         """
39         # Clean up the line
40         line = line.strip()
41 
42         # Ensure the line is valid
43         match = cls.LINE_RE.match(line)
44         if not match:
45             return None
46 
47         # Create our new `CGroupInfo` and set attributes from the line
48         info = cls()
49         info.id, info.groups, info.path = match.groups()
50 
51         # Parse the controllers from the groups
52         info.controllers = [c.strip() for c in info.groups.split(",") if c.strip()]
53 
54         # Break up the path to grab container_id and pod_id if available
55         # e.g. /docker/<container_id>
56         # e.g. /kubepods/test/pod<pod_id>/<container_id>
57         parts = [p for p in info.path.split("/")]
58 
59         # Grab the container id from the path if a valid id is present
60         if len(parts):
61             match = cls.CONTAINER_RE.match(parts.pop())
62             if match:
63                 info.container_id = match.group(1)
64 
65         # Grab the pod id from the path if a valid id is present
66         if len(parts):
67             match = cls.POD_RE.match(parts.pop())
68             if match:
69                 info.pod_id = match.group(1)
70 
71         return info
72 
73     def __str__(self):
74         return self.__repr__()
75 
76     def __repr__(self):
77         return "{}(id={!r}, groups={!r}, path={!r}, container_id={!r}, controllers={!r}, pod_id={!r})".format(
78             self.__class__.__name__, self.id, self.groups, self.path, self.container_id, self.controllers, self.pod_id,
79         )
80 
81 
82 def get_container_info(pid="self"):
83     """
84     Helper to fetch the current container id, if we are running in a container
85 
86     We will parse `/proc/{pid}/cgroup` to determine our container id.
87 
88     The results of calling this function are cached
89 
90     :param pid: The pid of the cgroup file to parse (default: 'self')
91     :type pid: str | int
92     :returns: The cgroup file info if found, or else None
93     :rtype: :class:`CGroupInfo` | None
94     """
95     try:
96         cgroup_file = "/proc/{0}/cgroup".format(pid)
97         with open(cgroup_file, mode="r") as fp:
98             for line in fp:
99                 info = CGroupInfo.from_line(line)
100                 if info and info.container_id:
101                     return info
102     except Exception:
103         log.debug("Failed to parse cgroup file for pid %r", pid, exc_info=True)
104 
105     return None
106
```
--- 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/internal/runtime/container.py b/ddtrace/internal/runtime/container.py
--- a/ddtrace/internal/runtime/container.py
+++ b/ddtrace/internal/runtime/container.py
@@ -1,3 +1,4 @@
+import os
 import re
 
 from ..logger import get_logger
@@ -92,8 +93,15 @@
     :returns: The cgroup file info if found, or else None
     :rtype: :class:`CGroupInfo` | None
     """
+
+    cgroup_file = "/proc/{0}/cgroup".format(pid)
+
+    if not os.path.exists(cgroup_file):
+        # If the cgroup file does not exist then this is likely not a container
+        # which is a valid use-case so pass.
+        return
+
     try:
-        cgroup_file = "/proc/{0}/cgroup".format(pid)
         with open(cgroup_file, mode="r") as fp:
             for line in fp:
                 info = CGroupInfo.from_line(line)
@@ -101,5 +109,3 @@
                     return info
     except Exception:
         log.debug("Failed to parse cgroup file for pid %r", pid, exc_info=True)
-
-    return None
diff --git a/ddtrace/monkey.py b/ddtrace/monkey.py
--- a/ddtrace/monkey.py
+++ b/ddtrace/monkey.py
@@ -92,6 +92,10 @@
     pass
 
 
+class ModuleNotFoundException(PatchException):
+    pass
+
+
 def _on_import_factory(module, raise_errors=True):
     """Factory to create an import hook for the provided module name"""
 
@@ -172,6 +176,10 @@
     """
     try:
         return _patch_module(module)
+    except ModuleNotFoundException:
+        if raise_errors:
+            raise
+        return False
     except Exception:
         if raise_errors:
             raise
@@ -199,14 +207,15 @@
 
         try:
             imported_module = importlib.import_module(path)
-            imported_module.patch()
         except ImportError:
             # if the import fails, the integration is not available
             raise PatchException("integration '%s' not available" % path)
-        except AttributeError:
+        else:
             # if patch() is not available in the module, it means
             # that the library is not installed in the environment
-            raise PatchException("module '%s' not installed" % module)
+            if not hasattr(imported_module, "patch"):
+                raise ModuleNotFoundException("module '%s' not installed" % module)
 
-        _PATCHED_MODULES.add(module)
-        return True
+            imported_module.patch()
+            _PATCHED_MODULES.add(module)
+            return True

{"golden_diff": "diff --git a/ddtrace/internal/runtime/container.py b/ddtrace/internal/runtime/container.py\n--- a/ddtrace/internal/runtime/container.py\n+++ b/ddtrace/internal/runtime/container.py\n@@ -1,3 +1,4 @@\n+import os\n import re\n \n from ..logger import get_logger\n@@ -92,8 +93,15 @@\n     :returns: The cgroup file info if found, or else None\n     :rtype: :class:`CGroupInfo` | None\n     \"\"\"\n+\n+    cgroup_file = \"/proc/{0}/cgroup\".format(pid)\n+\n+    if not os.path.exists(cgroup_file):\n+        # If the cgroup file does not exist then this is likely not a container\n+        # which is a valid use-case so pass.\n+        return\n+\n     try:\n-        cgroup_file = \"/proc/{0}/cgroup\".format(pid)\n         with open(cgroup_file, mode=\"r\") as fp:\n             for line in fp:\n                 info = CGroupInfo.from_line(line)\n@@ -101,5 +109,3 @@\n                     return info\n     except Exception:\n         log.debug(\"Failed to parse cgroup file for pid %r\", pid, exc_info=True)\n-\n-    return None\ndiff --git a/ddtrace/monkey.py b/ddtrace/monkey.py\n--- a/ddtrace/monkey.py\n+++ b/ddtrace/monkey.py\n@@ -92,6 +92,10 @@\n     pass\n \n \n+class ModuleNotFoundException(PatchException):\n+    pass\n+\n+\n def _on_import_factory(module, raise_errors=True):\n     \"\"\"Factory to create an import hook for the provided module name\"\"\"\n \n@@ -172,6 +176,10 @@\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@@ -199,14 +207,15 @@\n \n         try:\n             imported_module = importlib.import_module(path)\n-            imported_module.patch()\n         except ImportError:\n             # if the import fails, the integration is not available\n             raise PatchException(\"integration '%s' not available\" % path)\n-        except AttributeError:\n+        else:\n             # if patch() is not available in the module, it means\n             # that the library is not installed in the environment\n-            raise PatchException(\"module '%s' not installed\" % module)\n+            if not hasattr(imported_module, \"patch\"):\n+                raise ModuleNotFoundException(\"module '%s' not installed\" % module)\n \n-        _PATCHED_MODULES.add(module)\n-        return True\n+            imported_module.patch()\n+            _PATCHED_MODULES.add(module)\n+            return True\n", "issue": "Misleading error message when patching boto3 instead of boto2\n\r\n### Which version of dd-trace-py are you using?\r\n\r\n0.41.0\r\n\r\n### How can we reproduce your problem?\r\n\r\nThis logic of only exporting the `patch` method if there are no missing modules:\r\n\r\nhttps://github.com/DataDog/dd-trace-py/blob/20ca41bf398f9dcd753cf40943de78f124ec9516/ddtrace/contrib/boto/__init__.py#L18-L24\r\n\r\n\r\ncauses this code:\r\n\r\nhttps://github.com/DataDog/dd-trace-py/blob/20ca41bf398f9dcd753cf40943de78f124ec9516/ddtrace/monkey.py#L202\r\n\r\nto fail with this misleading error:\r\n```\r\nAttributeError: module 'ddtrace.contrib.boto' has no attribute 'patch'\r\n```\r\n\r\n### What is the result that you expected?\r\n\r\nA message that doesn't make me think something is wrong with my configuration.\r\n\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\n\nfrom ddtrace.vendor.wrapt.importer import when_imported\n\nfrom .internal.logger import get_logger\nfrom .settings import 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\": False,\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\": False,  # experimental propagation\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    \"vertica\": True,\n    \"molten\": True,\n    \"jinja2\": True,\n    \"mako\": True,\n    \"flask\": True,\n    \"kombu\": False,\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}\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\": (\"elasticsearch\",),\n}\n\n\nclass PatchException(Exception):\n    \"\"\"Wraps regular `Exception` class when patching modules\"\"\"\n\n    pass\n\n\ndef _on_import_factory(module, raise_errors=True):\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        imported_module = importlib.import_module(path)\n        imported_module.patch()\n\n    return on_import\n\n\ndef patch_all(**patch_modules):\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    \"\"\"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            # If the module has already been imported then patch immediately\n            if module in sys.modules:\n                patch_module(module, raise_errors=raise_errors)\n\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(module)(_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)\", len(patched_modules), len(modules), \",\".join(patched_modules),\n    )\n\n\ndef patch_module(module, raise_errors=True):\n    \"\"\"Patch a single module\n\n    Returns if the module got properly patched.\n    \"\"\"\n    try:\n        return _patch_module(module)\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    \"\"\"Get the list of patched modules\"\"\"\n    with _LOCK:\n        return sorted(_PATCHED_MODULES)\n\n\ndef _patch_module(module):\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            imported_module.patch()\n        except ImportError:\n            # if the import fails, the integration is not available\n            raise PatchException(\"integration '%s' not available\" % path)\n        except AttributeError:\n            # if patch() is not available in the module, it means\n            # that the library is not installed in the environment\n            raise PatchException(\"module '%s' not installed\" % module)\n\n        _PATCHED_MODULES.add(module)\n        return True\n", "path": "ddtrace/monkey.py"}, {"content": "import re\n\nfrom ..logger import get_logger\n\nlog = get_logger(__name__)\n\n\nclass CGroupInfo(object):\n    \"\"\"\n    CGroup class for container information parsed from a group cgroup file\n    \"\"\"\n\n    __slots__ = (\"id\", \"groups\", \"path\", \"container_id\", \"controllers\", \"pod_id\")\n\n    UUID_SOURCE_PATTERN = r\"[0-9a-f]{8}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{12}\"\n    CONTAINER_SOURCE_PATTERN = r\"[0-9a-f]{64}\"\n\n    LINE_RE = re.compile(r\"^(\\d+):([^:]*):(.+)$\")\n    POD_RE = re.compile(r\"pod({0})(?:\\.slice)?$\".format(UUID_SOURCE_PATTERN))\n    CONTAINER_RE = re.compile(r\"({0}|{1})(?:\\.scope)?$\".format(UUID_SOURCE_PATTERN, CONTAINER_SOURCE_PATTERN))\n\n    def __init__(self, **kwargs):\n        # Initialize all attributes in __slots__ to `None`\n        # DEV: Otherwise we'll get `AttributeError` when trying to access if they are unset\n        for attr in self.__slots__:\n            setattr(self, attr, kwargs.get(attr))\n\n    @classmethod\n    def from_line(cls, line):\n        \"\"\"\n        Parse a new :class:`CGroupInfo` from the provided line\n\n        :param line: A line from a cgroup file (e.g. /proc/self/cgroup) to parse information from\n        :type line: str\n        :returns: A :class:`CGroupInfo` object with all parsed data, if the line is valid, otherwise `None`\n        :rtype: :class:`CGroupInfo` | None\n\n        \"\"\"\n        # Clean up the line\n        line = line.strip()\n\n        # Ensure the line is valid\n        match = cls.LINE_RE.match(line)\n        if not match:\n            return None\n\n        # Create our new `CGroupInfo` and set attributes from the line\n        info = cls()\n        info.id, info.groups, info.path = match.groups()\n\n        # Parse the controllers from the groups\n        info.controllers = [c.strip() for c in info.groups.split(\",\") if c.strip()]\n\n        # Break up the path to grab container_id and pod_id if available\n        # e.g. /docker/<container_id>\n        # e.g. /kubepods/test/pod<pod_id>/<container_id>\n        parts = [p for p in info.path.split(\"/\")]\n\n        # Grab the container id from the path if a valid id is present\n        if len(parts):\n            match = cls.CONTAINER_RE.match(parts.pop())\n            if match:\n                info.container_id = match.group(1)\n\n        # Grab the pod id from the path if a valid id is present\n        if len(parts):\n            match = cls.POD_RE.match(parts.pop())\n            if match:\n                info.pod_id = match.group(1)\n\n        return info\n\n    def __str__(self):\n        return self.__repr__()\n\n    def __repr__(self):\n        return \"{}(id={!r}, groups={!r}, path={!r}, container_id={!r}, controllers={!r}, pod_id={!r})\".format(\n            self.__class__.__name__, self.id, self.groups, self.path, self.container_id, self.controllers, self.pod_id,\n        )\n\n\ndef get_container_info(pid=\"self\"):\n    \"\"\"\n    Helper to fetch the current container id, if we are running in a container\n\n    We will parse `/proc/{pid}/cgroup` to determine our container id.\n\n    The results of calling this function are cached\n\n    :param pid: The pid of the cgroup file to parse (default: 'self')\n    :type pid: str | int\n    :returns: The cgroup file info if found, or else None\n    :rtype: :class:`CGroupInfo` | None\n    \"\"\"\n    try:\n        cgroup_file = \"/proc/{0}/cgroup\".format(pid)\n        with open(cgroup_file, mode=\"r\") as fp:\n            for line in fp:\n                info = CGroupInfo.from_line(line)\n                if info and info.container_id:\n                    return info\n    except Exception:\n        log.debug(\"Failed to parse cgroup file for pid %r\", pid, exc_info=True)\n\n    return None\n", "path": "ddtrace/internal/runtime/container.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\n\nfrom ddtrace.vendor.wrapt.importer import when_imported\n\nfrom .internal.logger import get_logger\nfrom .settings import 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\": False,\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\": False,  # experimental propagation\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    \"vertica\": True,\n    \"molten\": True,\n    \"jinja2\": True,\n    \"mako\": True,\n    \"flask\": True,\n    \"kombu\": False,\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}\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\": (\"elasticsearch\",),\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    \"\"\"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        imported_module = importlib.import_module(path)\n        imported_module.patch()\n\n    return on_import\n\n\ndef patch_all(**patch_modules):\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    \"\"\"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            # If the module has already been imported then patch immediately\n            if module in sys.modules:\n                patch_module(module, raise_errors=raise_errors)\n\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(module)(_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)\", len(patched_modules), len(modules), \",\".join(patched_modules),\n    )\n\n\ndef patch_module(module, raise_errors=True):\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    \"\"\"Get the list of patched modules\"\"\"\n    with _LOCK:\n        return sorted(_PATCHED_MODULES)\n\n\ndef _patch_module(module):\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()\n            _PATCHED_MODULES.add(module)\n            return True\n", "path": "ddtrace/monkey.py"}, {"content": "import os\nimport re\n\nfrom ..logger import get_logger\n\nlog = get_logger(__name__)\n\n\nclass CGroupInfo(object):\n    \"\"\"\n    CGroup class for container information parsed from a group cgroup file\n    \"\"\"\n\n    __slots__ = (\"id\", \"groups\", \"path\", \"container_id\", \"controllers\", \"pod_id\")\n\n    UUID_SOURCE_PATTERN = r\"[0-9a-f]{8}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{12}\"\n    CONTAINER_SOURCE_PATTERN = r\"[0-9a-f]{64}\"\n\n    LINE_RE = re.compile(r\"^(\\d+):([^:]*):(.+)$\")\n    POD_RE = re.compile(r\"pod({0})(?:\\.slice)?$\".format(UUID_SOURCE_PATTERN))\n    CONTAINER_RE = re.compile(r\"({0}|{1})(?:\\.scope)?$\".format(UUID_SOURCE_PATTERN, CONTAINER_SOURCE_PATTERN))\n\n    def __init__(self, **kwargs):\n        # Initialize all attributes in __slots__ to `None`\n        # DEV: Otherwise we'll get `AttributeError` when trying to access if they are unset\n        for attr in self.__slots__:\n            setattr(self, attr, kwargs.get(attr))\n\n    @classmethod\n    def from_line(cls, line):\n        \"\"\"\n        Parse a new :class:`CGroupInfo` from the provided line\n\n        :param line: A line from a cgroup file (e.g. /proc/self/cgroup) to parse information from\n        :type line: str\n        :returns: A :class:`CGroupInfo` object with all parsed data, if the line is valid, otherwise `None`\n        :rtype: :class:`CGroupInfo` | None\n\n        \"\"\"\n        # Clean up the line\n        line = line.strip()\n\n        # Ensure the line is valid\n        match = cls.LINE_RE.match(line)\n        if not match:\n            return None\n\n        # Create our new `CGroupInfo` and set attributes from the line\n        info = cls()\n        info.id, info.groups, info.path = match.groups()\n\n        # Parse the controllers from the groups\n        info.controllers = [c.strip() for c in info.groups.split(\",\") if c.strip()]\n\n        # Break up the path to grab container_id and pod_id if available\n        # e.g. /docker/<container_id>\n        # e.g. /kubepods/test/pod<pod_id>/<container_id>\n        parts = [p for p in info.path.split(\"/\")]\n\n        # Grab the container id from the path if a valid id is present\n        if len(parts):\n            match = cls.CONTAINER_RE.match(parts.pop())\n            if match:\n                info.container_id = match.group(1)\n\n        # Grab the pod id from the path if a valid id is present\n        if len(parts):\n            match = cls.POD_RE.match(parts.pop())\n            if match:\n                info.pod_id = match.group(1)\n\n        return info\n\n    def __str__(self):\n        return self.__repr__()\n\n    def __repr__(self):\n        return \"{}(id={!r}, groups={!r}, path={!r}, container_id={!r}, controllers={!r}, pod_id={!r})\".format(\n            self.__class__.__name__, self.id, self.groups, self.path, self.container_id, self.controllers, self.pod_id,\n        )\n\n\ndef get_container_info(pid=\"self\"):\n    \"\"\"\n    Helper to fetch the current container id, if we are running in a container\n\n    We will parse `/proc/{pid}/cgroup` to determine our container id.\n\n    The results of calling this function are cached\n\n    :param pid: The pid of the cgroup file to parse (default: 'self')\n    :type pid: str | int\n    :returns: The cgroup file info if found, or else None\n    :rtype: :class:`CGroupInfo` | None\n    \"\"\"\n\n    cgroup_file = \"/proc/{0}/cgroup\".format(pid)\n\n    if not os.path.exists(cgroup_file):\n        # If the cgroup file does not exist then this is likely not a container\n        # which is a valid use-case so pass.\n        return\n\n    try:\n        with open(cgroup_file, mode=\"r\") as fp:\n            for line in fp:\n                info = CGroupInfo.from_line(line)\n                if info and info.container_id:\n                    return info\n    except Exception:\n        log.debug(\"Failed to parse cgroup file for pid %r\", pid, exc_info=True)\n", "path": "ddtrace/internal/runtime/container.py"}]}