Fix Tornado error handling.

The error handling was quite broken - the exception handlers were never invoked, and they used methods which have been removed.

This fixes #601
This commit is contained in:
Joakim Recht 2015-08-28 21:40:36 +02:00
parent 59e3730574
commit e97071ae84
3 changed files with 62 additions and 22 deletions

View File

@ -7,6 +7,9 @@ raven.contrib.tornado
"""
from __future__ import absolute_import
from functools import partial
from tornado import ioloop
from tornado.httpclient import AsyncHTTPClient, HTTPError
from raven.base import Client
@ -51,30 +54,25 @@ class AsyncSentryClient(Client):
headers = {}
if not self.state.should_try():
message = self._get_log_message(data)
self.error_logger.error(message)
data = self.decode(data)
self._log_failed_submission(data)
return
future = self._send_remote(
url=url, data=data, headers=headers, callback=callback
)
ioloop.IOLoop.current().add_future(future, partial(self._handle_result, url, data))
return future
def _handle_result(self, url, data, future):
try:
self._send_remote(
url=url, data=data, headers=headers, callback=callback
)
future.result()
except HTTPError as e:
body = e.response.body
self.error_logger.error(
'Unable to reach Sentry log server: %s '
'(url: %%s, body: %%s)' % (e,),
url, body, exc_info=True,
extra={'data': {'body': body, 'remote_url': url}}
)
data = self.decode(data)
self._failed_send(e, url, data)
except Exception as e:
self.error_logger.error(
'Unable to reach Sentry log server: %s (url: %%s)' % (e,),
url, exc_info=True, extra={'data': {'remote_url': url}}
)
message = self._get_log_message(data)
self.error_logger.error('Failed to submit message: %r', message)
self.state.set_fail()
data = self.decode(data)
self._failed_send(e, url, data)
else:
self.state.set_success()

View File

@ -7,6 +7,9 @@ raven.transport.tornado
"""
from __future__ import absolute_import
from functools import partial
from raven.transport.base import AsyncTransport
from raven.transport.http import HTTPTransport
try:
@ -17,7 +20,7 @@ except:
has_tornado = False
class TornadoHTTPTransport(HTTPTransport):
class TornadoHTTPTransport(AsyncTransport, HTTPTransport):
scheme = ['tornado+http', 'tornado+https']
@ -27,7 +30,7 @@ class TornadoHTTPTransport(HTTPTransport):
super(TornadoHTTPTransport, self).__init__(parsed_url, *args, **kwargs)
def send(self, data, headers):
def async_send(self, data, headers, success_cb, failure_cb):
kwargs = dict(method='POST', headers=headers, body=data)
kwargs["validate_cert"] = self.verify_ssl
kwargs["connect_timeout"] = self.timeout
@ -37,7 +40,21 @@ class TornadoHTTPTransport(HTTPTransport):
if ioloop.IOLoop.initialized():
client = AsyncHTTPClient()
kwargs['callback'] = None
future = client.fetch(self._url, **kwargs)
ioloop.IOLoop.current().add_future(future, partial(self.handler, success_cb, failure_cb))
else:
client = HTTPClient()
try:
client.fetch(self._url, **kwargs)
success_cb()
except Exception as e:
failure_cb(e)
client.fetch(self._url, **kwargs)
@staticmethod
def handler(future, success, error):
try:
future.result()
success()
except Exception as e:
error(e)

View File

@ -3,6 +3,8 @@ from __future__ import unicode_literals
from mock import patch
from tornado import web, gen, testing
from tornado.concurrent import Future
from tornado.httpclient import HTTPError
from raven.contrib.tornado import SentryMixin, AsyncSentryClient
from raven.utils import six
@ -212,3 +214,26 @@ class TornadoAsyncClientTestCase(testing.AsyncHTTPTestCase):
user_data = kwargs['user']
self.assertEqual(user_data['is_authenticated'], False)
@testing.gen_test
def test_sending_to_unresponsive_sentry_server_logs_error(self):
client = self.get_app().sentry_client
with patch.object(client, '_failed_send') as mock_failed:
client.send()
yield gen.sleep(0.01) # we need to run after the async send
assert mock_failed.called
@testing.gen_test
def test_non_successful_responses_marks_client_as_failed(self):
client = self.get_app().sentry_client
with patch.object(client, '_failed_send') as mock_failed:
with patch.object(client, '_send_remote') as mock_send:
f = Future()
f.set_exception(HTTPError(499, 'error'))
mock_send.return_value = f
client.send()
yield gen.sleep(0.01) # we need to run after the async send
assert mock_failed.called