Skip to content

Commit b4bcae4

Browse files
coryvirokrokob
authored andcommitted
Implemented a more robust filtering mechanism (#81)
* 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 * send_payload is now taking serialized data so no need to json.loads in tests * fix codacy lint * fix twisted test
1 parent 4954e6b commit b4bcae4

File tree

10 files changed

+318
-90
lines changed

10 files changed

+318
-90
lines changed

rollbar/__init__.py

Lines changed: 72 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import requests
2323
import six
2424

25-
from rollbar.lib import dict_merge, parse_qs, text, transport, urljoin, iteritems
25+
from rollbar.lib import events, filters, dict_merge, parse_qs, text, transport, urljoin, iteritems
2626

2727
__version__ = '0.13.17'
2828
__log_name__ = 'rollbar'
@@ -342,8 +342,9 @@ def init(access_token, environment='production', **kw):
342342
keys=shortener_keys,
343343
**SETTINGS['locals']['sizes'])
344344
_transforms.append(shortener)
345-
346345
_threads = queue.Queue()
346+
events.reset()
347+
filters.add_builtin_filters(SETTINGS)
347348

348349
_initialized = True
349350

@@ -413,7 +414,7 @@ def report_message(message, level='error', request=None, extra_data=None, payloa
413414

414415
def send_payload(payload, access_token):
415416
"""
416-
Sends a payload object, (the result of calling _build_payload()).
417+
Sends a payload object, (the result of calling _build_payload() + _serialize_payload()).
417418
Uses the configured handler from SETTINGS['handler']
418419
419420
Available handlers:
@@ -424,29 +425,35 @@ def send_payload(payload, access_token):
424425
- 'gae': calls _send_payload_appengine() (which makes a blocking call to Google App Engine)
425426
- 'twisted': calls _send_payload_twisted() (which makes an async HTTP reqeust using Twisted and Treq)
426427
"""
428+
payload = events.on_payload(payload)
429+
if payload is False:
430+
return
431+
432+
payload_str = _serialize_payload(payload)
433+
427434
handler = SETTINGS.get('handler')
428435
if handler == 'blocking':
429-
_send_payload(payload, access_token)
436+
_send_payload(payload_str, access_token)
430437
elif handler == 'agent':
431-
agent_log.error(payload)
438+
agent_log.error(payload_str)
432439
elif handler == 'tornado':
433440
if TornadoAsyncHTTPClient is None:
434441
log.error('Unable to find tornado')
435442
return
436-
_send_payload_tornado(payload, access_token)
443+
_send_payload_tornado(payload_str, access_token)
437444
elif handler == 'gae':
438445
if AppEngineFetch is None:
439446
log.error('Unable to find AppEngine URLFetch module')
440447
return
441-
_send_payload_appengine(payload, access_token)
448+
_send_payload_appengine(payload_str, access_token)
442449
elif handler == 'twisted':
443450
if treq is None:
444451
log.error('Unable to find Treq')
445452
return
446-
_send_payload_twisted(payload, access_token)
453+
_send_payload_twisted(payload_str, access_token)
447454
else:
448455
# default to 'thread'
449-
thread = threading.Thread(target=_send_payload, args=(payload, access_token))
456+
thread = threading.Thread(target=_send_payload, args=(payload_str, access_token))
450457
_threads.put(thread)
451458
thread.start()
452459

@@ -601,22 +608,27 @@ def _report_exc_info(exc_info, request, extra_data, payload_data, level=None):
601608
"""
602609
Called by report_exc_info() wrapper
603610
"""
604-
# check if exception is marked ignored
605-
cls, exc, trace = exc_info
606-
if getattr(exc, '_rollbar_ignore', False) or _is_ignored(exc):
607-
return
608611

609612
if not _check_config():
610613
return
611614

612-
data = _build_base_data(request)
615+
filtered_level = _filtered_level(exc_info[1])
616+
if level is None:
617+
level = filtered_level
618+
619+
filtered_exc_info = events.on_exception_info(exc_info,
620+
request=request,
621+
extra_data=extra_data,
622+
payload_data=payload_data,
623+
level=level)
624+
625+
if filtered_exc_info is False:
626+
return
613627

614-
filtered_level = _filtered_level(exc)
615-
if filtered_level:
616-
data['level'] = filtered_level
628+
cls, exc, trace = filtered_exc_info
617629

618-
# explicitly override the level with provided level
619-
if level:
630+
data = _build_base_data(request)
631+
if level is not None:
620632
data['level'] = level
621633

622634
# walk the trace chain to collect cause and context exceptions
@@ -697,12 +709,21 @@ def _report_message(message, level, request, extra_data, payload_data):
697709
if not _check_config():
698710
return
699711

712+
filtered_message = events.on_message(message,
713+
request=request,
714+
extra_data=extra_data,
715+
payload_data=payload_data,
716+
level=level)
717+
718+
if filtered_message is False:
719+
return
720+
700721
data = _build_base_data(request, level=level)
701722

702723
# message
703724
data['body'] = {
704725
'message': {
705-
'body': message
726+
'body': filtered_message
706727
}
707728
}
708729

@@ -1201,12 +1222,16 @@ def _build_payload(data):
12011222
'data': data
12021223
}
12031224

