Skip to content

Commit

Permalink
Merge pull request #27 from argos83/tech/support_non_json_serializabl…
Browse files Browse the repository at this point in the history
…e_objects

Don't fail when 'extra' in log records contain non JSON serializable objects
  • Loading branch information
phumpal committed Mar 10, 2016
2 parents 8e538f2 + 887dd41 commit 589951f
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 6 deletions.
2 changes: 1 addition & 1 deletion airbrake/__about__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
__summary__ = 'Python SDK for airbrake.io'
__author__ = 'BK Box, Sam Stavinoha'
__email__ = '[email protected]'
__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'
Expand Down
6 changes: 2 additions & 4 deletions airbrake/notifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions airbrake/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import traceback
import types
import json

try:
TypeType = types.TypeType
Expand All @@ -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."""
Expand Down
22 changes: 21 additions & 1 deletion tests/test_notifier.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -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 '<Use this instead>'
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()

0 comments on commit 589951f

Please sign in to comment.