scout-apm 3.3.0__cp38-cp38-musllinux_1_2_i686.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scout_apm/__init__.py +0 -0
- scout_apm/api/__init__.py +197 -0
- scout_apm/async_/__init__.py +1 -0
- scout_apm/async_/api.py +41 -0
- scout_apm/async_/instruments/__init__.py +0 -0
- scout_apm/async_/instruments/jinja2.py +13 -0
- scout_apm/async_/starlette.py +101 -0
- scout_apm/bottle.py +86 -0
- scout_apm/celery.py +153 -0
- scout_apm/compat.py +104 -0
- scout_apm/core/__init__.py +99 -0
- scout_apm/core/_objtrace.cpython-38-i386-linux-gnu.so +0 -0
- scout_apm/core/agent/__init__.py +0 -0
- scout_apm/core/agent/commands.py +250 -0
- scout_apm/core/agent/manager.py +319 -0
- scout_apm/core/agent/socket.py +211 -0
- scout_apm/core/backtrace.py +116 -0
- scout_apm/core/cli/__init__.py +0 -0
- scout_apm/core/cli/core_agent_manager.py +32 -0
- scout_apm/core/config.py +404 -0
- scout_apm/core/context.py +140 -0
- scout_apm/core/error.py +95 -0
- scout_apm/core/error_service.py +167 -0
- scout_apm/core/metadata.py +66 -0
- scout_apm/core/n_plus_one_tracker.py +41 -0
- scout_apm/core/objtrace.py +24 -0
- scout_apm/core/platform_detection.py +66 -0
- scout_apm/core/queue_time.py +99 -0
- scout_apm/core/sampler.py +149 -0
- scout_apm/core/samplers/__init__.py +0 -0
- scout_apm/core/samplers/cpu.py +76 -0
- scout_apm/core/samplers/memory.py +23 -0
- scout_apm/core/samplers/thread.py +41 -0
- scout_apm/core/stacktracer.py +30 -0
- scout_apm/core/threading.py +56 -0
- scout_apm/core/tracked_request.py +328 -0
- scout_apm/core/web_requests.py +167 -0
- scout_apm/django/__init__.py +7 -0
- scout_apm/django/apps.py +137 -0
- scout_apm/django/instruments/__init__.py +0 -0
- scout_apm/django/instruments/huey.py +30 -0
- scout_apm/django/instruments/sql.py +140 -0
- scout_apm/django/instruments/template.py +35 -0
- scout_apm/django/middleware.py +211 -0
- scout_apm/django/request.py +144 -0
- scout_apm/dramatiq.py +42 -0
- scout_apm/falcon.py +142 -0
- scout_apm/flask/__init__.py +118 -0
- scout_apm/flask/sqlalchemy.py +28 -0
- scout_apm/huey.py +54 -0
- scout_apm/hug.py +40 -0
- scout_apm/instruments/__init__.py +21 -0
- scout_apm/instruments/elasticsearch.py +263 -0
- scout_apm/instruments/jinja2.py +127 -0
- scout_apm/instruments/pymongo.py +105 -0
- scout_apm/instruments/redis.py +77 -0
- scout_apm/instruments/urllib3.py +80 -0
- scout_apm/rq.py +85 -0
- scout_apm/sqlalchemy.py +38 -0
- scout_apm-3.3.0.dist-info/LICENSE +21 -0
- scout_apm-3.3.0.dist-info/METADATA +82 -0
- scout_apm-3.3.0.dist-info/RECORD +65 -0
- scout_apm-3.3.0.dist-info/WHEEL +5 -0
- scout_apm-3.3.0.dist-info/entry_points.txt +2 -0
- scout_apm-3.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,263 @@
|
|
1
|
+
# coding=utf-8
|
2
|
+
|
3
|
+
import logging
|
4
|
+
from collections import namedtuple
|
5
|
+
|
6
|
+
import wrapt
|
7
|
+
|
8
|
+
from scout_apm.compat import get_pos_args, unwrap_decorators
|
9
|
+
from scout_apm.core.tracked_request import TrackedRequest
|
10
|
+
|
11
|
+
try:
|
12
|
+
from elasticsearch import VERSION as ELASTICSEARCH_VERSION
|
13
|
+
from elasticsearch import Elasticsearch
|
14
|
+
except ImportError: # pragma: no cover
|
15
|
+
Elasticsearch = None
|
16
|
+
ELASTICSEARCH_VERSION = (0, 0, 0)
|
17
|
+
|
18
|
+
try:
|
19
|
+
# Transport was moved to elastic_transport as of v8.0.0
|
20
|
+
from elastic_transport import Transport
|
21
|
+
except ImportError: # pragma: no cover
|
22
|
+
try:
|
23
|
+
from elasticsearch import Transport
|
24
|
+
except ImportError: # pragma: no cover
|
25
|
+
Transport = None
|
26
|
+
|
27
|
+
logger = logging.getLogger(__name__)
|
28
|
+
|
29
|
+
|
30
|
+
def ensure_installed():
|
31
|
+
logger.debug("Instrumenting elasticsearch.")
|
32
|
+
|
33
|
+
if Elasticsearch is None or Transport is None:
|
34
|
+
if Elasticsearch is None:
|
35
|
+
logger.debug(
|
36
|
+
"Couldn't import elasticsearch.Elasticsearch "
|
37
|
+
"- probably not installed."
|
38
|
+
)
|
39
|
+
if Transport is None:
|
40
|
+
logger.debug(
|
41
|
+
"Couldn't import elasticsearch.Transport or "
|
42
|
+
"elastic_transport.Transport - probably not installed."
|
43
|
+
)
|
44
|
+
else:
|
45
|
+
ensure_client_instrumented()
|
46
|
+
ensure_transport_instrumented()
|
47
|
+
|
48
|
+
|
49
|
+
ClientMethod = namedtuple("ClientMethod", ["name", "takes_index_argument"])
|
50
|
+
|
51
|
+
VERSIONED_CLIENT_METHODS = {
|
52
|
+
"v7": [
|
53
|
+
ClientMethod("bulk", True),
|
54
|
+
ClientMethod("clear_scroll", False),
|
55
|
+
ClientMethod("close", False),
|
56
|
+
ClientMethod("close_point_in_time", False),
|
57
|
+
ClientMethod("count", True),
|
58
|
+
ClientMethod("create", True),
|
59
|
+
ClientMethod("delete", True),
|
60
|
+
ClientMethod("delete_by_query", True),
|
61
|
+
ClientMethod("delete_by_query_rethrottle", False),
|
62
|
+
ClientMethod("delete_script", False),
|
63
|
+
ClientMethod("exists", True),
|
64
|
+
ClientMethod("exists_source", True),
|
65
|
+
ClientMethod("explain", True),
|
66
|
+
ClientMethod("field_caps", True),
|
67
|
+
ClientMethod("get", True),
|
68
|
+
ClientMethod("get_script", False),
|
69
|
+
ClientMethod("get_script_context", False),
|
70
|
+
ClientMethod("get_script_languages", False),
|
71
|
+
ClientMethod("get_source", True),
|
72
|
+
ClientMethod("index", True),
|
73
|
+
ClientMethod("info", False),
|
74
|
+
ClientMethod("mget", True),
|
75
|
+
ClientMethod("msearch", True),
|
76
|
+
ClientMethod("msearch_template", True),
|
77
|
+
ClientMethod("mtermvectors", True),
|
78
|
+
ClientMethod("open_point_in_time", True),
|
79
|
+
ClientMethod("ping", False),
|
80
|
+
ClientMethod("put_script", False),
|
81
|
+
ClientMethod("rank_eval", True),
|
82
|
+
ClientMethod("reindex", False),
|
83
|
+
ClientMethod("reindex_rethrottle", False),
|
84
|
+
ClientMethod("render_search_template", False),
|
85
|
+
ClientMethod("scripts_painless_execute", False),
|
86
|
+
ClientMethod("scroll", False),
|
87
|
+
ClientMethod("search", True),
|
88
|
+
ClientMethod("search_mvt", True),
|
89
|
+
ClientMethod("search_shards", True),
|
90
|
+
ClientMethod("search_template", True),
|
91
|
+
ClientMethod("termvectors", True),
|
92
|
+
ClientMethod("terms_enum", True),
|
93
|
+
ClientMethod("update", True),
|
94
|
+
ClientMethod("update_by_query", True),
|
95
|
+
ClientMethod("update_by_query_rethrottle", False),
|
96
|
+
],
|
97
|
+
"v8": [
|
98
|
+
ClientMethod("knn_search", True),
|
99
|
+
ClientMethod("health_report", False),
|
100
|
+
],
|
101
|
+
}
|
102
|
+
|
103
|
+
CLIENT_METHODS = VERSIONED_CLIENT_METHODS["v7"][:]
|
104
|
+
if ELASTICSEARCH_VERSION > (8, 0, 0):
|
105
|
+
CLIENT_METHODS += VERSIONED_CLIENT_METHODS["v8"]
|
106
|
+
|
107
|
+
|
108
|
+
have_patched_client = False
|
109
|
+
|
110
|
+
|
111
|
+
def ensure_client_instrumented():
|
112
|
+
global have_patched_client
|
113
|
+
|
114
|
+
if not have_patched_client:
|
115
|
+
instrumented_count = 0
|
116
|
+
for name, takes_index_argument in CLIENT_METHODS:
|
117
|
+
try:
|
118
|
+
method = getattr(Elasticsearch, name)
|
119
|
+
if takes_index_argument:
|
120
|
+
wrapped = wrap_client_index_method(method)
|
121
|
+
else:
|
122
|
+
wrapped = wrap_client_method(method)
|
123
|
+
setattr(Elasticsearch, name, wrapped)
|
124
|
+
instrumented_count += 1
|
125
|
+
except Exception as exc:
|
126
|
+
logger.debug(
|
127
|
+
"Failed to instrument elasticsearch.Elasticsearch.%s: %r",
|
128
|
+
name,
|
129
|
+
exc,
|
130
|
+
exc_info=exc,
|
131
|
+
)
|
132
|
+
if instrumented_count == 0:
|
133
|
+
logger.warning(
|
134
|
+
"Failed to instrument any elasticsearch.Elasticsearch methods."
|
135
|
+
" Enable debug logs to view root causes."
|
136
|
+
)
|
137
|
+
|
138
|
+
have_patched_client = True
|
139
|
+
|
140
|
+
|
141
|
+
@wrapt.decorator
|
142
|
+
def wrap_client_index_method(wrapped, instance, args, kwargs):
|
143
|
+
# elasticsearch-py 7.5.1 changed the order of arguments for client methods,
|
144
|
+
# so to be safe we need to inspect the wrapped method's positional
|
145
|
+
# arguments to see if we should pull it from there
|
146
|
+
if "index" in kwargs:
|
147
|
+
index = kwargs["index"]
|
148
|
+
else:
|
149
|
+
unwrapped = unwrap_decorators(wrapped)
|
150
|
+
pos_args = get_pos_args(unwrapped)
|
151
|
+
try:
|
152
|
+
index_index = pos_args.index("index")
|
153
|
+
except ValueError: # pragma: no cover
|
154
|
+
# This guards against the method not accepting an 'index' argument
|
155
|
+
# but they all do - for now
|
156
|
+
index = ""
|
157
|
+
else:
|
158
|
+
try:
|
159
|
+
index = args[index_index - 1] # subtract 'self'
|
160
|
+
except IndexError:
|
161
|
+
index = ""
|
162
|
+
|
163
|
+
if isinstance(index, (list, tuple)):
|
164
|
+
index = ",".join(index)
|
165
|
+
if index == "":
|
166
|
+
index = "Unknown"
|
167
|
+
index = index.title()
|
168
|
+
|
169
|
+
camel_name = "".join(c.title() for c in wrapped.__name__.split("_"))
|
170
|
+
operation = "Elasticsearch/{}/{}".format(index, camel_name)
|
171
|
+
tracked_request = TrackedRequest.instance()
|
172
|
+
with tracked_request.span(operation=operation, ignore_children=True):
|
173
|
+
return wrapped(*args, **kwargs)
|
174
|
+
|
175
|
+
|
176
|
+
@wrapt.decorator
|
177
|
+
def wrap_client_method(wrapped, instance, args, kwargs):
|
178
|
+
camel_name = "".join(c.title() for c in wrapped.__name__.split("_"))
|
179
|
+
operation = "Elasticsearch/{}".format(camel_name)
|
180
|
+
tracked_request = TrackedRequest.instance()
|
181
|
+
with tracked_request.span(operation=operation, ignore_children=True):
|
182
|
+
return wrapped(*args, **kwargs)
|
183
|
+
|
184
|
+
|
185
|
+
have_patched_transport = False
|
186
|
+
|
187
|
+
|
188
|
+
def ensure_transport_instrumented():
|
189
|
+
global have_patched_transport
|
190
|
+
|
191
|
+
if not have_patched_transport:
|
192
|
+
try:
|
193
|
+
Transport.perform_request = wrapped_perform_request(
|
194
|
+
Transport.perform_request
|
195
|
+
)
|
196
|
+
except Exception as exc:
|
197
|
+
logger.warning(
|
198
|
+
"Failed to instrument elasticsearch.Transport.perform_request: %r",
|
199
|
+
exc,
|
200
|
+
exc_info=exc,
|
201
|
+
)
|
202
|
+
|
203
|
+
have_patched_transport = True
|
204
|
+
|
205
|
+
|
206
|
+
def _sanitize_name(name):
|
207
|
+
try:
|
208
|
+
op = name.split("/")[-1]
|
209
|
+
op = op[1:] # chop leading '_' from op
|
210
|
+
known_names = (
|
211
|
+
"bench",
|
212
|
+
"bulk",
|
213
|
+
"count",
|
214
|
+
"delete_by_query",
|
215
|
+
"execute",
|
216
|
+
"exists",
|
217
|
+
"explain",
|
218
|
+
"field_caps",
|
219
|
+
"field_stats",
|
220
|
+
"health",
|
221
|
+
"mget",
|
222
|
+
"mlt",
|
223
|
+
"mpercolate",
|
224
|
+
"msearch",
|
225
|
+
"mtermvectors",
|
226
|
+
"percolate",
|
227
|
+
"pit",
|
228
|
+
"query",
|
229
|
+
"rank_eval",
|
230
|
+
"reindex",
|
231
|
+
"script_context",
|
232
|
+
"script_language",
|
233
|
+
"scroll",
|
234
|
+
"search_shards",
|
235
|
+
"source",
|
236
|
+
"suggest",
|
237
|
+
"template",
|
238
|
+
"termvectors",
|
239
|
+
"terms_enum",
|
240
|
+
"update",
|
241
|
+
"update_by_query",
|
242
|
+
"search",
|
243
|
+
)
|
244
|
+
if op in known_names:
|
245
|
+
return op.title()
|
246
|
+
return "Unknown"
|
247
|
+
except Exception:
|
248
|
+
return "Unknown"
|
249
|
+
|
250
|
+
|
251
|
+
@wrapt.decorator
|
252
|
+
def wrapped_perform_request(wrapped, instance, args, kwargs):
|
253
|
+
try:
|
254
|
+
op = _sanitize_name(args[1])
|
255
|
+
except IndexError:
|
256
|
+
op = "Unknown"
|
257
|
+
|
258
|
+
tracked_request = TrackedRequest.instance()
|
259
|
+
with tracked_request.span(
|
260
|
+
operation="Elasticsearch/{}".format(op),
|
261
|
+
ignore_children=True,
|
262
|
+
):
|
263
|
+
return wrapped(*args, **kwargs)
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# coding=utf-8
|
2
|
+
|
3
|
+
import logging
|
4
|
+
import sys
|
5
|
+
|
6
|
+
import wrapt
|
7
|
+
|
8
|
+
from scout_apm.core.tracked_request import TrackedRequest
|
9
|
+
|
10
|
+
try:
|
11
|
+
from jinja2 import Environment
|
12
|
+
except ImportError: # pragma: no cover
|
13
|
+
Environment = None
|
14
|
+
|
15
|
+
try:
|
16
|
+
from jinja2 import Template
|
17
|
+
except ImportError: # pragma: no cover
|
18
|
+
Template = None
|
19
|
+
|
20
|
+
# The async_ module can only be shipped on Python 3.6+
|
21
|
+
try:
|
22
|
+
from scout_apm.async_.instruments.jinja2 import wrapped_render_async
|
23
|
+
except ImportError:
|
24
|
+
wrapped_render_async = None
|
25
|
+
|
26
|
+
|
27
|
+
logger = logging.getLogger(__name__)
|
28
|
+
|
29
|
+
|
30
|
+
have_patched_environment_init = False
|
31
|
+
have_patched_template_render = False
|
32
|
+
have_patched_template_render_async = False
|
33
|
+
|
34
|
+
|
35
|
+
def ensure_installed():
|
36
|
+
global have_patched_template_render
|
37
|
+
|
38
|
+
logger.debug("Instrumenting Jinja2.")
|
39
|
+
|
40
|
+
if Template is None:
|
41
|
+
logger.debug("Couldn't import jinja2.Template - probably not installed.")
|
42
|
+
return
|
43
|
+
|
44
|
+
instrument_render_async()
|
45
|
+
|
46
|
+
if not have_patched_template_render:
|
47
|
+
try:
|
48
|
+
Template.render = wrapped_render(Template.render)
|
49
|
+
except Exception as exc:
|
50
|
+
logger.warning(
|
51
|
+
"Failed to instrument jinja2.Template.render: %r", exc, exc_info=exc
|
52
|
+
)
|
53
|
+
else:
|
54
|
+
have_patched_template_render = True
|
55
|
+
|
56
|
+
|
57
|
+
def instrument_render_async():
|
58
|
+
global have_patched_environment_init
|
59
|
+
global have_patched_template_render_async
|
60
|
+
|
61
|
+
if wrapped_render_async is None:
|
62
|
+
return
|
63
|
+
|
64
|
+
if not have_patched_environment_init and not hasattr(Template, "render_async"):
|
65
|
+
try:
|
66
|
+
Environment.__init__ = wrapped_environment_init_jinja_v2(
|
67
|
+
Environment.__init__
|
68
|
+
)
|
69
|
+
except Exception as exc:
|
70
|
+
logger.warning(
|
71
|
+
"Failed to instrument jinja2.Environment.__init__: %r",
|
72
|
+
exc,
|
73
|
+
exc_info=exc,
|
74
|
+
)
|
75
|
+
else:
|
76
|
+
have_patched_environment_init = True
|
77
|
+
elif hasattr(Template, "render_async") and not have_patched_template_render_async:
|
78
|
+
try:
|
79
|
+
Template.render_async = wrapped_render_async(Template.render_async)
|
80
|
+
except Exception as exc:
|
81
|
+
logger.warning(
|
82
|
+
"Failed to instrument jinja2.Template.render_async: %r",
|
83
|
+
exc,
|
84
|
+
exc_info=exc,
|
85
|
+
)
|
86
|
+
else:
|
87
|
+
have_patched_template_render_async = True
|
88
|
+
|
89
|
+
|
90
|
+
@wrapt.decorator
|
91
|
+
def wrapped_render(wrapped, instance, args, kwargs):
|
92
|
+
tracked_request = TrackedRequest.instance()
|
93
|
+
with tracked_request.span(operation="Template/Render") as span:
|
94
|
+
span.tag("name", instance.name)
|
95
|
+
return wrapped(*args, **kwargs)
|
96
|
+
|
97
|
+
|
98
|
+
@wrapt.decorator
|
99
|
+
def wrapped_environment_init_jinja_v2(wrapped, instance, args, kwargs):
|
100
|
+
"""
|
101
|
+
Delayed wrapping of render_async(), since Template won't have this method
|
102
|
+
until after jinja2.asyncsupport is imported, which since Jinja2 2.11.0 is
|
103
|
+
done conditionally in Environment.__init__:
|
104
|
+
https://github.com/pallets/jinja/issues/765
|
105
|
+
|
106
|
+
This is no longer needed since Jinja2 v3.0.0
|
107
|
+
"""
|
108
|
+
global have_patched_template_render_async
|
109
|
+
result = wrapped(*args, **kwargs)
|
110
|
+
|
111
|
+
if (
|
112
|
+
wrapped_render_async is not None
|
113
|
+
and not have_patched_template_render_async
|
114
|
+
and "jinja2.asyncsupport" in sys.modules
|
115
|
+
):
|
116
|
+
try:
|
117
|
+
Template.render_async = wrapped_render_async(Template.render_async)
|
118
|
+
except Exception as exc:
|
119
|
+
logger.warning(
|
120
|
+
"Failed to instrument jinja2.Template.render_async: %r",
|
121
|
+
exc,
|
122
|
+
exc_info=exc,
|
123
|
+
)
|
124
|
+
else:
|
125
|
+
have_patched_template_render_async = True
|
126
|
+
|
127
|
+
return result
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# coding=utf-8
|
2
|
+
|
3
|
+
import logging
|
4
|
+
|
5
|
+
import wrapt
|
6
|
+
|
7
|
+
from scout_apm.core.tracked_request import TrackedRequest
|
8
|
+
|
9
|
+
try:
|
10
|
+
import pymongo
|
11
|
+
from pymongo.collection import Collection
|
12
|
+
except ImportError:
|
13
|
+
pymongo = None
|
14
|
+
Collection = None
|
15
|
+
|
16
|
+
logger = logging.getLogger(__name__)
|
17
|
+
|
18
|
+
have_patched_collection = False
|
19
|
+
|
20
|
+
|
21
|
+
def ensure_installed():
|
22
|
+
global have_patched_collection
|
23
|
+
|
24
|
+
logger.debug("Instrumenting pymongo.")
|
25
|
+
|
26
|
+
if Collection is None:
|
27
|
+
logger.debug("Couldn't import pymongo.Collection - probably not installed.")
|
28
|
+
elif not have_patched_collection:
|
29
|
+
methods = COLLECTION_METHODS
|
30
|
+
if pymongo.version_tuple < (4, 0):
|
31
|
+
methods = COLLECTION_METHODS_V3
|
32
|
+
for name in methods:
|
33
|
+
try:
|
34
|
+
setattr(
|
35
|
+
Collection, name, wrap_collection_method(getattr(Collection, name))
|
36
|
+
)
|
37
|
+
except Exception as exc:
|
38
|
+
logger.warning(
|
39
|
+
"Failed to instrument pymongo.Collection.%s: %r",
|
40
|
+
name,
|
41
|
+
exc,
|
42
|
+
exc_info=exc,
|
43
|
+
)
|
44
|
+
have_patched_collection = True
|
45
|
+
|
46
|
+
|
47
|
+
COLLECTION_METHODS = [
|
48
|
+
"aggregate",
|
49
|
+
"aggregate_raw_batches",
|
50
|
+
"bulk_write",
|
51
|
+
"count_documents",
|
52
|
+
"create_index",
|
53
|
+
"create_indexes",
|
54
|
+
"delete_many",
|
55
|
+
"delete_one",
|
56
|
+
"distinct",
|
57
|
+
"drop",
|
58
|
+
"drop_index",
|
59
|
+
"drop_indexes",
|
60
|
+
"estimated_document_count",
|
61
|
+
"find",
|
62
|
+
"find_one",
|
63
|
+
"find_one_and_delete",
|
64
|
+
"find_one_and_replace",
|
65
|
+
"find_one_and_update",
|
66
|
+
"find_raw_batches",
|
67
|
+
"index_information",
|
68
|
+
"insert_many",
|
69
|
+
"insert_one",
|
70
|
+
"list_indexes",
|
71
|
+
"rename",
|
72
|
+
"replace_one",
|
73
|
+
"update_many",
|
74
|
+
"update_one",
|
75
|
+
"drop_search_index",
|
76
|
+
"create_search_indexes",
|
77
|
+
"create_search_index",
|
78
|
+
"list_search_indexes",
|
79
|
+
"update_search_index",
|
80
|
+
]
|
81
|
+
|
82
|
+
COLLECTION_METHODS_V3 = COLLECTION_METHODS + [
|
83
|
+
"count",
|
84
|
+
"ensure_index",
|
85
|
+
"find_and_modify",
|
86
|
+
"group",
|
87
|
+
"inline_map_reduce",
|
88
|
+
"insert",
|
89
|
+
"map_reduce",
|
90
|
+
"parallel_scan",
|
91
|
+
"reindex",
|
92
|
+
"remove",
|
93
|
+
"save",
|
94
|
+
"update",
|
95
|
+
]
|
96
|
+
|
97
|
+
|
98
|
+
@wrapt.decorator
|
99
|
+
def wrap_collection_method(wrapped, instance, args, kwargs):
|
100
|
+
tracked_request = TrackedRequest.instance()
|
101
|
+
camel_name = "".join(c.title() for c in wrapped.__name__.split("_"))
|
102
|
+
operation = "MongoDB/{}.{}".format(instance.name, camel_name)
|
103
|
+
with tracked_request.span(operation=operation, ignore_children=True) as span:
|
104
|
+
span.tag("name", instance.name)
|
105
|
+
return wrapped(*args, **kwargs)
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# coding=utf-8
|
2
|
+
|
3
|
+
import logging
|
4
|
+
|
5
|
+
import wrapt
|
6
|
+
|
7
|
+
from scout_apm.core.tracked_request import TrackedRequest
|
8
|
+
|
9
|
+
try:
|
10
|
+
import redis
|
11
|
+
except ImportError: # pragma: no cover
|
12
|
+
redis = None
|
13
|
+
else:
|
14
|
+
if redis.VERSION[0] >= 3:
|
15
|
+
from redis import Redis
|
16
|
+
from redis.client import Pipeline
|
17
|
+
else: # pragma: no cover
|
18
|
+
from redis import StrictRedis as Redis
|
19
|
+
from redis.client import BasePipeline as Pipeline
|
20
|
+
|
21
|
+
logger = logging.getLogger(__name__)
|
22
|
+
|
23
|
+
|
24
|
+
have_patched_redis_execute_command = False
|
25
|
+
have_patched_pipeline_execute = False
|
26
|
+
|
27
|
+
|
28
|
+
def ensure_installed():
|
29
|
+
global have_patched_redis_execute_command, have_patched_pipeline_execute
|
30
|
+
|
31
|
+
logger.debug("Instrumenting redis.")
|
32
|
+
|
33
|
+
if redis is None:
|
34
|
+
logger.debug("Couldn't import redis - probably not installed.")
|
35
|
+
else:
|
36
|
+
if not have_patched_redis_execute_command:
|
37
|
+
try:
|
38
|
+
Redis.execute_command = wrapped_execute_command(Redis.execute_command)
|
39
|
+
except Exception as exc:
|
40
|
+
logger.warning(
|
41
|
+
"Failed to instrument redis.Redis.execute_command: %r",
|
42
|
+
exc,
|
43
|
+
exc_info=exc,
|
44
|
+
)
|
45
|
+
else:
|
46
|
+
have_patched_redis_execute_command = True
|
47
|
+
|
48
|
+
if not have_patched_pipeline_execute:
|
49
|
+
try:
|
50
|
+
Pipeline.execute = wrapped_execute(Pipeline.execute)
|
51
|
+
except Exception as exc:
|
52
|
+
logger.warning(
|
53
|
+
"Failed to instrument redis.Pipeline.execute: %r", exc, exc_info=exc
|
54
|
+
)
|
55
|
+
else:
|
56
|
+
have_patched_pipeline_execute = True
|
57
|
+
|
58
|
+
return True
|
59
|
+
|
60
|
+
|
61
|
+
@wrapt.decorator
|
62
|
+
def wrapped_execute_command(wrapped, instance, args, kwargs):
|
63
|
+
try:
|
64
|
+
op = args[0]
|
65
|
+
except (IndexError, TypeError):
|
66
|
+
op = "Unknown"
|
67
|
+
|
68
|
+
tracked_request = TrackedRequest.instance()
|
69
|
+
with tracked_request.span(operation="Redis/{}".format(op)):
|
70
|
+
return wrapped(*args, **kwargs)
|
71
|
+
|
72
|
+
|
73
|
+
@wrapt.decorator
|
74
|
+
def wrapped_execute(wrapped, instance, args, kwargs):
|
75
|
+
tracked_request = TrackedRequest.instance()
|
76
|
+
with tracked_request.span(operation="Redis/MULTI"):
|
77
|
+
return wrapped(*args, **kwargs)
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# coding=utf-8
|
2
|
+
|
3
|
+
import logging
|
4
|
+
|
5
|
+
import urllib3
|
6
|
+
import wrapt
|
7
|
+
|
8
|
+
from scout_apm.core.config import scout_config
|
9
|
+
from scout_apm.core.tracked_request import TrackedRequest
|
10
|
+
|
11
|
+
try:
|
12
|
+
from urllib3 import HTTPConnectionPool
|
13
|
+
except ImportError: # pragma: no cover
|
14
|
+
HTTPConnectionPool = None
|
15
|
+
|
16
|
+
# Try except separately because _url_from_pool is explicitly imported for urllib3 >= 2.
|
17
|
+
# HTTPConnectionPool is always required.
|
18
|
+
try:
|
19
|
+
from urllib3.connectionpool import _url_from_pool
|
20
|
+
except ImportError: # pragma: no cover
|
21
|
+
|
22
|
+
def _url_from_pool(pool, path):
|
23
|
+
pass
|
24
|
+
|
25
|
+
|
26
|
+
logger = logging.getLogger(__name__)
|
27
|
+
|
28
|
+
have_patched_pool_urlopen = False
|
29
|
+
|
30
|
+
|
31
|
+
def ensure_installed():
|
32
|
+
global have_patched_pool_urlopen
|
33
|
+
|
34
|
+
logger.debug("Instrumenting urllib3.")
|
35
|
+
|
36
|
+
if HTTPConnectionPool is None:
|
37
|
+
logger.debug(
|
38
|
+
"Couldn't import urllib3.HTTPConnectionPool - probably not installed."
|
39
|
+
)
|
40
|
+
return False
|
41
|
+
elif not have_patched_pool_urlopen:
|
42
|
+
try:
|
43
|
+
HTTPConnectionPool.urlopen = wrapped_urlopen(HTTPConnectionPool.urlopen)
|
44
|
+
except Exception as exc:
|
45
|
+
logger.warning(
|
46
|
+
"Failed to instrument for Urllib3 HTTPConnectionPool.urlopen: %r",
|
47
|
+
exc,
|
48
|
+
exc_info=exc,
|
49
|
+
)
|
50
|
+
else:
|
51
|
+
have_patched_pool_urlopen = True
|
52
|
+
|
53
|
+
|
54
|
+
@wrapt.decorator
|
55
|
+
def wrapped_urlopen(wrapped, instance, args, kwargs):
|
56
|
+
def _extract_method(method, *args, **kwargs):
|
57
|
+
return method
|
58
|
+
|
59
|
+
try:
|
60
|
+
method = _extract_method(*args, **kwargs)
|
61
|
+
except TypeError:
|
62
|
+
method = "Unknown"
|
63
|
+
|
64
|
+
try:
|
65
|
+
if int(urllib3.__version__.split(".")[0]) < 2:
|
66
|
+
url = str(instance._absolute_url("/"))
|
67
|
+
else:
|
68
|
+
url = str(_url_from_pool(instance, "/"))
|
69
|
+
except Exception:
|
70
|
+
logger.exception("Could not get URL for HTTPConnectionPool")
|
71
|
+
url = "Unknown"
|
72
|
+
|
73
|
+
# Don't instrument ErrorMonitor calls
|
74
|
+
if str(url).startswith(scout_config.value("errors_host")):
|
75
|
+
return wrapped(*args, **kwargs)
|
76
|
+
|
77
|
+
tracked_request = TrackedRequest.instance()
|
78
|
+
with tracked_request.span(operation="HTTP/{}".format(method)) as span:
|
79
|
+
span.tag("url", str(url))
|
80
|
+
return wrapped(*args, **kwargs)
|