-
Notifications
You must be signed in to change notification settings - Fork 400
/
monkey.py
129 lines (103 loc) · 3.6 KB
/
monkey.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
"""Patch librairies to be automatically instrumented.
It can monkey patch supported standard libraries and third party modules.
A patched module will automatically report spans with its default configuration.
A library instrumentation can be configured (for instance, to report as another service)
using Pin. For that, check its documentation.
"""
import logging
import importlib
import threading
log = logging.getLogger(__name__)
# Default set of modules to automatically patch or not
PATCH_MODULES = {
'boto': False,
'botocore': False,
'bottle': False,
'cassandra': True,
'celery': True,
'elasticsearch': True,
'mongoengine': True,
'mysql': True,
'psycopg': True,
'pylibmc': True,
'pymongo': True,
'redis': True,
'requests': False, # Not ready yet
'sqlalchemy': False, # Prefer DB client instrumentation
'sqlite3': True,
'aiohttp': True, # requires asyncio (Python 3.4+)
'aiobotocore': False,
'httplib': False,
# Ignore some web framework integrations that might be configured explicitly in code
"django": False,
"flask": False,
"falcon": False,
"pylons": False,
"pyramid": False,
}
_LOCK = threading.Lock()
_PATCHED_MODULES = set()
class PatchException(Exception):
"""Wraps regular `Exception` class when patching modules"""
pass
def patch_all(**patch_modules):
"""Automatically patches all available modules.
:param dict \**patch_modules: Override whether particular modules are patched or not.
>>> patch_all({'redis': False, 'cassandra': False})
"""
modules = PATCH_MODULES.copy()
modules.update(patch_modules)
patch(raise_errors=False, **modules)
def patch(raise_errors=True, **patch_modules):
"""Patch only a set of given modules.
:param bool raise_errors: Raise error if one patch fail.
:param dict \**patch_modules: List of modules to patch.
>>> patch({'psycopg': True, 'elasticsearch': True})
"""
modules = [m for (m, should_patch) in patch_modules.items() if should_patch]
count = 0
for module in modules:
patched = patch_module(module, raise_errors=raise_errors)
if patched:
count += 1
log.info("patched %s/%s modules (%s)",
count,
len(modules),
",".join(get_patched_modules()))
def patch_module(module, raise_errors=True):
"""Patch a single module
Returns if the module got properly patched.
"""
try:
return _patch_module(module)
except Exception as exc:
if raise_errors:
raise
log.debug("failed to patch %s: %s", module, exc)
return False
def get_patched_modules():
"""Get the list of patched modules"""
with _LOCK:
return sorted(_PATCHED_MODULES)
def _patch_module(module):
"""_patch_module will attempt to monkey patch the module.
Returns if the module got patched.
Can also raise errors if it fails.
"""
path = 'ddtrace.contrib.%s' % module
with _LOCK:
if module in _PATCHED_MODULES:
log.debug("already patched: %s", path)
return False
try:
imported_module = importlib.import_module(path)
imported_module.patch()
except ImportError:
# if the import fails, the integration is not available
raise PatchException('integration not available')
except AttributeError:
# if patch() is not available in the module, it means
# that the library is not installed in the environment
raise PatchException('module not installed')
_PATCHED_MODULES.add(module)
return True