diff --git a/airbrake/__about__.py b/airbrake/__about__.py index c5df2a6..a00ced5 100644 --- a/airbrake/__about__.py +++ b/airbrake/__about__.py @@ -16,7 +16,7 @@ __summary__ = 'Python SDK for airbrake.io' __author__ = 'BK Box, Sam Stavinoha' __email__ = 'samuel.stavinoha@rackspace.com' -__version__ = '1.3.0' +__version__ = '1.3.1' __keywords__ = ['airbrake', 'exceptions', 'airbrake.io'] __license__ = 'Apache License, Version 2.0' __url__ = 'https://github.com/airbrake/airbrake-python' diff --git a/airbrake/notifier.py b/airbrake/notifier.py index 58ed863..891a33f 100644 --- a/airbrake/notifier.py +++ b/airbrake/notifier.py @@ -186,15 +186,13 @@ def log(self, exc_info=None, message=None, filename=None, 'notifier': self.notifier, 'environment': environment, 'session': session} - - return self.notify(payload) + return self.notify(json.dumps(payload, cls=utils.FailProofJSONEncoder)) def notify(self, payload): """Post the current errors payload body to airbrake.io.""" headers = {'Content-Type': 'application/json'} api_key = {'key': self.api_key} - - response = requests.post(self.api_url, data=json.dumps(payload), + response = requests.post(self.api_url, data=payload, headers=headers, params=api_key) response.raise_for_status() return response diff --git a/airbrake/utils.py b/airbrake/utils.py index 45521dd..5d38b48 100644 --- a/airbrake/utils.py +++ b/airbrake/utils.py @@ -8,6 +8,7 @@ import traceback import types +import json try: TypeType = types.TypeType @@ -16,6 +17,19 @@ TypeType = type +class FailProofJSONEncoder(json.JSONEncoder): + """Uses object's representation for unsupported types.""" + + def default(self, o): # pylint: disable=E0202 + # E0202 ignored in favor of compliance with documentation: + # https://docs.python.org/2/library/json.html#json.JSONEncoder.default + """Return object's repr when not JSON serializable.""" + try: + return repr(o) + except Exception: # pylint: disable=W0703 + return super(FailProofJSONEncoder, self).default(o) + + class CheckableQueue(Queue): """Checkable FIFO Queue which makes room for new items.""" diff --git a/tests/test_notifier.py b/tests/test_notifier.py index 0fa8adf..2506d76 100644 --- a/tests/test_notifier.py +++ b/tests/test_notifier.py @@ -1,16 +1,21 @@ import airbrake import mock import unittest +import json + from airbrake.notifier import Airbrake class TestAirbrakeNotifier(unittest.TestCase): - def _create_notify(test, exception, session={}, environment={}): + def _create_notify(test, exception, session={}, environment={}, **params): def notify(self, payload): + payload = json.loads(payload) test.assertEqual(session, payload['session']) test.assertEqual(environment, payload['environment']) test.assertEqual(str(exception), payload['errors'][0]['message']) + for param_name, expected_value in params.items(): + test.assertEqual(expected_value, payload['params'][param_name]) return notify def setUp(self): @@ -47,5 +52,20 @@ def test_exception_with_environment(self): extra = {'environment': self.environment} self.logger.exception(Exception(msg), extra=extra) + def test_exception_with_non_serializable(self): + msg = "Narf!" + + class NonSerializable: + def __repr__(self): + return '' + non_serializable = NonSerializable() + + notify = self._create_notify(msg, + very='important', + jsonify=repr(non_serializable)) + with mock.patch.object(Airbrake, 'notify', notify, 'jsonify'): + extra = {'very': 'important', 'jsonify': non_serializable} + self.logger.exception(Exception(msg), extra=extra) + if __name__ == '__main__': unittest.main()