scout-apm 3.3.0__cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl

Sign up to get free protection for your applications and to get access to all the features.
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-313-aarch64-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 +94 -0
  62. scout_apm-3.3.0.dist-info/RECORD +65 -0
  63. scout_apm-3.3.0.dist-info/WHEEL +7 -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)