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

Add the Client.install_asyncio_hook function. #1134

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()``.
61 changes: 61 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,57 @@ 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()
except AttributeError:
# No get_exception_handler before Python 3.5.2
loop_except_handler = getattr(loop, '_exception_handler', None)

if not loop_except_handler:
loop_except_handler = 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,
})

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