10000 feat: RQ integration (#139) · etherscan-io/sentry-python@0048920 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0048920

Browse files
authored
feat: RQ integration (getsentry#139)
* feat: RQ integration * fix: Build matrix
1 parent 583265a commit 0048920

File tree

5 files changed

+148
-0
lines changed

5 files changed

+148
-0
lines changed

sentry_sdk/integrations/celery.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def _process_failure_signal(sender, task_id, einfo, **kw):
3939
return
4040

4141
if isinstance(einfo.exception, SoftTimeLimitExceeded):
42+
# TODO: Move this into event processor
4243
with hub.push_scope() as scope:
4344
scope.fingerprint = [
4445
"celery",

sentry_sdk/integrations/rq.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from __future__ import absolute_import
2+
3+
import weakref
4+
5+
from sentry_sdk.hub import Hub
6+
from sentry_sdk.integrations import Integration
7+
from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
8+
9+
from rq.timeouts import JobTimeoutException
10+
from rq.worker import Worker
11+
12+
13+
class RqIntegration(Integration):
14+
identifier = "rq"
15+
16+
@staticmethod
17+
def setup_once():
18+
19+
old_perform_job = Worker.perform_job
20+
21+
def sentry_patched_perform_job(self, job, *args, **kwargs):
22+
hub = Hub.current
23+
integration = hub.get_integration(RqIntegration)
24+
25+
if integration is None:
26+
return old_perform_job(self, job, *args, **kwargs)
27+
28+
with hub.push_scope() as scope:
29+
scope.add_event_processor(_make_event_processor(weakref.ref(job)))
30+
return old_perform_job(self, job, *args, **kwargs)
31+
32+
Worker.perform_job = sentry_patched_perform_job
33+
34+
old_handle_exception = Worker.handle_exception
35+
36+
def sentry_patched_handle_exception(self, job, *exc_info, **kwargs):
37+
_capture_exception(exc_info)
38+
return old_handle_exception(self, job, *exc_info, **kwargs)
39+
40+
Worker.handle_exception = sentry_patched_handle_exception
41+
42+
43+
def _make_event_processor(weak_job):
44+
def event_processor(event, hint):
45+
job = weak_job()
46+
if job is not None:
47+
with capture_internal_exceptions():
48+
if "transaction" not in event:
49+
event["transaction"] = job.func_name
50+
51+
with capture_internal_exceptions():
52+
extra = event.setdefault("extra", {})
53+
extra["rq-job"] = {
54+
"job_id": job.id,
55+
"func": job.func_name,
56+
"args": job.args,
57+
"kwargs": job.kwargs,
58+
"description": job.description,
59+
}
60+
61+
if "exc_info" in hint:
62+
with capture_internal_exceptions():
63+
if issubclass(hint["exc_info"][0], JobTimeoutException):
64+
event["fingerprint"] = ["rq", "JobTimeoutException", job.func_name]
65+
66+
return event
67+
68+
return event_processor
69+
70+
71+
def _capture_exception(exc_info, **kwargs):
72+
hub = Hub.current
73+
if hub.get_integration(RqIntegration) is None:
74+
return
75+
event, hint = event_from_exception(
76+
exc_info,
77+
client_options=hub.client.options,
78+
mechanism={"type": "rq", "handled": False},
79+
)
80+
81+
hub.capture_event(event, hint=hint)

tests/integrations/rq/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import pytest
2+
3+
rq = pytest.importorskip("rq")

tests/integrations/rq/test_rq.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from sentry_sdk.integrations.rq import RqIntegration
2+
3+
import pytest
4+
5+
from fakeredis import FakeStrictRedis
6+
from rq import SimpleWorker, Queue
7+
8+
9+
@pytest.fixture
10+
def run_job():
11+
queue = Queue(connection=FakeStrictRedis())
12+
worker = SimpleWorker([queue], connection=queue.connection)
13+
14+
def inner(fn, *a, **kw):
15+
job = queue.enqueue(fn, *a, **kw)
16+
worker.work(burst=True)
17+
return job
18+
19+
return inner
20+
21+
22+
def crashing_job(foo):
23+
1 / 0
24+
25+
26+
def test_basic(sentry_init, capture_events, run_job):
27+
sentry_init(integrations=[RqIntegration()])
28+
29+
events = capture_events()
30+
run_job(crashing_job, foo=42)
31+
event, = events
32+
33+
exception, = event["exception"]["values"]
34+
assert exception["type"] == "ZeroDivisionError"
35+
assert exception["mechanism"]["type"] == "rq"
36+
assert exception["stacktrace"]["frames"][-1]["vars"]["foo"] == "42"
37+
38+
assert event["transaction"] == "tests.integrations.rq.test_rq.crashing_job"
39+
assert event["extra"]["rq-job"] == {
40+
"args": [],
41+
"description": "tests.integrations.rq.test_rq.crashing_job(foo=42)",
42+
"func": "tests.integrations.rq.test_rq.crashing_job",
43+
"job_id": event["extra"]["rq-job"]["job_id"],
44+
"kwargs": {"foo": 42},
45+
}

tox.ini

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ envlist =
3030

3131
{pypy,py2.7,py3.5,py3.6,py3.7,py3.8}-pyramid-{1.3,1.4,1.5,1.6,1.7,1.8,1.9}
3232

33+
{pypy,py2.7,py3.5,py3.6}-rq-0.6
34+
{pypy,py2.7,py3.5,py3.6}-rq-0.7
35+
{pypy,py2.7,py3.5,py3.6}-rq-0.8
36+
{pypy,py2.7,py3.5,py3.6}-rq-0.9
37+
{pypy,py2.7,py3.5,py3.6}-rq-0.10
38+
{pypy,py2.7,py3.5,py3.6}-rq-0.11
39+
{pypy,py2.7,py3.5,py3.6,py3.7,py3.8}-rq-0.12
40+
3341
[testenv]
3442
deps =
3543
-r test-requirements.txt
@@ -70,6 +78,15 @@ deps =
7078
pyramid-1.8: pyramid>=1.8,<1.9
7179
pyramid-1.9: pyramid>=1.9,<1.10
7280

81+
rq: fakeredis
82+
rq-0.6: rq>=0.6,<0.7
83+
rq-0.7: rq>=0.7,<0.8
84+
rq-0.8: rq>=0.8,<0.9
85+
rq-0.9: rq>=0.9,<0.10
86+
rq-0.10: rq>=0.10,<0.11
87+
rq-0.11: rq>=0.11,<0.12
88+
rq-0.12: rq>=0.12,<0.13
89+
7390
linters: black
7491
linters: flake8
7592
setenv =
@@ -82,6 +99,7 @@ setenv =
8299
aws_lambda: TESTPATH=tests/integrations/aws_lambda
83100
sanic: TESTPATH=tests/integrations/sanic
84101
pyramid: TESTPATH=tests/integrations/pyramid
102+
rq: TESTPATH=tests/integrations/rq
85103
passenv =
86104
AWS_ACCESS_KEY_ID
87105
AWS_SECRET_ACCESS_KEY

0 commit comments

Comments
 (0)
0