Merge pull request #237 from lopter/master

Update the ZeroRPC middleware for zerorpc-python >= 0.4.0
This commit is contained in:
David Cramer 2013-01-21 16:04:27 -08:00
commit 3caf9083b5
3 changed files with 57 additions and 19 deletions

View File

@ -21,6 +21,12 @@ submits exceptions to Sentry. This behavior can be disabled by passing the
sentry = SentryMiddleware(hide_zerorpc_frames=False, dsn='udp://public_key:secret_key@example.com:4242/1') sentry = SentryMiddleware(hide_zerorpc_frames=False, dsn='udp://public_key:secret_key@example.com:4242/1')
Compatibility
-------------
- ZeroRPC-Python < 0.4.0 is compatible with Raven <= 3.1.0;
- ZeroRPC-Python >= 0.4.0 requires Raven > 3.1.0.
Caveats Caveats
------- -------

View File

@ -9,50 +9,60 @@ raven.contrib.zerorpc
import inspect import inspect
from raven.base import Client from raven.base import Client
from raven.utils.stacks import iter_traceback_frames
class SentryMiddleware(object): class SentryMiddleware(object):
"""Sentry/Raven middleware for ZeroRPC. """Sentry/Raven middleware for ZeroRPC.
>>> import zerorpc
>>> from raven.contrib.zerorpc import SentryMiddleware
>>> sentry = SentryMiddleware(dsn='udp://..../') >>> sentry = SentryMiddleware(dsn='udp://..../')
>>> zerorpc.Context.get_instance().register_middleware(sentry) >>> zerorpc.Context.get_instance().register_middleware(sentry)
Exceptions detected server-side in ZeroRPC will be submitted to Sentry (and Exceptions detected server-side in ZeroRPC will be submitted to Sentry (and
propagated to the client as well). propagated to the client as well).
hide_zerorpc_frames: modify the exception stacktrace to remove the internal
zerorpc frames (True by default to make the stacktrace
as readable as possible);
client: use an existing raven.Client object, otherwise one will be
instantiated from the keyword arguments.
""" """
def __init__(self, hide_zerorpc_frames=True, client=None, **kwargs): def __init__(self, hide_zerorpc_frames=True, client=None, **kwargs):
"""Create a middleware object that can be injected in a ZeroRPC server.
- hide_zerorpc_frames: modify the exception stacktrace to remove the
internal zerorpc frames (True by default to make
the stacktrace as readable as possible);
- client: use an existing raven.Client object, otherwise one will be
instantiated from the keyword arguments.
"""
self._sentry_client = client or Client(**kwargs) self._sentry_client = client or Client(**kwargs)
self._hide_zerorpc_frames = hide_zerorpc_frames self._hide_zerorpc_frames = hide_zerorpc_frames
def inspect_error(self, task_context, exc_info): def server_inspect_exception(self, req_event, rep_event, task_ctx, exc_info):
"""Called when an exception has been raised in the code run by ZeroRPC""" """Called when an exception has been raised in the code run by ZeroRPC"""
# Hide the zerorpc internal frames for readability, frames to hide are: # Hide the zerorpc internal frames for readability, for a REQ/REP or
# REQ/STREAM server the frames to hide are:
# - core.ServerBase._async_task # - core.ServerBase._async_task
# - core.Pattern*.process_call # - core.Pattern*.process_call
# - context.Context.middleware_call_procedure
# - core.DecoratorBase.__call__ # - core.DecoratorBase.__call__
#
# For a PUSH/PULL or PUB/SUB server the frame to hide is:
# - core.Puller._receiver
if self._hide_zerorpc_frames: if self._hide_zerorpc_frames:
exc_traceback = exc_info[2] traceback = exc_info[2]
for zerorpc_frame, tb_lineno in iter_traceback_frames(exc_traceback): while traceback:
zerorpc_frame = traceback.tb_frame
zerorpc_frame.f_locals['__traceback_hide__'] = True zerorpc_frame.f_locals['__traceback_hide__'] = True
frame_info = inspect.getframeinfo(zerorpc_frame) frame_info = inspect.getframeinfo(zerorpc_frame)
# Is there a better way than this (or looking up the filenames or # Is there a better way than this (or looking up the filenames
# hardcoding the number of frames to skip) to know when we are out # or hardcoding the number of frames to skip) to know when we
# of zerorpc? # are out of zerorpc?
if frame_info.function == '__call__': if frame_info.function == '__call__' \
or frame_info.function == '_receiver':
break break
traceback = traceback.tb_next
self._sentry_client.captureException( self._sentry_client.captureException(
exc_info, exc_info,
extra=task_context extra=task_ctx
) )

View File

@ -36,6 +36,7 @@ class ZeroRPCTest(unittest2.TestCase):
client=self._sentry client=self._sentry
)) ))
def test_zerorpc_middleware_with_reqrep(self):
self._server = zerorpc.Server(random) self._server = zerorpc.Server(random)
self._server.bind(self._server_endpoint) self._server.bind(self._server_endpoint)
gevent.spawn(self._server.run) gevent.spawn(self._server.run)
@ -43,7 +44,6 @@ class ZeroRPCTest(unittest2.TestCase):
self._client = zerorpc.Client() self._client = zerorpc.Client()
self._client.connect(self._server_endpoint) self._client.connect(self._server_endpoint)
def test_zerorpc_middleware(self):
try: try:
self._client.choice([]) self._client.choice([])
except zerorpc.exceptions.RemoteError, ex: except zerorpc.exceptions.RemoteError, ex:
@ -58,6 +58,28 @@ class ZeroRPCTest(unittest2.TestCase):
self.fail('An IndexError exception should have been raised an catched') self.fail('An IndexError exception should have been raised an catched')
def test_zerorpc_middleware_with_pushpull(self):
self._server = zerorpc.Puller(random)
self._server.bind(self._server_endpoint)
gevent.spawn(self._server.run)
self._client = zerorpc.Pusher()
self._client.connect(self._server_endpoint)
self._client.choice([])
for attempt in xrange(0, 10):
gevent.sleep(0.1)
if len(self._sentry.events):
exc = self._sentry.events[0]['sentry.interfaces.Exception']
self.assertEqual(exc['type'], 'IndexError')
frames = self._sentry.events[0]['sentry.interfaces.Stacktrace']['frames']
self.assertEqual(frames[0]['function'], 'choice')
self.assertEqual(frames[0]['module'], 'random')
return
self.fail('An IndexError exception should have been sent to Sentry')
def tearDown(self): def tearDown(self):
self._client.close() self._client.close()
self._server.close() self._server.close()