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
scout_apm/dramatiq.py ADDED
@@ -0,0 +1,42 @@
1
+ # coding=utf-8
2
+
3
+ import dramatiq
4
+
5
+ import scout_apm.core
6
+ from scout_apm.core.tracked_request import TrackedRequest
7
+
8
+
9
+ class ScoutMiddleware(dramatiq.Middleware):
10
+ def __init__(self):
11
+ installed = scout_apm.core.install()
12
+ self._do_nothing = not installed
13
+
14
+ def before_process_message(self, broker, message):
15
+ if self._do_nothing:
16
+ return
17
+ tracked_request = TrackedRequest.instance()
18
+ tracked_request.tag("queue", message.queue_name)
19
+ tracked_request.tag("message_id", message.message_id)
20
+ operation = "Job/" + message.actor_name
21
+ tracked_request.start_span(operation=operation)
22
+ tracked_request.operation = operation
23
+
24
+ def after_process_message(self, broker, message, result=None, exception=None):
25
+ if self._do_nothing:
26
+ return
27
+ tracked_request = TrackedRequest.instance()
28
+ tracked_request.is_real_request = True
29
+ if exception:
30
+ tracked_request.tag("error", "true")
31
+ tracked_request.stop_span()
32
+
33
+ def after_skip_message(self, broker, message):
34
+ """
35
+ The message was skipped by another middleware raising SkipMessage.
36
+ Stop the span and thus the request, it won't have been marked as real
37
+ so that's alright.
38
+ """
39
+ if self._do_nothing:
40
+ return
41
+ tracked_request = TrackedRequest.instance()
42
+ tracked_request.stop_span()
scout_apm/falcon.py ADDED
@@ -0,0 +1,142 @@
1
+ # coding=utf-8
2
+
3
+ import logging
4
+ import warnings
5
+
6
+ import falcon
7
+
8
+ from scout_apm.api import install
9
+ from scout_apm.core.config import scout_config
10
+ from scout_apm.core.queue_time import track_request_queue_time
11
+ from scout_apm.core.tracked_request import TrackedRequest
12
+ from scout_apm.core.web_requests import create_filtered_path, ignore_path
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ # Falcon Middleware docs:
17
+ # https://falcon.readthedocs.io/en/stable/api/middleware.html
18
+
19
+
20
+ class ScoutMiddleware(object):
21
+ """
22
+ Falcon Middleware for integration with Scout APM.
23
+ """
24
+
25
+ def __init__(self, config, hug_http_interface=None):
26
+ self.api = None
27
+ self.hug_http_interface = hug_http_interface
28
+ installed = install(config=config)
29
+ self._do_nothing = not installed
30
+
31
+ def set_api(self, api):
32
+ if not isinstance(api, falcon.API):
33
+ raise ValueError("api should be an instance of falcon.API")
34
+ self.api = api
35
+
36
+ def process_request(self, req, resp):
37
+ if self._do_nothing:
38
+ return
39
+ if self.api is None and self.hug_http_interface is not None:
40
+ self.api = self.hug_http_interface.falcon
41
+ tracked_request = TrackedRequest.instance()
42
+ tracked_request.is_real_request = True
43
+ req.context.scout_tracked_request = tracked_request
44
+ tracked_request.start_span(
45
+ operation="Middleware", should_capture_backtrace=False
46
+ )
47
+
48
+ path = req.path
49
+ # Falcon URL parameter values are *either* single items or lists
50
+ url_params = [
51
+ (k, v)
52
+ for k, vs in req.params.items()
53
+ for v in (vs if isinstance(vs, list) else [vs])
54
+ ]
55
+ tracked_request.tag("path", create_filtered_path(path, url_params))
56
+ if ignore_path(path):
57
+ tracked_request.tag("ignore_transaction", True)
58
+
59
+ if scout_config.value("collect_remote_ip"):
60
+ # Determine a remote IP to associate with the request. The value is
61
+ # spoofable by the requester so this is not suitable to use in any
62
+ # security sensitive context.
63
+ user_ip = (
64
+ req.get_header("x-forwarded-for", default="").split(",")[0]
65
+ or req.get_header("client-ip", default="").split(",")[0]
66
+ or req.remote_addr
67
+ )
68
+ tracked_request.tag("user_ip", user_ip)
69
+
70
+ queue_time = req.get_header("x-queue-start", default="") or req.get_header(
71
+ "x-request-start", default=""
72
+ )
73
+ track_request_queue_time(queue_time, tracked_request)
74
+
75
+ def process_resource(self, req, resp, resource, params):
76
+ if self._do_nothing:
77
+ return
78
+
79
+ tracked_request = getattr(req.context, "scout_tracked_request", None)
80
+ if tracked_request is None:
81
+ # Somehow we didn't start a request - this might occur in
82
+ # combination with a pretty adversarial application, so guard
83
+ # against it, although if a request was started and the context was
84
+ # lost, other problems might occur.
85
+ return
86
+
87
+ if self.api is None:
88
+ warnings.warn(
89
+ (
90
+ "{}.set_api() should be called before requests begin for"
91
+ + " more detail."
92
+ ).format(self.__class__.__name__),
93
+ RuntimeWarning,
94
+ stacklevel=2,
95
+ )
96
+ operation = "Controller/{}.{}.{}".format(
97
+ resource.__module__, resource.__class__.__name__, req.method
98
+ )
99
+ else:
100
+ # Find the current responder's name. Falcon passes middleware the
101
+ # current resource but unfortunately not the method being called, hence
102
+ # we have to go through routing again.
103
+ responder, _params, _resource, _uri_template = self.api._get_responder(req)
104
+ operation = self._name_operation(req, responder, resource)
105
+
106
+ span = tracked_request.start_span(
107
+ operation=operation, should_capture_backtrace=False
108
+ )
109
+ tracked_request.operation = operation
110
+ req.context.scout_resource_span = span
111
+
112
+ def _name_operation(self, req, responder, resource):
113
+ try:
114
+ last_part = responder.__name__
115
+ except AttributeError:
116
+ last_part = req.method
117
+ return "Controller/{}.{}.{}".format(
118
+ resource.__module__, resource.__class__.__name__, last_part
119
+ )
120
+
121
+ def process_response(self, req, resp, resource, req_succeeded):
122
+ tracked_request = getattr(req.context, "scout_tracked_request", None)
123
+ if tracked_request is None:
124
+ # Somehow we didn't start a request
125
+ return
126
+
127
+ # Falcon only stores the response status line, we have to parse it
128
+ try:
129
+ status_code = int(resp.status.split(" ")[0])
130
+ except ValueError:
131
+ # Bad status line - force it to be tagged as an error because
132
+ # client will experience it as one
133
+ status_code = 500
134
+
135
+ if not req_succeeded or 500 <= status_code <= 599:
136
+ tracked_request.tag("error", "true")
137
+
138
+ span = getattr(req.context, "scout_resource_span", None)
139
+ if span is not None:
140
+ tracked_request.stop_span()
141
+ # Stop Middleware span
142
+ tracked_request.stop_span()
@@ -0,0 +1,118 @@
1
+ # coding=utf-8
2
+
3
+ import logging
4
+ import sys
5
+
6
+ import wrapt
7
+ from flask import current_app
8
+ from flask.globals import request, session
9
+
10
+ import scout_apm.core
11
+ from scout_apm.core.config import scout_config
12
+ from scout_apm.core.error import ErrorMonitor
13
+ from scout_apm.core.tracked_request import TrackedRequest
14
+ from scout_apm.core.web_requests import RequestComponents, werkzeug_track_request_data
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class ScoutApm(object):
20
+ def __init__(self, app):
21
+ self.app = app
22
+ self._attempted_install = False
23
+ app.full_dispatch_request = self.wrapped_full_dispatch_request(
24
+ app.full_dispatch_request
25
+ )
26
+ app.preprocess_request = self.wrapped_preprocess_request(app.preprocess_request)
27
+
28
+ @wrapt.decorator
29
+ def wrapped_full_dispatch_request(self, wrapped, instance, args, kwargs):
30
+ if not self._attempted_install:
31
+ self.extract_flask_settings()
32
+ installed = scout_apm.core.install()
33
+ self._do_nothing = not installed
34
+ self._attempted_install = True
35
+
36
+ if self._do_nothing:
37
+ return wrapped(*args, **kwargs)
38
+
39
+ # Pass on routing exceptions (normally 404's)
40
+ if request.routing_exception is not None:
41
+ return wrapped(*args, **kwargs)
42
+
43
+ request_components = get_request_components(self.app, request)
44
+ operation = "Controller/{}.{}".format(
45
+ request_components.module, request_components.controller
46
+ )
47
+
48
+ tracked_request = TrackedRequest.instance()
49
+ tracked_request.is_real_request = True
50
+ tracked_request.operation = operation
51
+ request._scout_tracked_request = tracked_request
52
+
53
+ werkzeug_track_request_data(request, tracked_request)
54
+
55
+ with tracked_request.span(
56
+ operation=operation, should_capture_backtrace=False
57
+ ) as span:
58
+ request._scout_view_span = span
59
+
60
+ try:
61
+ response = wrapped(*args, **kwargs)
62
+ except Exception as exc:
63
+ tracked_request.tag("error", "true")
64
+ if scout_config.value("errors_enabled"):
65
+ ErrorMonitor.send(
66
+ sys.exc_info(),
67
+ request_components=get_request_components(self.app, request),
68
+ request_path=request.path,
69
+ request_params=dict(request.args.lists()),
70
+ session=dict(session.items()),
71
+ environment=self.app.config,
72
+ )
73
+ raise exc
74
+ else:
75
+ if 500 <= response.status_code <= 599:
76
+ tracked_request.tag("error", "true")
77
+ return response
78
+
79
+ @wrapt.decorator
80
+ def wrapped_preprocess_request(self, wrapped, instance, args, kwargs):
81
+ tracked_request = getattr(request, "_scout_tracked_request", None)
82
+ if tracked_request is None:
83
+ return wrapped(*args, **kwargs)
84
+
85
+ # Unlike middleware in other frameworks, using request preprocessors is
86
+ # less common in Flask, so only add a span if there is any in use
87
+ have_before_request_funcs = (
88
+ None in instance.before_request_funcs
89
+ or request.blueprint in instance.before_request_funcs
90
+ )
91
+ if not have_before_request_funcs:
92
+ return wrapped(*args, **kwargs)
93
+
94
+ with tracked_request.span("PreprocessRequest", should_capture_backtrace=False):
95
+ return wrapped(*args, **kwargs)
96
+
97
+ def extract_flask_settings(self):
98
+ """
99
+ Copies SCOUT_* settings in the app into Scout's config lookup
100
+ """
101
+ configs = {}
102
+ configs["application_root"] = self.app.instance_path
103
+ for name in current_app.config:
104
+ if name.startswith("SCOUT_"):
105
+ value = current_app.config[name]
106
+ clean_name = name.replace("SCOUT_", "").lower()
107
+ configs[clean_name] = value
108
+ scout_config.set(**configs)
109
+
110
+
111
+ def get_request_components(app, request):
112
+ view_func = app.view_functions[request.endpoint]
113
+ request_components = RequestComponents(
114
+ module=view_func.__module__,
115
+ controller=view_func.__name__,
116
+ action=request.method,
117
+ )
118
+ return request_components
@@ -0,0 +1,28 @@
1
+ # coding=utf-8
2
+
3
+ import wrapt
4
+ from flask_sqlalchemy import SQLAlchemy
5
+
6
+ import scout_apm.sqlalchemy
7
+
8
+
9
+ def instrument_sqlalchemy(db):
10
+ # Version 3 of flask_sqlalchemy changed how engines are created
11
+ if hasattr(db, "_make_engine"):
12
+ db._make_engine = wrapped_make_engine(db._make_engine)
13
+ else:
14
+ SQLAlchemy.get_engine = wrapped_get_engine(SQLAlchemy.get_engine)
15
+
16
+
17
+ @wrapt.decorator
18
+ def wrapped_get_engine(wrapped, instance, args, kwargs):
19
+ engine = wrapped(*args, **kwargs)
20
+ scout_apm.sqlalchemy.instrument_sqlalchemy(engine)
21
+ return engine
22
+
23
+
24
+ @wrapt.decorator
25
+ def wrapped_make_engine(wrapped, instance, args, kwargs):
26
+ engine = wrapped(*args, **kwargs)
27
+ scout_apm.sqlalchemy.instrument_sqlalchemy(engine)
28
+ return engine
scout_apm/huey.py ADDED
@@ -0,0 +1,54 @@
1
+ # coding=utf-8
2
+
3
+ from huey.exceptions import RetryTask, TaskLockedException
4
+ from huey.signals import SIGNAL_CANCELED
5
+
6
+ import scout_apm.core
7
+ from scout_apm.core.tracked_request import TrackedRequest
8
+
9
+ # Because neither hooks nor signals are called in *all* cases, we need to use
10
+ # both in order to capture every case. See source:
11
+ # https://github.com/coleifer/huey/blob/e6710bd6a9f581ebc728e24f5923d26eb0047750/huey/api.py#L331 # noqa
12
+
13
+
14
+ def attach_scout(huey):
15
+ installed = scout_apm.core.install()
16
+ if installed:
17
+ attach_scout_handlers(huey)
18
+
19
+
20
+ def attach_scout_handlers(huey):
21
+ huey.pre_execute()(scout_on_pre_execute)
22
+ huey.post_execute()(scout_on_post_execute)
23
+ huey.signal(SIGNAL_CANCELED)(scout_on_cancelled)
24
+
25
+
26
+ def scout_on_pre_execute(task):
27
+ tracked_request = TrackedRequest.instance()
28
+
29
+ tracked_request.tag("task_id", task.id)
30
+
31
+ operation = "Job/{}.{}".format(task.__module__, task.__class__.__name__)
32
+ tracked_request.start_span(operation=operation)
33
+ tracked_request.operation = operation
34
+
35
+
36
+ def scout_on_post_execute(task, task_value, exception):
37
+ tracked_request = TrackedRequest.instance()
38
+ if exception is None:
39
+ tracked_request.is_real_request = True
40
+ elif isinstance(exception, TaskLockedException):
41
+ pass
42
+ elif isinstance(exception, RetryTask):
43
+ tracked_request.is_real_request = True
44
+ tracked_request.tag("retrying", True)
45
+ else:
46
+ tracked_request.is_real_request = True
47
+ tracked_request.tag("error", "true")
48
+ tracked_request.stop_span()
49
+
50
+
51
+ def scout_on_cancelled(signal, task, exc=None):
52
+ # In the case of a cancelled signal, Huey doesn't run the post_execute
53
+ # handler, so we need to tidy up
54
+ TrackedRequest.instance().stop_span()
scout_apm/hug.py ADDED
@@ -0,0 +1,40 @@
1
+ # coding=utf-8
2
+
3
+ import hug
4
+ from hug.interface import HTTP
5
+
6
+ from scout_apm.falcon import ScoutMiddleware as FalconMiddleware
7
+
8
+
9
+ class ScoutMiddleware(FalconMiddleware):
10
+ """
11
+ Hug's HTTP interface is based on Falcon. Therefore we use a subclass of our
12
+ Falcon integration with Hug specific extras.
13
+ """
14
+
15
+ def __init__(self, config, hug_http_interface):
16
+ super(ScoutMiddleware, self).__init__(config)
17
+ self.hug_http_interface = hug_http_interface
18
+
19
+ def process_request(self, req, resp):
20
+ if not self._do_nothing and self.api is None:
21
+ self.api = self.hug_http_interface.falcon
22
+ return super(ScoutMiddleware, self).process_request(req, resp)
23
+
24
+ def _name_operation(self, req, responder, resource):
25
+ if isinstance(responder, HTTP):
26
+ # Hug doesn't use functions but its custom callable classes
27
+ return "Controller/{}.{}".format(
28
+ responder.interface._function.__module__,
29
+ responder.interface._function.__name__,
30
+ )
31
+ return super(ScoutMiddleware, self)._name_operation(req, responder, resource)
32
+
33
+
34
+ def integrate_scout(hug_module_name, config):
35
+ http_interface = hug.API(hug_module_name).http
36
+ scout_middleware = ScoutMiddleware(
37
+ config=config,
38
+ hug_http_interface=http_interface,
39
+ )
40
+ http_interface.add_middleware(scout_middleware)
@@ -0,0 +1,21 @@
1
+ # coding=utf-8
2
+
3
+ import importlib
4
+ import logging
5
+
6
+ from scout_apm.core.config import scout_config
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ instrument_names = ["elasticsearch", "jinja2", "pymongo", "redis", "urllib3"]
11
+
12
+
13
+ def ensure_all_installed():
14
+ disabled_instruments = scout_config.value("disabled_instruments")
15
+ for instrument_name in instrument_names:
16
+ if instrument_name in disabled_instruments:
17
+ logger.info("%s instrument is disabled. Skipping.", instrument_name)
18
+ continue
19
+
20
+ module = importlib.import_module("{}.{}".format(__name__, instrument_name))
21
+ module.ensure_installed()