Skip to content

Commit b9d9d3b

Browse files
authored
Fix notice_error logic for non-iterable exceptions. (#1564)
1 parent 2dd463d commit b9d9d3b

File tree

3 files changed

+38
-6
lines changed

3 files changed

+38
-6
lines changed

newrelic/api/time_trace.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -360,16 +360,23 @@ def _observe_exception(self, exc_info=None, ignore=None, expected=None, status_c
360360
return fullname, message, message_raw, tb, is_expected
361361

362362
def notice_error(self, error=None, attributes=None, expected=None, ignore=None, status_code=None):
363+
def error_is_iterable(error):
364+
return hasattr(error, "__iter__") and not isinstance(error, (str, bytes))
365+
366+
def none_in_error(error):
367+
return error_is_iterable(error) and None in error
368+
363369
attributes = attributes if attributes is not None else {}
364370

365371
# If no exception details provided, use current exception.
366372

367-
# Pull from sys.exc_info if no exception is passed
368-
if not error or None in error:
373+
# Pull from sys.exc_info() if no exception is passed
374+
# Check that the error exists and that it is a fully populated iterable
375+
if not error or none_in_error(error) or (error and not error_is_iterable(error)):
369376
error = sys.exc_info()
370377

371378
# If no exception to report, exit
372-
if not error or None in error:
379+
if not error or none_in_error(error) or (error and not error_is_iterable(error)):
373380
return
374381

375382
exc, value, tb = error

newrelic/core/stats_engine.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -676,9 +676,14 @@ def record_time_metrics(self, metrics):
676676
self.record_time_metric(metric)
677677

678678
def notice_error(self, error=None, attributes=None, expected=None, ignore=None, status_code=None):
679+
def error_is_iterable(error):
680+
return hasattr(error, "__iter__") and not isinstance(error, (str, bytes))
681+
682+
def none_in_error(error):
683+
return error_is_iterable(error) and None in error
684+
679685
attributes = attributes if attributes is not None else {}
680686
settings = self.__settings
681-
682687
if not settings:
683688
return
684689

@@ -691,11 +696,12 @@ def notice_error(self, error=None, attributes=None, expected=None, ignore=None,
691696
return
692697

693698
# Pull from sys.exc_info if no exception is passed
694-
if not error or None in error:
699+
# Check that the error exists and that it is a fully populated iterable
700+
if not error or none_in_error(error) or (error and not error_is_iterable(error)):
695701
error = sys.exc_info()
696702

697703
# If no exception to report, exit
698-
if not error or None in error:
704+
if not error or none_in_error(error) or (error and not error_is_iterable(error)):
699705
return
700706

701707
exc, value, tb = error

tests/agent_features/test_notice_error.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,25 @@
3939

4040
# =============== Test errors during a transaction ===============
4141

42+
_key_error_name = callable_name(KeyError)
43+
44+
_test_notice_error_exception_object = [(_key_error_name, "'f'")]
45+
46+
47+
@validate_transaction_errors(errors=_test_notice_error_exception_object)
48+
@background_task()
49+
def test_notice_error_non_iterable_object():
50+
"""Test that notice_error works when passed an exception object directly"""
51+
try:
52+
test_dict = {"a": 4, "b": 5}
53+
# Force a KeyError
54+
test_dict["f"]
55+
except KeyError as e:
56+
# The caught exception here is a non-iterable, singular KeyError object with no associated traceback
57+
# This will exercise logic to pull from sys.exc_info() instead of using the exception directly
58+
notice_error(e)
59+
60+
4261
_test_notice_error_sys_exc_info = [(_runtime_error_name, "one")]
4362

4463

0 commit comments

Comments
 (0)