1225+
return payload
1226+
1227+
1228+
def _serialize_payload(payload):
12041229
return json.dumps(payload)
12051230

12061231

1207-
def _send_payload(payload, access_token):
1232+
def _send_payload(payload_str, access_token):
12081233
try:
1209-
_post_api('item/', payload, access_token=access_token)
1234+
_post_api('item/', payload_str, access_token=access_token)
12101235
except Exception as e:
12111236
log.exception('Exception while posting item %r', e)
12121237
try:
@@ -1216,14 +1241,14 @@ def _send_payload(payload, access_token):
12161241
pass
12171242

12181243

1219-
def _send_payload_appengine(payload, access_token):
1244+
def _send_payload_appengine(payload_str, access_token):
12201245
try:
1221-
_post_api_appengine('item/', payload, access_token=access_token)
1246+
_post_api_appengine('item/', payload_str, access_token=access_token)
12221247
except Exception as e:
12231248
log.exception('Exception while posting item %r', e)
12241249

12251250

1226-
def _post_api_appengine(path, payload, access_token=None):
1251+
def _post_api_appengine(path, payload_str, access_token=None):
12271252
headers = {'Content-Type': 'application/json'}
12281253

12291254
if access_token is not None:
@@ -1232,29 +1257,29 @@ def _post_api_appengine(path, payload, access_token=None):
12321257
url = urljoin(SETTINGS['endpoint'], path)
12331258
resp = AppEngineFetch(url,
12341259
method="POST",
1235-
payload=payload,
1260+
payload=payload_str,
12361261
headers=headers,
12371262
allow_truncated=False,
12381263
deadline=SETTINGS.get('timeout', DEFAULT_TIMEOUT),
12391264
validate_certificate=SETTINGS.get('verify_https', True))
12401265

1241-
return _parse_response(path, SETTINGS['access_token'], payload, resp)
1266+
return _parse_response(path, SETTINGS['access_token'], payload_str, resp)
12421267

12431268

1244-
def _post_api(path, payload, access_token=None):
1269+
def _post_api(path, payload_str, access_token=None):
12451270
headers = {'Content-Type': 'application/json'}
12461271

12471272
if access_token is not None:
12481273
headers['X-Rollbar-Access-Token'] = access_token
12491274

12501275
url = urljoin(SETTINGS['endpoint'], path)
12511276
resp = transport.post(url,
1252-
data=payload,
1253-
headers=headers,
1254-
timeout=SETTINGS.get('timeout', DEFAULT_TIMEOUT),
1255-
verify=SETTINGS.get('verify_https', True))
1277+
data=payload_str,
1278+
headers=headers,
1279+
timeout=SETTINGS.get('timeout', DEFAULT_TIMEOUT),
1280+
verify=SETTINGS.get('verify_https', True))
12561281

1257-
return _parse_response(path, SETTINGS['access_token'], payload, resp)
1282+
return _parse_response(path, SETTINGS['access_token'], payload_str, resp)
12581283

12591284

12601285
def _get_api(path, access_token=None, endpoint=None, **params):
@@ -1265,46 +1290,47 @@ def _get_api(path, access_token=None, endpoint=None, **params):
12651290
return _parse_response(path, access_token, params, resp, endpoint=endpoint)
12661291

12671292

1268-
def _send_payload_tornado(payload, access_token):
1293+
def _send_payload_tornado(payload_str, access_token):
12691294
try:
1270-
_post_api_tornado('item/', payload, access_token=access_token)
1295+
_post_api_tornado('item/', payload_str, access_token=access_token)
12711296
except Exception as e:
12721297
log.exception('Exception while posting item %r', e)
12731298

12741299

12751300
@tornado_coroutine
1276-
def _post_api_tornado(path, payload, access_token=None):
1301+
def _post_api_tornado(path, payload_str, access_token=None):
12771302
headers = {'Content-Type': 'application/json'}
12781303

12791304
if access_token is not None:
12801305
headers['X-Rollbar-Access-Token'] = access_token
12811306

12821307
url = urljoin(SETTINGS['endpoint'], path)
12831308

1284-
resp = yield TornadoAsyncHTTPClient().fetch(
1285-
url, body=payload, method='POST', connect_timeout=SETTINGS.get('timeout', DEFAULT_TIMEOUT),
1286-
request_timeout=SETTINGS.get('timeout', DEFAULT_TIMEOUT)
1287-
)
1309+
resp = yield TornadoAsyncHTTPClient().fetch(url,
1310+
body=payload_str,
1311+
method='POST',
1312+
connect_timeout=SETTINGS.get('timeout', DEFAULT_TIMEOUT),
1313+
request_timeout=SETTINGS.get('timeout', DEFAULT_TIMEOUT))
12881314

