From 91052a04459c9cc09d1ff1a30c4f06ba63a67146 Mon Sep 17 00:00:00 2001 From: Cory Virok Date: Tue, 3 Nov 2015 15:21:33 -0800 Subject: [PATCH] Implemented a more robust filtering mechanism You can now add custom filtering logic to determine if the error/message/payload should be sent to Rollbar. e.g. Only send messages that match a regular expression: ```py import re import rollbar from rollbar import events def ignore_unimportant_messages(message, **kw): if re.search(r'everthing is broken', message): return message return False events.add_message_handler(ignore_unimportant_messages) """Don't report to Rollbar""" rollbar.report_message('everything is fine...') """Report to Rollbar""" rollbar.report_message('everything is broken') ``` @brianr cc @chrisfole --- rollbar/__init__.py | 118 +++++++++++++++---------- rollbar/lib/events.py | 99 +++++++++++++++++++++ rollbar/lib/filters/__init__.py | 11 +++ rollbar/lib/filters/basic.py | 13 +++ rollbar/test/flask_tests/test_flask.py | 4 +- rollbar/test/test_basic_filters.py | 29 ++++++ rollbar/test/test_custom_filters.py | 50 +++++++++++ rollbar/test/test_loghandler.py | 4 +- rollbar/test/test_rollbar.py | 58 ++++++------ 9 files changed, 307 insertions(+), 79 deletions(-) create mode 100644 rollbar/lib/events.py create mode 100644 rollbar/lib/filters/__init__.py create mode 100644 rollbar/lib/filters/basic.py create mode 100644 rollbar/test/test_basic_filters.py create mode 100644 rollbar/test/test_custom_filters.py diff --git a/rollbar/__init__.py b/rollbar/__init__.py index 6960e907..e1da33d1 100644 --- a/rollbar/__init__.py +++ b/rollbar/__init__.py @@ -22,7 +22,7 @@ import requests import six -from rollbar.lib import dict_merge, parse_qs, text, transport, urljoin, iteritems +from rollbar.lib import events, filters, dict_merge, parse_qs, text, transport, urljoin, iteritems __version__ = '0.13.17' __log_name__ = 'rollbar' @@ -342,8 +342,9 @@ def init(access_token, environment='production', **kw): keys=shortener_keys, **SETTINGS['locals']['sizes']) _transforms.append(shortener) - _threads = queue.Queue() + events.reset() + filters.add_builtin_filters(SETTINGS) _initialized = True @@ -413,7 +414,7 @@ def report_message(message, level='error', request=None, extra_data=None, payloa def send_payload(payload, access_token): """ - Sends a payload object, (the result of calling _build_payload()). + Sends a payload object, (the result of calling _build_payload() + _serialize_payload()). Uses the configured handler from SETTINGS['handler'] Available handlers: @@ -424,29 +425,35 @@ def send_payload(payload, access_token): - 'gae': calls _send_payload_appengine() (which makes a blocking call to Google App Engine) - 'twisted': calls _send_payload_twisted() (which makes an async HTTP reqeust using Twisted and Treq) """ + payload = events.on_payload(payload) + if payload is False: + return + + payload_str = _serialize_payload(payload) + handler = SETTINGS.get('handler') if handler == 'blocking': - _send_payload(payload, access_token) + _send_payload(payload_str, access_token) elif handler == 'agent': - agent_log.error(payload) + agent_log.error(payload_str) elif handler == 'tornado': if TornadoAsyncHTTPClient is None: log.error('Unable to find tornado') return - _send_payload_tornado(payload, access_token) + _send_payload_tornado(payload_str, access_token) elif handler == 'gae': if AppEngineFetch is None: log.error('Unable to find AppEngine URLFetch module') return - _send_payload_appengine(payload, access_token) + _send_payload_appengine(payload_str, access_token) elif handler == 'twisted': if treq is None: log.error('Unable to find Treq') return - _send_payload_twisted(payload, access_token) + _send_payload_twisted(payload_str, access_token) else: # default to 'thread' - thread = threading.Thread(target=_send_payload, args=(payload, access_token)) + thread = threading.Thread(target=_send_payload, args=(payload_str, access_token)) _threads.put(thread) thread.start() @@ -601,22 +608,27 @@ def _report_exc_info(exc_info, request, extra_data, payload_data, level=None): """ Called by report_exc_info() wrapper """ - # check if exception is marked ignored - cls, exc, trace = exc_info - if getattr(exc, '_rollbar_ignore', False) or _is_ignored(exc): - return if not _check_config(): return - data = _build_base_data(request) + filtered_level = _filtered_level(exc_info[1]) + if level is None: + level = filtered_level + + filtered_exc_info = events.on_exception_info(exc_info, + request=request, + extra_data=extra_data, + payload_data=payload_data, + level=level) + + if filtered_exc_info is False: + return - filtered_level = _filtered_level(exc) - if filtered_level: - data['level'] = filtered_level + cls, exc, trace = filtered_exc_info - # explicitly override the level with provided level - if level: + data = _build_base_data(request) + if level is not None: data['level'] = level # walk the trace chain to collect cause and context exceptions @@ -697,12 +709,21 @@ def _report_message(message, level, request, extra_data, payload_data): if not _check_config(): return + filtered_message = events.on_message(message, + request=request, + extra_data=extra_data, + payload_data=payload_data, + level=level) + + if filtered_message is False: + return + data = _build_base_data(request, level=level) # message data['body'] = { 'message': { - 'body': message + 'body': filtered_message } } @@ -1201,12 +1222,16 @@ def _build_payload(data): 'data': data } + return payload + + +def _serialize_payload(payload): return json.dumps(payload) -def _send_payload(payload, access_token): +def _send_payload(payload_str, access_token): try: - _post_api('item/', payload, access_token=access_token) + _post_api('item/', payload_str, access_token=access_token) except Exception as e: log.exception('Exception while posting item %r', e) try: @@ -1216,14 +1241,14 @@ def _send_payload(payload, access_token): pass -def _send_payload_appengine(payload, access_token): +def _send_payload_appengine(payload_str, access_token): try: - _post_api_appengine('item/', payload, access_token=access_token) + _post_api_appengine('item/', payload_str, access_token=access_token) except Exception as e: log.exception('Exception while posting item %r', e) -def _post_api_appengine(path, payload, access_token=None): +def _post_api_appengine(path, payload_str, access_token=None): headers = {'Content-Type': 'application/json'} if access_token is not None: @@ -1232,16 +1257,16 @@ def _post_api_appengine(path, payload, access_token=None): url = urljoin(SETTINGS['endpoint'], path) resp = AppEngineFetch(url, method="POST", - payload=payload, + payload=payload_str, headers=headers, allow_truncated=False, deadline=SETTINGS.get('timeout', DEFAULT_TIMEOUT), validate_certificate=SETTINGS.get('verify_https', True)) - return _parse_response(path, SETTINGS['access_token'], payload, resp) + return _parse_response(path, SETTINGS['access_token'], payload_str, resp) -def _post_api(path, payload, access_token=None): +def _post_api(path, payload_str, access_token=None): headers = {'Content-Type': 'application/json'} if access_token is not None: @@ -1249,12 +1274,12 @@ def _post_api(path, payload, access_token=None): url = urljoin(SETTINGS['endpoint'], path) resp = transport.post(url, - data=payload, - headers=headers, - timeout=SETTINGS.get('timeout', DEFAULT_TIMEOUT), - verify=SETTINGS.get('verify_https', True)) + data=payload_str, + headers=headers, + timeout=SETTINGS.get('timeout', DEFAULT_TIMEOUT), + verify=SETTINGS.get('verify_https', True)) - return _parse_response(path, SETTINGS['access_token'], payload, resp) + return _parse_response(path, SETTINGS['access_token'], payload_str, resp) def _get_api(path, access_token=None, endpoint=None, **params): @@ -1265,15 +1290,15 @@ def _get_api(path, access_token=None, endpoint=None, **params): return _parse_response(path, access_token, params, resp, endpoint=endpoint) -def _send_payload_tornado(payload, access_token): +def _send_payload_tornado(payload_str, access_token): try: - _post_api_tornado('item/', payload, access_token=access_token) + _post_api_tornado('item/', payload_str, access_token=access_token) except Exception as e: log.exception('Exception while posting item %r', e) @tornado_coroutine -def _post_api_tornado(path, payload, access_token=None): +def _post_api_tornado(path, payload_str, access_token=None): headers = {'Content-Type': 'application/json'} if access_token is not None: @@ -1281,30 +1306,31 @@ def _post_api_tornado(path, payload, access_token=None): url = urljoin(SETTINGS['endpoint'], path) - resp = yield TornadoAsyncHTTPClient().fetch( - url, body=payload, method='POST', connect_timeout=SETTINGS.get('timeout', DEFAULT_TIMEOUT), - request_timeout=SETTINGS.get('timeout', DEFAULT_TIMEOUT) - ) + resp = yield TornadoAsyncHTTPClient().fetch(url, + body=payload_str, + method='POST', + connect_timeout=SETTINGS.get('timeout', DEFAULT_TIMEOUT), + request_timeout=SETTINGS.get('timeout', DEFAULT_TIMEOUT)) r = requests.Response() r._content = resp.body r.status_code = resp.code r.headers.update(resp.headers) - _parse_response(path, SETTINGS['access_token'], payload, r) + _parse_response(path, SETTINGS['access_token'], payload_str, r) -def _send_payload_twisted(payload, access_token): +def _send_payload_twisted(payload_str, access_token): try: - _post_api_twisted('item/', payload, access_token=access_token) + _post_api_twisted('item/', payload_str, access_token=access_token) except Exception as e: log.exception('Exception while posting item %r', e) -def _post_api_twisted(path, payload, access_token=None): +def _post_api_twisted(path, payload_str, access_token=None): def post_data_cb(data, resp): resp._content = data - _parse_response(path, SETTINGS['access_token'], payload, resp) + _parse_response(path, SETTINGS['access_token'], payload_str, resp) def post_cb(resp): r = requests.Response() @@ -1317,7 +1343,7 @@ def post_cb(resp): headers['X-Rollbar-Access-Token'] = [access_token] url = urljoin(SETTINGS['endpoint'], path) - d = treq.post(url, payload, headers=headers, + d = treq.post(url, payload_str, headers=headers, timeout=SETTINGS.get('timeout', DEFAULT_TIMEOUT)) d.addCallback(post_cb) diff --git a/rollbar/lib/events.py b/rollbar/lib/events.py new file mode 100644 index 00000000..96f68144 --- /dev/null +++ b/rollbar/lib/events.py @@ -0,0 +1,99 @@ +EXCEPTION_INFO = 'exception_info' +MESSAGE = 'message' +PAYLOAD = 'payload' + +_event_handlers = { + EXCEPTION_INFO: [], + MESSAGE: [], + PAYLOAD: [] +} + + +def _check_type(type): + if type not in _event_handlers: + raise ValueError('Unknown type: %s. Must be one of %s' % (type, _event_handlers.keys())) + + +def _add_handler(type, handler_fn, pos): + _check_type(type) + + pos = pos if pos is not None else -1 + handlers = _event_handlers[type] + + try: + handlers.index(handler_fn) + except ValueError: + handlers.insert(pos, handler_fn) + + +def _remove_handler(type, handler_fn): + _check_type(type) + + handlers = _event_handlers[type] + + try: + index = handlers.index(handler_fn) + handlers.pop(index) + except ValueError: + pass + + +def _on_event(type, target, **kw): + _check_type(type) + + ref = target + for handler in _event_handlers[type]: + result = handler(ref, **kw) + if result is False: + return False + + ref = result + + return ref + + +# Add/remove event handlers + +def add_exception_info_handler(handler_fn, pos=None): + _add_handler(EXCEPTION_INFO, handler_fn, pos) + + +def remove_exception_info_handler(handler_fn): + _remove_handler(EXCEPTION_INFO, handler_fn) + + +def add_message_handler(handler_fn, pos=None): + _add_handler(MESSAGE, handler_fn, pos) + + +def remove_message_handler(handler_fn): + _remove_handler(MESSAGE, handler_fn) + + +def add_payload_handler(handler_fn, pos=None): + _add_handler(PAYLOAD, handler_fn, pos) + + +def remove_payload_handler(handler_fn): + _remove_handler(PAYLOAD, handler_fn) + + +# Event handler processing + +def on_exception_info(exc_info, **kw): + return _on_event(EXCEPTION_INFO, exc_info, **kw) + + +def on_message(message, **kw): + return _on_event(MESSAGE, message, **kw) + + +def on_payload(payload, **kw): + return _on_event(PAYLOAD, payload, **kw) + + +# Misc + +def reset(): + for handlers in _event_handlers.values(): + del handlers[:] \ No newline at end of file diff --git a/rollbar/lib/filters/__init__.py b/rollbar/lib/filters/__init__.py new file mode 100644 index 00000000..f054e70e --- /dev/null +++ b/rollbar/lib/filters/__init__.py @@ -0,0 +1,11 @@ +from rollbar.lib import events +from rollbar.lib.filters.basic import filter_rollbar_ignored_exceptions, filter_by_level + + +def add_builtin_filters(settings): + # exc_info filters + events.add_exception_info_handler(filter_rollbar_ignored_exceptions) + events.add_exception_info_handler(filter_by_level) + + # message filters + events.add_message_handler(filter_by_level) diff --git a/rollbar/lib/filters/basic.py b/rollbar/lib/filters/basic.py new file mode 100644 index 00000000..b9e37809 --- /dev/null +++ b/rollbar/lib/filters/basic.py @@ -0,0 +1,13 @@ +def filter_rollbar_ignored_exceptions(exc_info, **kw): + cls, exc, trace = exc_info + if getattr(exc, '_rollbar_ignore', False): + return False + + return exc_info + + +def filter_by_level(target, **kw): + if 'level' in kw and kw['level'] == 'ignored': + return False + + return target diff --git a/rollbar/test/flask_tests/test_flask.py b/rollbar/test/flask_tests/test_flask.py index c12b1330..f4a74690 100644 --- a/rollbar/test/flask_tests/test_flask.py +++ b/rollbar/test/flask_tests/test_flask.py @@ -82,7 +82,7 @@ def test_uncaught(self, send_payload): self.assertEqual(resp.status_code, 500) self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] data = payload['data'] self.assertIn('body', data) @@ -110,7 +110,7 @@ def test_uncaught_json_request(self, send_payload): self.assertEqual(resp.status_code, 500) self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] data = payload['data'] self.assertIn('body', data) diff --git a/rollbar/test/test_basic_filters.py b/rollbar/test/test_basic_filters.py new file mode 100644 index 00000000..81569ede --- /dev/null +++ b/rollbar/test/test_basic_filters.py @@ -0,0 +1,29 @@ +from rollbar.lib import events, filters + +from rollbar.test import BaseTest + + +class BasicFiltersTest(BaseTest): + def setUp(self): + events.reset() + filters.add_builtin_filters({}) + + def test_rollbar_ignored_exception(self): + class IgnoredException(Exception): + _rollbar_ignore = True + + class NotIgnoredException(Exception): + _rollbar_ignore = False + + self.assertFalse(events.on_exception_info((None, IgnoredException(), None))) + self.assertIsNot(events.on_exception_info((None, NotIgnoredException(), None)), False) + + def test_filter_by_level(self): + self.assertFalse(events.on_exception_info((None, 123, None), level='ignored')) + self.assertIsNot(events.on_exception_info((None, 123, None), level='error'), False) + + self.assertFalse(events.on_message('hello world', level='ignored')) + self.assertIsNot(events.on_message('hello world', level='error'), False) + + self.assertFalse(events.on_payload({}, level='ignored')) + self.assertIsNot(events.on_message({}, level='error'), False) diff --git a/rollbar/test/test_custom_filters.py b/rollbar/test/test_custom_filters.py new file mode 100644 index 00000000..518c9610 --- /dev/null +++ b/rollbar/test/test_custom_filters.py @@ -0,0 +1,50 @@ +import re + +from rollbar.lib import events, filters + +from rollbar.test import BaseTest + + +class CustomFiltersTest(BaseTest): + def setUp(self): + events.reset() + filters.add_builtin_filters({}) + + def test_ignore_by_setting_rollbar_ignore(self): + class NotIgnoredByDefault(Exception): + pass + + def _ignore_if_cruel_world_filter(exc_info, **kw): + cls, exc, trace = exc_info + if 'cruel world' in str(exc): + exc._rollbar_ignore = True + + return exc_info + + events.add_exception_info_handler(_ignore_if_cruel_world_filter, pos=0) + + self.assertIsNot(events.on_exception_info((None, NotIgnoredByDefault('hello world'), None)), False) + self.assertFalse(events.on_exception_info((None, NotIgnoredByDefault('hello cruel world'), None))) + + def test_ignore_messages_by_regex(self): + regex = re.compile(r'cruel') + + def _ignore_cruel_world_substring(message, **kw): + if regex.search(message): + return False + + return message + + events.add_message_handler(_ignore_cruel_world_substring) + + self.assertFalse(events.on_message('hello cruel world')) + self.assertIsNot(events.on_message('hello world'), False) + + def test_modify_payload(self): + def _add_test_key(payload, **kw): + payload['test'] = 333 + return payload + + events.add_payload_handler(_add_test_key) + + self.assertEqual(events.on_payload({'hello': 'world'}), {'hello': 'world', 'test': 333}) diff --git a/rollbar/test/test_loghandler.py b/rollbar/test/test_loghandler.py index bf188da0..ffa7a3fa 100644 --- a/rollbar/test/test_loghandler.py +++ b/rollbar/test/test_loghandler.py @@ -42,10 +42,10 @@ def test_message_stays_unformatted(self, send_payload): logger = self._create_logger() logger.warning("Hello %d %s", 1, 'world') - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertEqual(payload['data']['body']['message']['body'], "Hello %d %s") - self.assertEqual(payload['data']['body']['message']['args'], [1, 'world']) + self.assertEqual(payload['data']['body']['message']['args'], (1, 'world')) self.assertEqual(payload['data']['body']['message']['record']['name'], __name__) def test_request_is_get_from_log_record_if_present(self): diff --git a/rollbar/test/test_rollbar.py b/rollbar/test/test_rollbar.py index 01b74722..5fe49f1d 100644 --- a/rollbar/test/test_rollbar.py +++ b/rollbar/test/test_rollbar.py @@ -125,7 +125,7 @@ def _raise(): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertEqual(payload['access_token'], _test_access_token) self.assertIn('body', payload['data']) @@ -367,7 +367,7 @@ def test_report_messsage(self, send_payload): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertEqual(payload['access_token'], _test_access_token) self.assertIn('body', payload['data']) @@ -379,7 +379,7 @@ def test_report_messsage(self, send_payload): def test_uuid(self, send_payload): uuid = rollbar.report_message('foo') - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertEqual(payload['data']['uuid'], uuid) @@ -392,7 +392,7 @@ def test_report_exc_info_level(self, send_payload): rollbar.report_exc_info() self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertEqual(payload['data']['level'], 'error') try: @@ -401,7 +401,7 @@ def test_report_exc_info_level(self, send_payload): rollbar.report_exc_info(level='info') self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertEqual(payload['data']['level'], 'info') # payload takes precendence over 'level' @@ -411,7 +411,7 @@ def test_report_exc_info_level(self, send_payload): rollbar.report_exc_info(level='info', payload_data={'level': 'warn'}) self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertEqual(payload['data']['level'], 'warn') @mock.patch('rollbar.send_payload') @@ -489,7 +489,7 @@ def __init__(self, arg1): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) @@ -510,7 +510,7 @@ def test_args_lambda_no_args(self, send_payload): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertNotIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) @@ -529,7 +529,7 @@ def test_args_lambda_with_args(self, send_payload): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) @@ -553,7 +553,7 @@ def test_args_lambda_with_defaults(self, send_payload): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) @@ -577,7 +577,7 @@ def test_args_lambda_with_star_args(self, send_payload): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertNotIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertIn('varargspec', payload['data']['body']['trace']['frames'][-1]) @@ -600,7 +600,7 @@ def test_args_lambda_with_star_args_and_args(self, send_payload): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertIn('varargspec', payload['data']['body']['trace']['frames'][-1]) @@ -628,7 +628,7 @@ def test_args_lambda_with_kwargs(self, send_payload): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertNotIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) @@ -652,7 +652,7 @@ def test_args_lambda_with_kwargs_and_args(self, send_payload): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) @@ -682,7 +682,7 @@ def test_args_lambda_with_kwargs_and_args_and_defaults(self, send_payload): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) @@ -720,7 +720,7 @@ def _raise(arg1): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) @@ -744,7 +744,7 @@ def test_anonymous_tuple_args(self, send_payload): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) @@ -770,7 +770,7 @@ def _raise(password='sensitive', clear='text'): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('kwargs', payload['data']['body']['trace']['frames'][-1]['locals']) @@ -860,7 +860,7 @@ def _raise(): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertRegex(payload['data']['body']['trace']['frames'][-1]['locals']['password'], '\*+') self.assertRegex(payload['data']['body']['trace']['frames'][-1]['locals']['Password'], '\*+') @@ -885,7 +885,7 @@ def _raise(): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertEqual('', payload['data']['body']['trace']['frames'][-1]['locals']['infinity']) self.assertEqual('', payload['data']['body']['trace']['frames'][-1]['locals']['negative_infinity']) @@ -910,7 +910,7 @@ def _raise(obj): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertTrue( (isinstance(payload['data']['body']['trace']['frames'][-1]['locals']['obj'], dict) and @@ -941,7 +941,7 @@ def _raise(password='sensitive'): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertEqual('sensitive', payload['data']['body']['trace']['frames'][-1]['locals']['copy']) @@ -959,7 +959,7 @@ def _raise(large): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) @@ -988,7 +988,7 @@ def _raise(large): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) @@ -1021,7 +1021,7 @@ def _raise(): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertNotIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) @@ -1047,7 +1047,7 @@ def test_all_project_frames_have_locals(self, send_payload): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] for frame in payload['data']['body']['trace']['frames']: self.assertIn('locals', frame) @@ -1066,7 +1066,7 @@ def test_only_last_frame_has_locals(self, send_payload): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] num_frames = len(payload['data']['body']['trace']['frames']) for i, frame in enumerate(payload['data']['body']['trace']['frames']): @@ -1090,7 +1090,7 @@ def test_modify_arg(self, send_payload): self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] frames = payload['data']['body']['trace']['frames'] called_with_frame = frames[1] @@ -1108,7 +1108,7 @@ def test_unicode_exc_info(self, send_payload): rollbar.report_exc_info() self.assertEqual(send_payload.called, True) - payload = json.loads(send_payload.call_args[0][0]) + payload = send_payload.call_args[0][0] self.assertEqual(payload['data']['body']['trace']['exception']['message'], message) @mock.patch('rollbar.lib.transport.post', side_effect=lambda *args, **kw: MockResponse({'status': 'OK'}, 200))