Skip to content
This repository was archived by the owner on Oct 23, 2023. It is now read-only.

Commit 1f3619a

Browse files
committed
Add the Client.install_asyncio_hook function.
1 parent 5fdc04d commit 1f3619a

File tree

3 files changed

+100
-0
lines changed

3 files changed

+100
-0
lines changed

docs/advanced.rst

+16
Original file line numberDiff line numberDiff line change
@@ -302,3 +302,19 @@ A Note on uWSGI
302302
If you're using uWSGI you will need to add ``enable-threads`` to the
303303
default invocation, or you will need to switch off of the threaded default
304304
transport.
305+
306+
Integration with asyncio
307+
---------------
308+
309+
asyncio introduces additional level of exception handling: exceptions emitted by tasks and futures
310+
must be explicitly handled and are not automatically propagated into ``sys.excepthook``.
311+
Unhandled exceptions (exceptions that were not retrieved before object's destruction) are passed into
312+
loop's own exception handler.
313+
314+
To handle them the per-loop exception handler can be installed:
315+
316+
.. code-block:: python
317+
318+
client.install_asyncio_hook()
319+
320+
The function accepts one optional argument: ``loop``. It defaults to ``asyncio.get_event_loop()``.

raven/base.py

+62
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,16 @@ def get_excepthook_client():
7373
return client
7474

7575

76+
def get_loop_excepthook_client(loop=None):
77+
import asyncio
78+
79+
loop = loop or asyncio.get_event_loop()
80+
hook = loop.get_exception_handler()
81+
client = getattr(hook, 'raven_client', None)
82+
if client is not None:
83+
return client
84+
85+
7686
class ModuleProxyCache(dict):
7787
def __missing__(self, key):
7888
module, class_name = key.rsplit('.', 1)
@@ -283,6 +293,58 @@ def install_logging_hook(self):
283293
from raven.breadcrumbs import install_logging_hook
284294
install_logging_hook()
285295

296+
def install_asyncio_hook(self, loop=None):
297+
import asyncio
298+
299+
loop = loop or asyncio.get_event_loop()
300+
301+
try:
302+
loop_except_handler = loop.get_exception_handler()
303+
except AttributeError:
304+
# No get_exception_handler before Python 3.5.2
305+
loop_except_handler = getattr(loop, '_exception_handler', None)
306+
307+
if not loop_except_handler:
308+
loop_except_handler = type(loop).default_exception_handler
309+
310+
def handle_exception(loop, context):
311+
if 'exception' in context:
312+
exception = context['exception']
313+
exc_info = type(exception), exception, exception.__traceback__
314+
self.captureException(exc_info=exc_info, level='exception') # asyncio exceptions are non-fatal
315+
else:
316+
data = {}
317+
318+
if 'source_traceback' in context:
319+
tb = context['source_traceback']
320+
elif 'handle' in context and getattr(context['handle'], '_source_traceback', None):
321+
tb = context['handle']._source_traceback
322+
elif 'future' in context and getattr(context['future'], '_source_traceback', None):
323+
tb = context['future']._source_traceback
324+
else:
325+
tb = None
326+
327+
if tb:
328+
frames = []
329+
330+
for file_name, lineno, function_name, text in tb:
331+
frames.append({
332+
'filename': file_name,
333+
'lineno': lineno,
334+
'function': function_name,
335+
})
336+
337+
if frames:
338+
data = {'stacktrace': {'frames': frames}}
339+
340+
message = context.get('message', 'Unhandled exception in event loop')
341+
self.captureMessage(message, data=data, level='exception')
342+
343+
loop_except_handler(loop, context)
344+
345+
handle_exception.raven_client = self
346+
loop.set_exception_handler(handle_exception)
347+
286348
def hook_libraries(self, libraries):
287349
from raven.breadcrumbs import hook_libraries
288350
hook_libraries(libraries)

tests/base/tests.py

+22
Original file line numberDiff line numberDiff line change
@@ -656,3 +656,25 @@ def test_repos_configuration(self):
656656
'name': 'getsentry/raven-python',
657657
},
658658
}
659+
660+
def test_install_asyncio_hook(self):
661+
try:
662+
import asyncio
663+
664+
loop = asyncio.new_event_loop()
665+
client = TempStoreClient()
666+
client.install_asyncio_hook(loop=loop)
667+
668+
f = asyncio.Future(loop=loop)
669+
f.set_exception(RuntimeError('foobar'))
670+
del f
671+
672+
assert len(client.events) == 1
673+
event = client.events[0]
674+
assert event['level'] == 'exception'
675+
exception = event['exception']['values'][-1]
676+
assert exception['type'] == 'RuntimeError'
677+
assert exception['value'] == 'foobar'
678+
except ImportError:
679+
pass
680+

0 commit comments

Comments
 (0)