12891315
r = requests.Response()
12901316
r._content = resp.body
12911317
r.status_code = resp.code
12921318
r.headers.update(resp.headers)
12931319

1294-
_parse_response(path, SETTINGS['access_token'], payload, r)
1320+
_parse_response(path, SETTINGS['access_token'], payload_str, r)
12951321

12961322

1297-
def _send_payload_twisted(payload, access_token):
1323+
def _send_payload_twisted(payload_str, access_token):
12981324
try:
1299-
_post_api_twisted('item/', payload, access_token=access_token)
1325+
_post_api_twisted('item/', payload_str, access_token=access_token)
13001326
except Exception as e:
13011327
log.exception('Exception while posting item %r', e)
13021328

13031329

1304-
def _post_api_twisted(path, payload, access_token=None):
1330+
def _post_api_twisted(path, payload_str, access_token=None):
13051331
def post_data_cb(data, resp):
13061332
resp._content = data
1307-
_parse_response(path, SETTINGS['access_token'], payload, resp)
1333+
_parse_response(path, SETTINGS['access_token'], payload_str, resp)
13081334

13091335
def post_cb(resp):
13101336
r = requests.Response()
@@ -1317,7 +1343,7 @@ def post_cb(resp):
13171343
headers['X-Rollbar-Access-Token'] = [access_token]
13181344

13191345
url = urljoin(SETTINGS['endpoint'], path)
1320-
d = treq.post(url, payload, headers=headers,
1346+
d = treq.post(url, payload_str, headers=headers,
13211347
timeout=SETTINGS.get('timeout', DEFAULT_TIMEOUT))
13221348
d.addCallback(post_cb)
13231349

rollbar/lib/events.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
EXCEPTION_INFO = 'exception_info'
2+
MESSAGE = 'message'
3+
PAYLOAD = 'payload'
4+
5+
_event_handlers = {
6+
EXCEPTION_INFO: [],
7+
MESSAGE: [],
8+
PAYLOAD: []
9+
}
10+
11+
12+
def _check_type(typ):
13+
if typ not in _event_handlers:
14+
raise ValueError('Unknown type: %s. Must be one of %s' % (typ, _event_handlers.keys()))
15+
16+
17+
def _add_handler(typ, handler_fn, pos):
18+
_check_type(typ)
19+
20+
pos = pos if pos is not None else -1
21+
handlers = _event_handlers[typ]
22+
23+
try:
24+
handlers.index(handler_fn)
25+
except ValueError:
26+
handlers.insert(pos, handler_fn)
27+
28+
29+
def _remove_handler(typ, handler_fn):
30+
_check_type(typ)
31+
32+
handlers = _event_handlers[typ]
33+
34+
try:
35+
index = handlers.index(handler_fn)
36+
handlers.pop(index)
37+
except ValueError:
38+
pass
39+
40+
41+
def _on_event(typ, target, **kw):
42+
_check_type(typ)
43+
44+
ref = target
45+
for handler in _event_handlers[typ]:
46+
result = handler(ref, **kw)
47+
if result is False:
48+
return False
49+
50+
ref = result
51+
52+
return ref
53+
54+
55+
# Add/remove event handlers
56+
57+
def add_exception_info_handler(handler_fn, pos=None):
58+
_add_handler(EXCEPTION_INFO, handler_fn, pos)
59+
60+
61+
def remove_exception_info_handler(handler_fn):
62+
_remove_handler(EXCEPTION_INFO, handler_fn)
63+
64+
65+
def add_message_handler(handler_fn, pos=None):
66+
_add_handler(MESSAGE, handler_fn, pos)
67+
68+
69+
def remove_message_handler(handler_fn):
70+
_remove_handler(MESSAGE, handler_fn)
71+
72+
73+
def add_payload_handler(handler_fn, pos=None):
74+
_add_handler(PAYLOAD, handler_fn, pos)
75+
76+
77+
def remove_payload_handler(handler_fn):
78+
_remove_handler(PAYLOAD, handler_fn)
79+
80+
81+
# Event handler processing
82+
83+
def on_exception_info(exc_info, **kw):
84+
return _on_event(EXCEPTION_INFO, exc_info, **kw)
85+
86+
87+
def on_message(message, **kw):
88+
return _on_event(MESSAGE, message, **kw)
89+
90+
91+
def on_payload(payload, **kw):
92+
return _on_event(PAYLOAD, payload, **kw)
93+
94+
95+
# Misc
96+
97+
def reset():
98+
for handlers in _event_handlers.values():
99+
del handlers[:]

rollbar/lib/filters/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from rollbar.lib import events
2+
from rollbar.lib.filters.basic import filter_rollbar_ignored_exceptions, filter_by_level
3+
4+
5+
def add_builtin_filters(settings):
6+
# exc_info filters
7+
events.add_exception_info_handler(filter_rollbar_ignored_exceptions)
8+
events.add_exception_info_handler(filter_by_level)
9+
10+
# message filters
11+
events.add_message_handler(filter_by_level)

0 commit comments

Comments
 (0)