Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions newrelic/api/time_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,15 +362,19 @@ def _observe_exception(self, exc_info=None, ignore=None, expected=None, status_c
def notice_error(self, error=None, attributes=None, expected=None, ignore=None, status_code=None):
attributes = attributes if attributes is not None else {}

# If no exception details provided, use current exception.
# If an exception instance is passed, attempt to unpack it into an exception tuple with traceback
if isinstance(error, BaseException):
error = (type(error), error, getattr(error, "__traceback__", None))

# Pull from sys.exc_info if no exception is passed
if not error or None in error:
# Use current exception from sys.exc_info() if no exception was passed,
# or if the exception tuple is missing components like the traceback
if not error or (isinstance(error, (tuple, list)) and None in error):
error = sys.exc_info()

# If no exception to report, exit
if not error or None in error:
return
# Error should be a tuple or list of 3 elements by this point.
# If it's falsey or missing a component like the traceback, quietly exit early.
if not isinstance(error, (tuple, list)) or len(error) != 3 or None in error:
return

exc, value, tb = error

Expand Down
17 changes: 11 additions & 6 deletions newrelic/core/stats_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -678,7 +678,6 @@ def record_time_metrics(self, metrics):
def notice_error(self, error=None, attributes=None, expected=None, ignore=None, status_code=None):
attributes = attributes if attributes is not None else {}
settings = self.__settings

if not settings:
return

Expand All @@ -690,13 +689,19 @@ def notice_error(self, error=None, attributes=None, expected=None, ignore=None,
if not settings.collect_errors and not settings.collect_error_events:
return

# Pull from sys.exc_info if no exception is passed
if not error or None in error:
# If an exception instance is passed, attempt to unpack it into an exception tuple with traceback
if isinstance(error, BaseException):
error = (type(error), error, getattr(error, "__traceback__", None))

# Use current exception from sys.exc_info() if no exception was passed,
# or if the exception tuple is missing components like the traceback
if not error or (isinstance(error, (tuple, list)) and None in error):
error = sys.exc_info()

# If no exception to report, exit
if not error or None in error:
return
# Error should be a tuple or list of 3 elements by this point.
# If it's falsey or missing a component like the traceback, quietly exit early.
if not isinstance(error, (tuple, list)) or len(error) != 3 or None in error:
return

exc, value, tb = error

Expand Down
91 changes: 48 additions & 43 deletions tests/agent_features/test_notice_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,8 @@

# =============== Test errors during a transaction ===============

_test_notice_error_sys_exc_info = [(_runtime_error_name, "one")]


@validate_transaction_errors(errors=_test_notice_error_sys_exc_info)
@validate_transaction_errors(errors=[(_runtime_error_name, "one")])
@background_task()
def test_notice_error_sys_exc_info():
try:
Expand All @@ -51,10 +49,7 @@ def test_notice_error_sys_exc_info():
notice_error(sys.exc_info())


_test_notice_error_no_exc_info = [(_runtime_error_name, "one")]


@validate_transaction_errors(errors=_test_notice_error_no_exc_info)
@validate_transaction_errors(errors=[(_runtime_error_name, "one")])
@background_task()
def test_notice_error_no_exc_info():
try:
Expand All @@ -63,10 +58,44 @@ def test_notice_error_no_exc_info():
notice_error()


_test_notice_error_custom_params = [(_runtime_error_name, "one")]
@validate_transaction_errors(errors=[(_runtime_error_name, "one")])
@background_task()
def test_notice_error_exception_instance():
"""Test that notice_error works when passed an exception object directly"""
try:
raise RuntimeError("one")
except RuntimeError as e:
exc = e # Reassign name to ensure scope isn't lost

# Call notice_error outside of try/except block to ensure it's not pulling from sys.exc_info()
notice_error(exc)


@validate_transaction_errors(errors=[(_runtime_error_name, "one"), (_type_error_name, "two")])
@background_task()
def test_notice_error_exception_instance_multiple_exceptions():
"""Test that notice_error reports the passed exception object even when a different exception is active."""
try:
raise RuntimeError("one")
except RuntimeError as e:
exc1 = e # Reassign name to ensure scope isn't lost

try:
raise TypeError("two")
except TypeError as exc2:
notice_error(exc1)
notice_error(exc2)


@validate_transaction_error_event_count(0)
@background_task()
def test_notice_error_exception_instance_no_traceback():
"""Test that notice_error does not report an exception if it has not been raised as it has no __traceback__"""
exc = RuntimeError("one")
notice_error(exc) # Try once with no active exception


@validate_transaction_errors(errors=_test_notice_error_custom_params, required_params=[("key", "value")])
@validate_transaction_errors(errors=[(_runtime_error_name, "one")], required_params=[("key", "value")])
@background_task()
def test_notice_error_custom_params():
try:
Expand All @@ -75,10 +104,7 @@ def test_notice_error_custom_params():
notice_error(sys.exc_info(), attributes={"key": "value"})


_test_notice_error_multiple_different_type = [(_runtime_error_name, "one"), (_type_error_name, "two")]


@validate_transaction_errors(errors=_test_notice_error_multiple_different_type)
@validate_transaction_errors(errors=[(_runtime_error_name, "one"), (_type_error_name, "two")])
@background_task()
def test_notice_error_multiple_different_type():
try:
Expand All @@ -92,10 +118,7 @@ def test_notice_error_multiple_different_type():
notice_error()


_test_notice_error_multiple_same_type = [(_runtime_error_name, "one"), (_runtime_error_name, "two")]


@validate_transaction_errors(errors=_test_notice_error_multiple_same_type)
@validate_transaction_errors(errors=[(_runtime_error_name, "one"), (_runtime_error_name, "two")])
@background_task()
def test_notice_error_multiple_same_type():
try:
Expand All @@ -111,11 +134,9 @@ def test_notice_error_multiple_same_type():

# =============== Test errors outside a transaction ===============

_test_application_exception = [(_runtime_error_name, "one")]


@reset_core_stats_engine()
@validate_application_errors(errors=_test_application_exception)
@validate_application_errors(errors=[(_runtime_error_name, "one")])
def test_application_exception():
try:
raise RuntimeError("one")
Expand All @@ -124,11 +145,8 @@ def test_application_exception():
notice_error(application=application_instance)


_test_application_exception_sys_exc_info = [(_runtime_error_name, "one")]


@reset_core_stats_engine()
@validate_application_errors(errors=_test_application_exception_sys_exc_info)
@validate_application_errors(errors=[(_runtime_error_name, "one")])
def test_application_exception_sys_exec_info():
try:
raise RuntimeError("one")
Expand All @@ -137,11 +155,8 @@ def test_application_exception_sys_exec_info():
notice_error(sys.exc_info(), application=application_instance)


_test_application_exception_custom_params = [(_runtime_error_name, "one")]


@reset_core_stats_engine()
@validate_application_errors(errors=_test_application_exception_custom_params, required_params=[("key", "value")])
@validate_application_errors(errors=[(_runtime_error_name, "one")], required_params=[("key", "value")])
def test_application_exception_custom_params():
try:
raise RuntimeError("one")
Expand All @@ -150,11 +165,8 @@ def test_application_exception_custom_params():
notice_error(attributes={"key": "value"}, application=application_instance)


_test_application_exception_multiple = [(_runtime_error_name, "one"), (_runtime_error_name, "one")]


@reset_core_stats_engine()
@validate_application_errors(errors=_test_application_exception_multiple)
@validate_application_errors(errors=[(_runtime_error_name, "one"), (_runtime_error_name, "one")])
@background_task()
def test_application_exception_multiple():
"""Exceptions submitted straight to the stats engine doesn't check for
Expand All @@ -174,12 +186,11 @@ def test_application_exception_multiple():

# =============== Test exception message stripping/allowlisting ===============

_test_notice_error_strip_message_disabled = [(_runtime_error_name, "one")]

_strip_message_disabled_settings = {"strip_exception_messages.enabled": False}


@validate_transaction_errors(errors=_test_notice_error_strip_message_disabled)
@validate_transaction_errors(errors=[(_runtime_error_name, "one")])
@override_application_settings(_strip_message_disabled_settings)
@background_task()
def test_notice_error_strip_message_disabled():
Expand Down Expand Up @@ -215,12 +226,10 @@ def test_notice_error_strip_message_disabled_outside_transaction():
assert my_error.message == ErrorOne.message


_test_notice_error_strip_message_enabled = [(_runtime_error_name, STRIP_EXCEPTION_MESSAGE)]

_strip_message_enabled_settings = {"strip_exception_messages.enabled": True}


@validate_transaction_errors(errors=_test_notice_error_strip_message_enabled)
@validate_transaction_errors(errors=[(_runtime_error_name, STRIP_EXCEPTION_MESSAGE)])
@override_application_settings(_strip_message_enabled_settings)
@background_task()
def test_notice_error_strip_message_enabled():
Expand Down Expand Up @@ -256,15 +265,13 @@ def test_notice_error_strip_message_enabled_outside_transaction():
assert my_error.message == STRIP_EXCEPTION_MESSAGE


_test_notice_error_strip_message_in_allowlist = [(_runtime_error_name, "original error message")]

_strip_message_in_allowlist_settings = {
"strip_exception_messages.enabled": True,
"strip_exception_messages.allowlist": [_runtime_error_name],
}


@validate_transaction_errors(errors=_test_notice_error_strip_message_in_allowlist)
@validate_transaction_errors(errors=[(_runtime_error_name, "original error message")])
@override_application_settings(_strip_message_in_allowlist_settings)
@background_task()
def test_notice_error_strip_message_in_allowlist():
Expand Down Expand Up @@ -307,15 +314,13 @@ def test_notice_error_strip_message_in_allowlist_outside_transaction():
assert my_error.message == ErrorThree.message


_test_notice_error_strip_message_not_in_allowlist = [(_runtime_error_name, STRIP_EXCEPTION_MESSAGE)]

_strip_message_not_in_allowlist_settings = {
"strip_exception_messages.enabled": True,
"strip_exception_messages.allowlist": ["FooError", "BarError"],
}


@validate_transaction_errors(errors=_test_notice_error_strip_message_not_in_allowlist)
@validate_transaction_errors(errors=[(_runtime_error_name, STRIP_EXCEPTION_MESSAGE)])
@override_application_settings(_strip_message_not_in_allowlist_settings)
@background_task()
def test_notice_error_strip_message_not_in_allowlist():
Expand Down
Loading