Skip to content

Commit 9b7c240

Browse files
authored
feat: celery integration (#8)
* feat: celery integration * chore: skip tests if celery not installed
1 parent 2b69aa6 commit 9b7c240

File tree

6 files changed

+160
-55
lines changed

6 files changed

+160
-55
lines changed

sentry_sdk/hub.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,25 +112,32 @@ def capture_exception(self, error=None):
112112
return
113113
if error is None:
114114
exc_type, exc_value, tb = sys.exc_info()
115+
elif isinstance(error, tuple) and len(error) == 3:
116+
exc_type, exc_value, tb = error
115117
else:
116118
tb = getattr(error, '__traceback__', None)
117119
if tb is not None:
118120
exc_type = type(error)
119121
exc_value = error
120122
else:
121123
exc_type, exc_value, tb = sys.exc_info()
122-
tb = skip_internal_frames(tb)
123124
if exc_value is not error:
124125
tb = None
125126
exc_value = error
126127
exc_type = type(error)
127128

129+
if tb is not None:
130+
tb = skip_internal_frames(tb)
131+
128132
event = create_event()
129-
event['exception'] = {
130-
'values': exceptions_from_error_tuple(
131-
exc_type, exc_value, tb, client.options['with_locals'])
132-
}
133-
return self.capture_event(event)
133+
try:
134+
event['exception'] = {
135+
'values': exceptions_from_error_tuple(
136+
exc_type, exc_value, tb, client.options['with_locals'])
137+
}
138+
return self.capture_event(event)
139+
except Exception:
140+
capture_internal_exception()
134141

135142
def capture_internal_exception(self, error=None):
136143
"""Capture an exception that is likely caused by a bug in the SDK

sentry_sdk/integrations/celery.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from __future__ import absolute_import
2+
3+
from celery.signals import (
4+
after_setup_logger, task_failure, task_prerun, task_postrun
5+
)
6+
7+
from sentry_sdk import get_current_hub, configure_scope, capture_exception
8+
from sentry_sdk.utils import ContextVar
9+
10+
11+
def install():
12+
task_prerun.connect(_handle_task_prerun, weak=False)
13+
task_postrun.connect(_handle_task_postrun, weak=False)
14+
task_failure.connect(_process_failure_signal, weak=False)
15+
16+
17+
def _process_failure_signal(sender, task_id, einfo, **kw):
18+
if hasattr(sender, 'throws') and isinstance(einfo.exception, sender.throws):
19+
return
20+
21+
capture_exception(einfo.exc_info)
22+
23+
24+
_task_scope = ContextVar('sentry_celery_task_scope')
25+
26+
27+
def _handle_task_prerun(sender, task, **kw):
28+
try:
29+
assert _task_scope.get(None) is None, 'race condition'
30+
_task_scope.set(get_current_hub().push_scope().__enter__())
31+
32+
with configure_scope() as scope:
33+
scope.transaction = task.name
34+
except Exception:
35+
get_current_hub().capture_internal_exception()
36+
37+
38+
def _handle_task_postrun(sender, task_id, task, **kw):
39+
try:
40+
val = _task_scope.get(None)
41+
assert val is not None, 'race condition'
42+
val.__exit__(None, None, None)
43+
_task_scope.set(None)
44+
except Exception:
45+
get_current_hub().capture_internal_exception()

sentry_sdk/integrations/django/__init__.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,24 @@ def _get_transaction_from_request(request):
3131
# not yet available
3232
class SentryMiddleware(MiddlewareMixin):
3333
def process_request(self, request):
34-
assert _request_scope.get(None) is None, 'race condition'
35-
_request_scope.set(get_current_hub().push_scope().__enter__())
36-
3734
try:
35+
assert _request_scope.get(None) is None, 'race condition'
36+
_request_scope.set(get_current_hub().push_scope().__enter__())
37+
3838
with configure_scope() as scope:
3939
scope.transaction = _get_transaction_from_request(request)
4040
except Exception:
41-
capture_exception()
41+
get_current_hub().capture_internal_exception()
4242

4343

4444
def _request_finished(*args, **kwargs):
45-
val = _request_scope.get(None)
46-
assert val is not None, 'race condition'
47-
val.__exit__(None, None, None)
48-
_request_scope.set(None)
45+
try:
46+
val = _request_scope.get(None)
47+
assert val is not None, 'race condition'
48+
val.__exit__(None, None, None)
49+
_request_scope.set(None)
50+
except Exception:
51+
get_current_hub().capture_internal_exception()
4952

5053

5154
def _got_request_exception(request=None, **kwargs):

sentry_sdk/integrations/flask.py

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -31,50 +31,59 @@ def init_app(self, app):
3131

3232

3333
def _push_appctx(*args, **kwargs):
34-
assert getattr(
35-
_app_ctx_stack.top,
36-
'_sentry_app_scope_manager',
37-
None
38-
) is None, 'race condition'
39-
_app_ctx_stack.top._sentry_app_scope_manager = \
40-
get_current_hub().push_scope().__enter__()
34+
try:
35+
assert getattr(
36+
_app_ctx_stack.top,
37+
'_sentry_app_scope_manager',
38+
None
39+
) is None, 'race condition'
40+
_app_ctx_stack.top._sentry_app_scope_manager = \
41+
get_current_hub().push_scope().__enter__()
42+
except Exception:
43+
get_current_hub().capture_internal_exception()
4144

4245

4346
def _pop_appctx(exception):
44-
assert getattr(
45-
_app_ctx_stack.top,
46-
'_sentry_app_scope_manager',
47-
None
48-
) is not None, 'race condition'
49-
_app_ctx_stack.top._sentry_app_scope_manager.__exit__(None, None, None)
50-
_app_ctx_stack.top._sentry_app_scope_manager = None
47+
try:
48+
assert getattr(
49+
_app_ctx_stack.top,
50+
'_sentry_app_scope_manager',
51+
None
52+
) is not None, 'race condition'
53+
_app_ctx_stack.top._sentry_app_scope_manager.__exit__(None, None, None)
54+
_app_ctx_stack.top._sentry_app_scope_manager = None
55+
except Exception:
56+
get_current_hub().capture_internal_exception()
5157

5258

5359
def _capture_exception(sender, exception, **kwargs):
5460
capture_exception(exception)
5561

5662

5763
def _before_request(*args, **kwargs):
58-
assert getattr(
59-
_app_ctx_stack.top,
60-
'_sentry_app_scope_manager',
61-
None
62-
) is not None, 'scope push failed'
63-
64-
with configure_scope() as scope:
65-
if request.url_rule:
66-
scope.transaction = request.url_rule.endpoint
67-
68-
try:
69-
scope.request = _get_request_info()
70-
except Exception:
71-
get_current_hub().capture_internal_exception()
72-
73-
try:
74-
scope.user = _get_user_info()
75-
except Exception:
76-
raise
77-
get_current_hub().capture_internal_exception()
64+
try:
65+
assert getattr(
66+
_app_ctx_stack.top,
67+
'_sentry_app_scope_manager',
68+
None
69+
) is not None, 'scope push failed'
70+
71+
with configure_scope() as scope:
72+
if request.url_rule:
73+
scope.transaction = request.url_rule.endpoint
74+
75+
try:
76+
scope.request = _get_request_info()
77+
except Exception:
78+
get_current_hub().capture_internal_exception()
79+
80+
try:
81+
scope.user = _get_user_info()
82+
except Exception:
83+
raise
84+
get_current_hub().capture_internal_exception()
85+
except Exception:
86+
get_current_hub().capture_internal_exception()
7887

7988
def _get_request_info():
8089
{
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import pytest
2+
3+
pytest.importorskip('celery')
4+
5+
from celery import Celery
6+
7+
from sentry_sdk.integrations.celery import install
8+
9+
install()
10+
11+
12+
@pytest.fixture
13+
def celery():
14+
celery = Celery(__name__)
15+
celery.conf.CELERY_ALWAYS_EAGER = True
16+
return celery
17+
18+
19+
def test_simple(capture_events, celery):
20+
@celery.task(name='dummy_task')
21+
def dummy_task(x, y):
22+
return x / y
23+
24+
dummy_task.delay(1, 2)
25+
dummy_task.delay(1, 0)
26+
event, = capture_events
27+
assert event['transaction'] == 'dummy_task'
28+
29+
exception, = event['exception']['values']
30+
assert exception['type'] == 'ZeroDivisionError'
31+
32+
33+
def test_ignore_expected(capture_events, celery):
34+
@celery.task(name='dummy_task', throws=(ZeroDivisionError,))
35+
def dummy_task(x, y):
36+
return x / y
37+
38+
dummy_task.delay(1, 2)
39+
dummy_task.delay(1, 0)
40+
assert not capture_events

tox.ini

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ envlist =
1818
{py27,py34,py35}-django-18
1919
{py27,py34}-django-17
2020
py27-django-16
21-
{py27,py35}-flask-{10,11}
22-
py35-flask-12
23-
py35-flask-dev
21+
22+
{py27,py35,py36,py37}-flask-{10,11,12,dev}
23+
24+
{py27,py35,py36,py37}-celery-4
25+
py27-celery-3
2426

2527

2628
[testenv]
@@ -42,12 +44,11 @@ deps =
4244
flask-11: Flask>=0.11,<0.12
4345
flask-12: Flask>=0.12,<0.13
4446
flask-dev: git+https://github.com/pallets/flask.git#egg=flask
47+
celery-3: Celery>=3.1,<4.0
48+
celery-4: Celery>=4.0,<5.0
4549
flask: flask-login
4650
setenv =
4751
PYTHONDONTWRITEBYTECODE=1
48-
TESTPATH=tests
49-
django: TESTPATH=tests/integrations/django
50-
flask: TESTPATH=tests/integrations/flask
5152
usedevelop = True
5253
extras =
5354
flask: flask
@@ -61,4 +62,4 @@ basepython =
6162
pypy: pypy
6263

6364
commands =
64-
py.test {env:TESTPATH} {posargs}
65+
py.test {posargs}

0 commit comments

Comments
 (0)