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.
Files changed (65) hide show
  1. scout_apm/__init__.py +0 -0
  2. scout_apm/api/__init__.py +197 -0
  3. scout_apm/async_/__init__.py +1 -0
  4. scout_apm/async_/api.py +41 -0
  5. scout_apm/async_/instruments/__init__.py +0 -0
  6. scout_apm/async_/instruments/jinja2.py +13 -0
  7. scout_apm/async_/starlette.py +101 -0
  8. scout_apm/bottle.py +86 -0
  9. scout_apm/celery.py +153 -0
  10. scout_apm/compat.py +104 -0
  11. scout_apm/core/__init__.py +99 -0
  12. scout_apm/core/_objtrace.cpython-38-i386-linux-gnu.so +0 -0
  13. scout_apm/core/agent/__init__.py +0 -0
  14. scout_apm/core/agent/commands.py +250 -0
  15. scout_apm/core/agent/manager.py +319 -0
  16. scout_apm/core/agent/socket.py +211 -0
  17. scout_apm/core/backtrace.py +116 -0
  18. scout_apm/core/cli/__init__.py +0 -0
  19. scout_apm/core/cli/core_agent_manager.py +32 -0
  20. scout_apm/core/config.py +404 -0
  21. scout_apm/core/context.py +140 -0
  22. scout_apm/core/error.py +95 -0
  23. scout_apm/core/error_service.py +167 -0
  24. scout_apm/core/metadata.py +66 -0
  25. scout_apm/core/n_plus_one_tracker.py +41 -0
  26. scout_apm/core/objtrace.py +24 -0
  27. scout_apm/core/platform_detection.py +66 -0
  28. scout_apm/core/queue_time.py +99 -0
  29. scout_apm/core/sampler.py +149 -0
  30. scout_apm/core/samplers/__init__.py +0 -0
  31. scout_apm/core/samplers/cpu.py +76 -0
  32. scout_apm/core/samplers/memory.py +23 -0
  33. scout_apm/core/samplers/thread.py +41 -0
  34. scout_apm/core/stacktracer.py +30 -0
  35. scout_apm/core/threading.py +56 -0
  36. scout_apm/core/tracked_request.py +328 -0
  37. scout_apm/core/web_requests.py +167 -0
  38. scout_apm/django/__init__.py +7 -0
  39. scout_apm/django/apps.py +137 -0
  40. scout_apm/django/instruments/__init__.py +0 -0
  41. scout_apm/django/instruments/huey.py +30 -0
  42. scout_apm/django/instruments/sql.py +140 -0
  43. scout_apm/django/instruments/template.py +35 -0
  44. scout_apm/django/middleware.py +211 -0
  45. scout_apm/django/request.py +144 -0
  46. scout_apm/dramatiq.py +42 -0
  47. scout_apm/falcon.py +142 -0
  48. scout_apm/flask/__init__.py +118 -0
  49. scout_apm/flask/sqlalchemy.py +28 -0
  50. scout_apm/huey.py +54 -0
  51. scout_apm/hug.py +40 -0
  52. scout_apm/instruments/__init__.py +21 -0
  53. scout_apm/instruments/elasticsearch.py +263 -0
  54. scout_apm/instruments/jinja2.py +127 -0
  55. scout_apm/instruments/pymongo.py +105 -0
  56. scout_apm/instruments/redis.py +77 -0
  57. scout_apm/instruments/urllib3.py +80 -0
  58. scout_apm/rq.py +85 -0
  59. scout_apm/sqlalchemy.py +38 -0
  60. scout_apm-3.3.0.dist-info/LICENSE +21 -0
  61. scout_apm-3.3.0.dist-info/METADATA +82 -0
  62. scout_apm-3.3.0.dist-info/RECORD +65 -0
  63. scout_apm-3.3.0.dist-info/WHEEL +5 -0
  64. scout_apm-3.3.0.dist-info/entry_points.txt +2 -0
  65. 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)