plainx-sentry 0.3.1__tar.gz → 0.4.1__tar.gz
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.
- {plainx_sentry-0.3.1 → plainx_sentry-0.4.1}/PKG-INFO +2 -2
- plainx_sentry-0.4.1/plainx/sentry/middleware.py +154 -0
- {plainx_sentry-0.3.1 → plainx_sentry-0.4.1}/pyproject.toml +2 -2
- {plainx_sentry-0.3.1 → plainx_sentry-0.4.1}/uv.lock +27 -27
- plainx_sentry-0.3.1/plainx/sentry/middleware.py +0 -144
- {plainx_sentry-0.3.1 → plainx_sentry-0.4.1}/.gitignore +0 -0
- {plainx_sentry-0.3.1 → plainx_sentry-0.4.1}/README.md +0 -0
- {plainx_sentry-0.3.1 → plainx_sentry-0.4.1}/plainx/sentry/__init__.py +0 -0
- {plainx_sentry-0.3.1 → plainx_sentry-0.4.1}/plainx/sentry/config.py +0 -0
- {plainx_sentry-0.3.1 → plainx_sentry-0.4.1}/plainx/sentry/default_settings.py +0 -0
- {plainx_sentry-0.3.1 → plainx_sentry-0.4.1}/plainx/sentry/templates/sentry/js.html +0 -0
- {plainx_sentry-0.3.1 → plainx_sentry-0.4.1}/plainx/sentry/templates.py +0 -0
- {plainx_sentry-0.3.1 → plainx_sentry-0.4.1}/scripts/install +0 -0
- {plainx_sentry-0.3.1 → plainx_sentry-0.4.1}/scripts/release +0 -0
- {plainx_sentry-0.3.1 → plainx_sentry-0.4.1}/scripts/test +0 -0
- {plainx_sentry-0.3.1 → plainx_sentry-0.4.1}/tests/settings.py +0 -0
- {plainx_sentry-0.3.1 → plainx_sentry-0.4.1}/tests/templates/index.html +0 -0
- {plainx_sentry-0.3.1 → plainx_sentry-0.4.1}/tests/test_sentry.py +0 -0
- {plainx_sentry-0.3.1 → plainx_sentry-0.4.1}/tests/urls.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plainx-sentry
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Author-email: Dave Gaeddert <dave.gaeddert@gmail.com>
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
|
-
Requires-Dist: sentry-sdk>=
|
|
6
|
+
Requires-Dist: sentry-sdk>=2.24.0
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
|
|
9
9
|
# plainx-sentry
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import sentry_sdk
|
|
2
|
+
from plain.runtime import settings
|
|
3
|
+
from sentry_sdk.tracing import TransactionSource
|
|
4
|
+
from sentry_sdk.utils import capture_internal_exceptions
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
from plain.models.db import connection
|
|
8
|
+
except ImportError:
|
|
9
|
+
connection = None
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def trace_db(execute, sql, params, many, context):
|
|
17
|
+
with sentry_sdk.start_span(op="db", description=sql) as span:
|
|
18
|
+
# Mostly borrowed from the Sentry Django integration...
|
|
19
|
+
data = {
|
|
20
|
+
"db.params": params,
|
|
21
|
+
"db.executemany": many,
|
|
22
|
+
"db.system": connection.vendor,
|
|
23
|
+
"db.name": connection.settings_dict.get("NAME"),
|
|
24
|
+
"db.user": connection.settings_dict.get("USER"),
|
|
25
|
+
"server.address": connection.settings_dict.get("HOST"),
|
|
26
|
+
"server.port": connection.settings_dict.get("PORT"),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
sentry_sdk.add_breadcrumb(message=sql, category="query", data=data)
|
|
30
|
+
|
|
31
|
+
for k, v in data.items():
|
|
32
|
+
span.set_data(k, v)
|
|
33
|
+
|
|
34
|
+
result = execute(sql, params, many, context)
|
|
35
|
+
|
|
36
|
+
return result
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class SentryMiddleware:
|
|
40
|
+
def __init__(self, get_response):
|
|
41
|
+
self.get_response = get_response
|
|
42
|
+
|
|
43
|
+
def __call__(self, request):
|
|
44
|
+
# Don't do anything if Sentry is not active
|
|
45
|
+
if not sentry_sdk.get_client().is_active():
|
|
46
|
+
return self.get_response(request)
|
|
47
|
+
|
|
48
|
+
def event_processor(event, hint):
|
|
49
|
+
# request gets attached directly to an event,
|
|
50
|
+
# not necessarily in the "context"
|
|
51
|
+
request_info = event.setdefault("request", {})
|
|
52
|
+
request_info["url"] = request.build_absolute_uri()
|
|
53
|
+
request_info["method"] = request.method
|
|
54
|
+
request_info["query_string"] = request.META.get("QUERY_STRING", "")
|
|
55
|
+
# Headers and env need some PII filtering, ideally,
|
|
56
|
+
# among other filters... similar for GET/POST data?
|
|
57
|
+
# request_info["headers"] = dict(request.headers)
|
|
58
|
+
try:
|
|
59
|
+
request_info["data"] = request.body.decode("utf-8")
|
|
60
|
+
except Exception:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
if user := getattr(request, "user", None):
|
|
64
|
+
event["user"] = {"id": str(user.pk)}
|
|
65
|
+
if settings.SENTRY_PII_ENABLED:
|
|
66
|
+
if email := getattr(user, "email", None):
|
|
67
|
+
event["user"]["email"] = email
|
|
68
|
+
if username := getattr(user, "username", None):
|
|
69
|
+
event["user"]["username"] = username
|
|
70
|
+
|
|
71
|
+
return event
|
|
72
|
+
|
|
73
|
+
with sentry_sdk.isolation_scope() as scope:
|
|
74
|
+
# Reset the scope (and breadcrumbs) for each request
|
|
75
|
+
scope.clear()
|
|
76
|
+
scope.add_event_processor(event_processor)
|
|
77
|
+
|
|
78
|
+
# Sentry's Django integration patches the WSGIHandler.
|
|
79
|
+
# We could make our own WSGIHandler and patch it or call it directly from gunicorn,
|
|
80
|
+
# but putting our middleware at the top of MIDDLEWARE is pretty close and easier.
|
|
81
|
+
with sentry_sdk.start_transaction(
|
|
82
|
+
op="http.server", name=request.path_info
|
|
83
|
+
) as transaction:
|
|
84
|
+
if connection:
|
|
85
|
+
# Also get spans for db queries
|
|
86
|
+
with connection.execute_wrapper(trace_db):
|
|
87
|
+
response = self.get_response(request)
|
|
88
|
+
else:
|
|
89
|
+
# No db presumably
|
|
90
|
+
response = self.get_response(request)
|
|
91
|
+
|
|
92
|
+
if resolver_match := getattr(request, "resolver_match", None):
|
|
93
|
+
# Rename the transaction using a pattern,
|
|
94
|
+
# and attach other url/views tags we can use to filter
|
|
95
|
+
transaction.name = f"route:{resolver_match.route}"
|
|
96
|
+
transaction.set_tag("url_namespace", resolver_match.namespace)
|
|
97
|
+
transaction.set_tag("url_name", resolver_match.url_name)
|
|
98
|
+
transaction.set_tag("view_name", resolver_match.view_name)
|
|
99
|
+
transaction.set_tag("view_class", resolver_match._func_path)
|
|
100
|
+
# Don't need to filter on this, but do want the context to view
|
|
101
|
+
transaction.set_context(
|
|
102
|
+
"url_params",
|
|
103
|
+
{
|
|
104
|
+
"args": resolver_match.args,
|
|
105
|
+
"kwargs": resolver_match.kwargs,
|
|
106
|
+
},
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
transaction.set_http_status(response.status_code)
|
|
110
|
+
|
|
111
|
+
return response
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class SentryWorkerMiddleware:
|
|
115
|
+
def __init__(self, run_job):
|
|
116
|
+
self.run_job = run_job
|
|
117
|
+
|
|
118
|
+
def __call__(self, job):
|
|
119
|
+
# Don't do anything if Sentry is not active
|
|
120
|
+
if not sentry_sdk.get_client().is_active():
|
|
121
|
+
return self.run_job(job)
|
|
122
|
+
|
|
123
|
+
def event_processor(event, hint):
|
|
124
|
+
with capture_internal_exceptions():
|
|
125
|
+
# Attach it directly to any events
|
|
126
|
+
extra = event.setdefault("extra", {})
|
|
127
|
+
extra["plain.worker"] = {"job": job.as_json()}
|
|
128
|
+
return event
|
|
129
|
+
|
|
130
|
+
with sentry_sdk.isolation_scope() as scope:
|
|
131
|
+
# Reset the scope (and breadcrumbs) for each request
|
|
132
|
+
scope.clear()
|
|
133
|
+
scope.add_event_processor(event_processor)
|
|
134
|
+
|
|
135
|
+
with sentry_sdk.start_transaction(
|
|
136
|
+
op="plain.worker.job",
|
|
137
|
+
name=f"job:{job.job_class}",
|
|
138
|
+
source=TransactionSource.TASK,
|
|
139
|
+
) as transaction:
|
|
140
|
+
if connection:
|
|
141
|
+
# Also get spans for db queries
|
|
142
|
+
with connection.execute_wrapper(trace_db):
|
|
143
|
+
job_result = self.run_job(job)
|
|
144
|
+
else:
|
|
145
|
+
# No db presumably
|
|
146
|
+
job_result = self.run_job(job)
|
|
147
|
+
|
|
148
|
+
with capture_internal_exceptions():
|
|
149
|
+
# Don't need to filter on this, but do want the context to view
|
|
150
|
+
transaction.set_context("job", job.as_json())
|
|
151
|
+
|
|
152
|
+
transaction.set_status("ok")
|
|
153
|
+
|
|
154
|
+
return job_result
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "plainx-sentry"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.1"
|
|
4
4
|
description = ""
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -8,7 +8,7 @@ authors = [
|
|
|
8
8
|
]
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
dependencies = [
|
|
11
|
-
"sentry-sdk>=
|
|
11
|
+
"sentry-sdk>=2.24.0",
|
|
12
12
|
]
|
|
13
13
|
|
|
14
14
|
[dependency-groups]
|
|
@@ -34,11 +34,11 @@ wheels = [
|
|
|
34
34
|
|
|
35
35
|
[[package]]
|
|
36
36
|
name = "iniconfig"
|
|
37
|
-
version = "2.
|
|
37
|
+
version = "2.1.0"
|
|
38
38
|
source = { registry = "https://pypi.org/simple" }
|
|
39
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
39
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 }
|
|
40
40
|
wheels = [
|
|
41
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
41
|
+
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 },
|
|
42
42
|
]
|
|
43
43
|
|
|
44
44
|
[[package]]
|
|
@@ -153,7 +153,7 @@ wheels = [
|
|
|
153
153
|
|
|
154
154
|
[[package]]
|
|
155
155
|
name = "plainx-sentry"
|
|
156
|
-
version = "0.
|
|
156
|
+
version = "0.4.0"
|
|
157
157
|
source = { editable = "." }
|
|
158
158
|
dependencies = [
|
|
159
159
|
{ name = "sentry-sdk" },
|
|
@@ -167,7 +167,7 @@ dev = [
|
|
|
167
167
|
]
|
|
168
168
|
|
|
169
169
|
[package.metadata]
|
|
170
|
-
requires-dist = [{ name = "sentry-sdk", specifier = ">=
|
|
170
|
+
requires-dist = [{ name = "sentry-sdk", specifier = ">=2.24.0" }]
|
|
171
171
|
|
|
172
172
|
[package.metadata.requires-dev]
|
|
173
173
|
dev = [
|
|
@@ -211,40 +211,40 @@ wheels = [
|
|
|
211
211
|
|
|
212
212
|
[[package]]
|
|
213
213
|
name = "ruff"
|
|
214
|
-
version = "0.
|
|
214
|
+
version = "0.11.2"
|
|
215
215
|
source = { registry = "https://pypi.org/simple" }
|
|
216
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
216
|
+
sdist = { url = "https://files.pythonhosted.org/packages/90/61/fb87430f040e4e577e784e325351186976516faef17d6fcd921fe28edfd7/ruff-0.11.2.tar.gz", hash = "sha256:ec47591497d5a1050175bdf4e1a4e6272cddff7da88a2ad595e1e326041d8d94", size = 3857511 }
|
|
217
217
|
wheels = [
|
|
218
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
219
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
220
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
221
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
222
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
223
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
224
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
225
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
226
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
227
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
228
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
229
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
230
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
231
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
232
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
233
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
234
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
218
|
+
{ url = "https://files.pythonhosted.org/packages/62/99/102578506f0f5fa29fd7e0df0a273864f79af044757aef73d1cae0afe6ad/ruff-0.11.2-py3-none-linux_armv6l.whl", hash = "sha256:c69e20ea49e973f3afec2c06376eb56045709f0212615c1adb0eda35e8a4e477", size = 10113146 },
|
|
219
|
+
{ url = "https://files.pythonhosted.org/packages/74/ad/5cd4ba58ab602a579997a8494b96f10f316e874d7c435bcc1a92e6da1b12/ruff-0.11.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2c5424cc1c4eb1d8ecabe6d4f1b70470b4f24a0c0171356290b1953ad8f0e272", size = 10867092 },
|
|
220
|
+
{ url = "https://files.pythonhosted.org/packages/fc/3e/d3f13619e1d152c7b600a38c1a035e833e794c6625c9a6cea6f63dbf3af4/ruff-0.11.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ecf20854cc73f42171eedb66f006a43d0a21bfb98a2523a809931cda569552d9", size = 10224082 },
|
|
221
|
+
{ url = "https://files.pythonhosted.org/packages/90/06/f77b3d790d24a93f38e3806216f263974909888fd1e826717c3ec956bbcd/ruff-0.11.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c543bf65d5d27240321604cee0633a70c6c25c9a2f2492efa9f6d4b8e4199bb", size = 10394818 },
|
|
222
|
+
{ url = "https://files.pythonhosted.org/packages/99/7f/78aa431d3ddebfc2418cd95b786642557ba8b3cb578c075239da9ce97ff9/ruff-0.11.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20967168cc21195db5830b9224be0e964cc9c8ecf3b5a9e3ce19876e8d3a96e3", size = 9952251 },
|
|
223
|
+
{ url = "https://files.pythonhosted.org/packages/30/3e/f11186d1ddfaca438c3bbff73c6a2fdb5b60e6450cc466129c694b0ab7a2/ruff-0.11.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:955a9ce63483999d9f0b8f0b4a3ad669e53484232853054cc8b9d51ab4c5de74", size = 11563566 },
|
|
224
|
+
{ url = "https://files.pythonhosted.org/packages/22/6c/6ca91befbc0a6539ee133d9a9ce60b1a354db12c3c5d11cfdbf77140f851/ruff-0.11.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:86b3a27c38b8fce73bcd262b0de32e9a6801b76d52cdb3ae4c914515f0cef608", size = 12208721 },
|
|
225
|
+
{ url = "https://files.pythonhosted.org/packages/19/b0/24516a3b850d55b17c03fc399b681c6a549d06ce665915721dc5d6458a5c/ruff-0.11.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3b66a03b248c9fcd9d64d445bafdf1589326bee6fc5c8e92d7562e58883e30f", size = 11662274 },
|
|
226
|
+
{ url = "https://files.pythonhosted.org/packages/d7/65/76be06d28ecb7c6070280cef2bcb20c98fbf99ff60b1c57d2fb9b8771348/ruff-0.11.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0397c2672db015be5aa3d4dac54c69aa012429097ff219392c018e21f5085147", size = 13792284 },
|
|
227
|
+
{ url = "https://files.pythonhosted.org/packages/ce/d2/4ceed7147e05852876f3b5f3fdc23f878ce2b7e0b90dd6e698bda3d20787/ruff-0.11.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:869bcf3f9abf6457fbe39b5a37333aa4eecc52a3b99c98827ccc371a8e5b6f1b", size = 11327861 },
|
|
228
|
+
{ url = "https://files.pythonhosted.org/packages/c4/78/4935ecba13706fd60ebe0e3dc50371f2bdc3d9bc80e68adc32ff93914534/ruff-0.11.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2a2b50ca35457ba785cd8c93ebbe529467594087b527a08d487cf0ee7b3087e9", size = 10276560 },
|
|
229
|
+
{ url = "https://files.pythonhosted.org/packages/81/7f/1b2435c3f5245d410bb5dc80f13ec796454c21fbda12b77d7588d5cf4e29/ruff-0.11.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7c69c74bf53ddcfbc22e6eb2f31211df7f65054bfc1f72288fc71e5f82db3eab", size = 9945091 },
|
|
230
|
+
{ url = "https://files.pythonhosted.org/packages/39/c4/692284c07e6bf2b31d82bb8c32f8840f9d0627d92983edaac991a2b66c0a/ruff-0.11.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6e8fb75e14560f7cf53b15bbc55baf5ecbe373dd5f3aab96ff7aa7777edd7630", size = 10977133 },
|
|
231
|
+
{ url = "https://files.pythonhosted.org/packages/94/cf/8ab81cb7dd7a3b0a3960c2769825038f3adcd75faf46dd6376086df8b128/ruff-0.11.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:842a472d7b4d6f5924e9297aa38149e5dcb1e628773b70e6387ae2c97a63c58f", size = 11378514 },
|
|
232
|
+
{ url = "https://files.pythonhosted.org/packages/d9/3a/a647fa4f316482dacf2fd68e8a386327a33d6eabd8eb2f9a0c3d291ec549/ruff-0.11.2-py3-none-win32.whl", hash = "sha256:aca01ccd0eb5eb7156b324cfaa088586f06a86d9e5314b0eb330cb48415097cc", size = 10319835 },
|
|
233
|
+
{ url = "https://files.pythonhosted.org/packages/86/54/3c12d3af58012a5e2cd7ebdbe9983f4834af3f8cbea0e8a8c74fa1e23b2b/ruff-0.11.2-py3-none-win_amd64.whl", hash = "sha256:3170150172a8f994136c0c66f494edf199a0bbea7a409f649e4bc8f4d7084080", size = 11373713 },
|
|
234
|
+
{ url = "https://files.pythonhosted.org/packages/d6/d4/dd813703af8a1e2ac33bf3feb27e8a5ad514c9f219df80c64d69807e7f71/ruff-0.11.2-py3-none-win_arm64.whl", hash = "sha256:52933095158ff328f4c77af3d74f0379e34fd52f175144cefc1b192e7ccd32b4", size = 10441990 },
|
|
235
235
|
]
|
|
236
236
|
|
|
237
237
|
[[package]]
|
|
238
238
|
name = "sentry-sdk"
|
|
239
|
-
version = "2.
|
|
239
|
+
version = "2.24.1"
|
|
240
240
|
source = { registry = "https://pypi.org/simple" }
|
|
241
241
|
dependencies = [
|
|
242
242
|
{ name = "certifi" },
|
|
243
243
|
{ name = "urllib3" },
|
|
244
244
|
]
|
|
245
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
245
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f1/ef/4847dcd63e3f3c451cf701a825d21200f1322d46ac97586d5c90a13dfea1/sentry_sdk-2.24.1.tar.gz", hash = "sha256:8ba3c29990fa48865b908b3b9dc5ae7fa7e72407c7c9e91303e5206b32d7b8b1", size = 318124 }
|
|
246
246
|
wheels = [
|
|
247
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
247
|
+
{ url = "https://files.pythonhosted.org/packages/65/95/91137ffe7a5956496155af5ffbe45ee4ddfa795a569136147e766abd14b1/sentry_sdk-2.24.1-py2.py3-none-any.whl", hash = "sha256:36baa6a1128b9d98d2adc5e9b2f887eff0a6af558fc2b96ed51919042413556d", size = 336945 },
|
|
248
248
|
]
|
|
249
249
|
|
|
250
250
|
[[package]]
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import sentry_sdk
|
|
2
|
-
from plain.runtime import settings
|
|
3
|
-
from sentry_sdk.tracing import TRANSACTION_SOURCE_TASK
|
|
4
|
-
from sentry_sdk.utils import capture_internal_exceptions
|
|
5
|
-
|
|
6
|
-
try:
|
|
7
|
-
from plain.models.db import connection
|
|
8
|
-
except ImportError:
|
|
9
|
-
connection = None
|
|
10
|
-
|
|
11
|
-
import logging
|
|
12
|
-
|
|
13
|
-
logger = logging.getLogger(__name__)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def trace_db(execute, sql, params, many, context):
|
|
17
|
-
with sentry_sdk.start_span(op="db", description=sql) as span:
|
|
18
|
-
# Mostly borrowed from the Sentry Django integration...
|
|
19
|
-
data = {
|
|
20
|
-
"db.params": params,
|
|
21
|
-
"db.executemany": many,
|
|
22
|
-
"db.system": connection.vendor,
|
|
23
|
-
"db.name": connection.settings_dict.get("NAME"),
|
|
24
|
-
"db.user": connection.settings_dict.get("USER"),
|
|
25
|
-
"server.address": connection.settings_dict.get("HOST"),
|
|
26
|
-
"server.port": connection.settings_dict.get("PORT"),
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
sentry_sdk.add_breadcrumb(message=sql, category="query", data=data)
|
|
30
|
-
|
|
31
|
-
for k, v in data.items():
|
|
32
|
-
span.set_data(k, v)
|
|
33
|
-
|
|
34
|
-
result = execute(sql, params, many, context)
|
|
35
|
-
|
|
36
|
-
return result
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class SentryMiddleware:
|
|
40
|
-
def __init__(self, get_response):
|
|
41
|
-
self.get_response = get_response
|
|
42
|
-
|
|
43
|
-
def __call__(self, request):
|
|
44
|
-
def event_processor(event, hint):
|
|
45
|
-
# request gets attached directly to an event,
|
|
46
|
-
# not necessarily in the "context"
|
|
47
|
-
request_info = event.setdefault("request", {})
|
|
48
|
-
request_info["url"] = request.build_absolute_uri()
|
|
49
|
-
request_info["method"] = request.method
|
|
50
|
-
request_info["query_string"] = request.META.get("QUERY_STRING", "")
|
|
51
|
-
# Headers and env need some PII filtering, ideally,
|
|
52
|
-
# among other filters... similar for GET/POST data?
|
|
53
|
-
# request_info["headers"] = dict(request.headers)
|
|
54
|
-
try:
|
|
55
|
-
request_info["data"] = request.body.decode("utf-8")
|
|
56
|
-
except Exception:
|
|
57
|
-
pass
|
|
58
|
-
|
|
59
|
-
if user := getattr(request, "user", None):
|
|
60
|
-
event["user"] = {"id": str(user.pk)}
|
|
61
|
-
if settings.SENTRY_PII_ENABLED:
|
|
62
|
-
if email := getattr(user, "email", None):
|
|
63
|
-
event["user"]["email"] = email
|
|
64
|
-
if username := getattr(user, "username", None):
|
|
65
|
-
event["user"]["username"] = username
|
|
66
|
-
|
|
67
|
-
return event
|
|
68
|
-
|
|
69
|
-
# Reset the scope (and breadcrumbs) for each request
|
|
70
|
-
scope = sentry_sdk.get_isolation_scope()
|
|
71
|
-
scope.add_event_processor(event_processor)
|
|
72
|
-
|
|
73
|
-
# Sentry's Django integration patches the WSGIHandler.
|
|
74
|
-
# We could make our own WSGIHandler and patch it or call it directly from gunicorn,
|
|
75
|
-
# but putting our middleware at the top of MIDDLEWARE is pretty close and easier.
|
|
76
|
-
with sentry_sdk.start_transaction(
|
|
77
|
-
op="http.server", name=request.path_info
|
|
78
|
-
) as transaction:
|
|
79
|
-
if connection:
|
|
80
|
-
# Also get spans for db queries
|
|
81
|
-
with connection.execute_wrapper(trace_db):
|
|
82
|
-
response = self.get_response(request)
|
|
83
|
-
else:
|
|
84
|
-
# No db presumably
|
|
85
|
-
response = self.get_response(request)
|
|
86
|
-
|
|
87
|
-
if resolver_match := getattr(request, "resolver_match", None):
|
|
88
|
-
# Rename the transaction using a pattern,
|
|
89
|
-
# and attach other url/views tags we can use to filter
|
|
90
|
-
transaction.name = f"route:{resolver_match.route}"
|
|
91
|
-
transaction.set_tag("url_namespace", resolver_match.namespace)
|
|
92
|
-
transaction.set_tag("url_name", resolver_match.url_name)
|
|
93
|
-
transaction.set_tag("view_name", resolver_match.view_name)
|
|
94
|
-
transaction.set_tag("view_class", resolver_match._func_path)
|
|
95
|
-
# Don't need to filter on this, but do want the context to view
|
|
96
|
-
transaction.set_context(
|
|
97
|
-
"url_params",
|
|
98
|
-
{
|
|
99
|
-
"args": resolver_match.args,
|
|
100
|
-
"kwargs": resolver_match.kwargs,
|
|
101
|
-
},
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
transaction.set_http_status(response.status_code)
|
|
105
|
-
|
|
106
|
-
return response
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
class SentryWorkerMiddleware:
|
|
110
|
-
def __init__(self, run_job):
|
|
111
|
-
self.run_job = run_job
|
|
112
|
-
|
|
113
|
-
def __call__(self, job):
|
|
114
|
-
def event_processor(event, hint):
|
|
115
|
-
with capture_internal_exceptions():
|
|
116
|
-
# Attach it directly to any events
|
|
117
|
-
extra = event.setdefault("extra", {})
|
|
118
|
-
extra["plain.worker"] = {"job": job.as_json()}
|
|
119
|
-
return event
|
|
120
|
-
|
|
121
|
-
# Reset the scope (and breadcrumbs) for each job
|
|
122
|
-
scope = sentry_sdk.get_isolation_scope()
|
|
123
|
-
scope.add_event_processor(event_processor)
|
|
124
|
-
|
|
125
|
-
with sentry_sdk.start_transaction(
|
|
126
|
-
op="plain.worker.job",
|
|
127
|
-
name=f"job:{job.job_class}",
|
|
128
|
-
source=TRANSACTION_SOURCE_TASK,
|
|
129
|
-
) as transaction:
|
|
130
|
-
if connection:
|
|
131
|
-
# Also get spans for db queries
|
|
132
|
-
with connection.execute_wrapper(trace_db):
|
|
133
|
-
job_result = self.run_job(job)
|
|
134
|
-
else:
|
|
135
|
-
# No db presumably
|
|
136
|
-
job_result = self.run_job(job)
|
|
137
|
-
|
|
138
|
-
with capture_internal_exceptions():
|
|
139
|
-
# Don't need to filter on this, but do want the context to view
|
|
140
|
-
transaction.set_context("job", job.as_json())
|
|
141
|
-
|
|
142
|
-
transaction.set_status("ok")
|
|
143
|
-
|
|
144
|
-
return job_result
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|