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

Commit

Permalink
Add the Client.install_asyncio_hook function.
Browse files Browse the repository at this point in the history
  • Loading branch information
Kentzo committed Nov 12, 2017
1 parent 5fdc04d commit 7439554
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 0 deletions.
16 changes: 16 additions & 0 deletions docs/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -302,3 +302,19 @@ A Note on uWSGI
If you're using uWSGI you will need to add ``enable-threads`` to the
default invocation, or you will need to switch off of the threaded default
transport.

Integration with asyncio
---------------

asyncio introduces additional level of exception handling: exceptions emitted by tasks and futures
must be explicitly handled and are not automatically propagated into ``sys.excepthook``.
Unhandled exceptions (exceptions that were not retrieved before object's destruction) are passed into
loop's own exception handler.

To handle them the per-loop exception handler can be installed:

.. code-block:: python
client.install_asyncio_hook()
The function accepts one optional argument: ``loop``. It defaults to ``asyncio.get_event_loop()``.
59 changes: 59 additions & 0 deletions raven/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ def get_excepthook_client():
return client


def get_loop_excepthook_client(loop=None):
import asyncio

loop = loop or asyncio.get_event_loop()
hook = loop.get_exception_handler()
client = getattr(hook, 'raven_client', None)
if client is not None:
return client


class ModuleProxyCache(dict):
def __missing__(self, key):
module, class_name = key.rsplit('.', 1)
Expand Down Expand Up @@ -283,6 +293,55 @@ def install_logging_hook(self):
from raven.breadcrumbs import install_logging_hook
install_logging_hook()

def install_asyncio_hook(self, loop=None):
import asyncio

loop = loop or asyncio.get_event_loop()

try:
loop_except_handler = loop.get_exception_handler() or type(loop).default_exception_handler
except AttributeError:
# No get_exception_handler before Python 3.5.2
loop_except_handler = getattr(loop, '_exception_handler', None) or type(loop).default_exception_handler

def handle_exception(loop, context):
if 'exception' in context:
exception = context['exception']
exc_info = type(exception), exception, exception.__traceback__
self.captureException(exc_info=exc_info, level='exception') # asyncio exceptions are non-fatal
else:
data = {}

if 'source_traceback' in context:
tb = context['source_traceback']
elif 'handle' in context and getattr(context['handle'], '_source_traceback', None):
tb = context['handle']._source_traceback
elif 'future' in context and getattr(context['future'], '_source_traceback', None):
tb = context['future']._source_traceback
else:
tb = None

if tb:
frames = []

for file_name, lineno, function_name, text in tb:
frames.append({
'filename': file_name,
'lineno': lineno,
'function': function_name,
})

if frames:
data = {'stacktrace': {'frames': frames}}

message = context.get('message', 'Unhandled exception in event loop')
self.captureMessage(message, data=data, level='exception')

loop_except_handler(loop, context)

handle_exception.raven_client = self
loop.set_exception_handler(handle_exception)

def hook_libraries(self, libraries):
from raven.breadcrumbs import hook_libraries
hook_libraries(libraries)
Expand Down
22 changes: 22 additions & 0 deletions tests/base/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -656,3 +656,25 @@ def test_repos_configuration(self):
'name': 'getsentry/raven-python',
},
}

def test_install_asyncio_hook(self):
try:
import asyncio

loop = asyncio.new_event_loop()
client = TempStoreClient()
client.install_asyncio_hook(loop=loop)

f = asyncio.Future(loop=loop)
f.set_exception(RuntimeError('foobar'))
del f

assert len(client.events) == 1
event = client.events[0]
assert event['level'] == 'exception'
exception = event['exception']['values'][-1]
assert exception['type'] == 'RuntimeError'
assert exception['value'] == 'foobar'
except ImportError:
pass

0 comments on commit 7439554

Please sign in to comment.