From 7198a7526d54d148218b75a2f0c3bc5d30bf2b15 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 18 Oct 2024 14:51:11 +0200 Subject: [PATCH 01/12] extract full stack frame and give it to where it is needed --- sentry_sdk/utils.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 4d07974809..01da517a6f 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -15,6 +15,7 @@ from decimal import Decimal from functools import partial, partialmethod, wraps from numbers import Real +import traceback from urllib.parse import parse_qs, unquote, urlencode, urlsplit, urlunsplit try: @@ -737,6 +738,7 @@ def single_exception_from_error_tuple( exception_id=None, # type: Optional[int] parent_id=None, # type: Optional[int] source=None, # type: Optional[str] + full_stack=None, ): # type: (...) -> Dict[str, Any] """ @@ -807,6 +809,10 @@ def single_exception_from_error_tuple( ] if frames: + # TODO: insert missing frames from full_stack into frames + # elements of frames list look like this: + # {'filename': 'main.py', 'abs_path': '/Users/antonpirker/code/testing-sentry/test-plain-python-missing-stack-frames/main.py', 'function': 'foo', 'module': '__main__', 'lineno': 19, 'pre_context': [' foo()', '', '', 'def foo():', ' try:'], 'context_line': ' bar()', 'post_context': [' except Exception as e:', ' capture_exception(e)', '', '', 'def bar():'], 'vars': {'e': "Exception('1 some exception')"}} + # elements of full_stack are instances of type FrameSummary exception_value["stacktrace"] = {"frames": frames} return exception_value @@ -953,6 +959,7 @@ def exceptions_from_error_tuple( exc_info, # type: ExcInfo client_options=None, # type: Optional[Dict[str, Any]] mechanism=None, # type: Optional[Dict[str, Any]] + full_stack=None, ): # type: (...) -> List[Dict[str, Any]] exc_type, exc_value, tb = exc_info @@ -970,6 +977,7 @@ def exceptions_from_error_tuple( mechanism=mechanism, exception_id=0, parent_id=0, + full_stack=full_stack, ) else: @@ -977,7 +985,12 @@ def exceptions_from_error_tuple( for exc_type, exc_value, tb in walk_exception_chain(exc_info): exceptions.append( single_exception_from_error_tuple( - exc_type, exc_value, tb, client_options, mechanism + exc_type=exc_type, + exc_value=exc_value, + tb=tb, + client_options=client_options, + mechanism=mechanism, + full_stack=full_stack, ) ) @@ -1104,12 +1117,13 @@ def event_from_exception( # type: (...) -> Tuple[Event, Dict[str, Any]] exc_info = exc_info_from_error(exc_info) hint = event_hint_with_exc_info(exc_info) + full_stack = traceback.extract_stack() return ( { "level": "error", "exception": { "values": exceptions_from_error_tuple( - exc_info, client_options, mechanism + exc_info, client_options, mechanism, full_stack ) }, }, From 8ae7d34529db1980de500b6fe443f085defbdcd7 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 18 Oct 2024 17:52:37 +0200 Subject: [PATCH 02/12] Adding missing frames --- sentry_sdk/utils.py | 54 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 01da517a6f..550eb8e3b6 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -738,7 +738,7 @@ def single_exception_from_error_tuple( exception_id=None, # type: Optional[int] parent_id=None, # type: Optional[int] source=None, # type: Optional[str] - full_stack=None, + full_stack=None, ): # type: (...) -> Dict[str, Any] """ @@ -813,6 +813,50 @@ def single_exception_from_error_tuple( # elements of frames list look like this: # {'filename': 'main.py', 'abs_path': '/Users/antonpirker/code/testing-sentry/test-plain-python-missing-stack-frames/main.py', 'function': 'foo', 'module': '__main__', 'lineno': 19, 'pre_context': [' foo()', '', '', 'def foo():', ' try:'], 'context_line': ' bar()', 'post_context': [' except Exception as e:', ' capture_exception(e)', '', '', 'def bar():'], 'vars': {'e': "Exception('1 some exception')"}} # elements of full_stack are instances of type FrameSummary + + full_stack.reverse() + frames.reverse() + + for stackframe in full_stack: + stackframe_id = { + "filename": stackframe.filename, + "line": stackframe.line, + "lineno": stackframe.lineno, + "name": stackframe.name, + } + + found = False + for frame in frames: + frame_id = { + "filename": frame["abs_path"], + "line": frame["context_line"], + "lineno": frame["lineno"], + "name": frame["function"], + } + + if stackframe_id == frame_id: + found = True + break + + if not found: + frames.append( + { + "filename": os.path.basename(stackframe.filename), + "abs_path": stackframe.filename, + "function": stackframe.name, + "module": None, + "lineno": stackframe.lineno, + "pre_context": [], + "context_line": stackframe.line, + "post_context": [], + "vars": {}, + } + ) + + frames.reverse() + from pprint import pprint + + pprint(frames) exception_value["stacktrace"] = {"frames": frames} return exception_value @@ -986,10 +1030,10 @@ def exceptions_from_error_tuple( exceptions.append( single_exception_from_error_tuple( exc_type=exc_type, - exc_value=exc_value, - tb=tb, - client_options=client_options, - mechanism=mechanism, + exc_value=exc_value, + tb=tb, + client_options=client_options, + mechanism=mechanism, full_stack=full_stack, ) ) From e69ca9a5051c54f5a6d8057e6c55d7245874fde4 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 21 Oct 2024 09:56:15 +0200 Subject: [PATCH 03/12] Added some todos --- sentry_sdk/utils.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 550eb8e3b6..8839b2d9ad 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -1161,7 +1161,14 @@ def event_from_exception( # type: (...) -> Tuple[Event, Dict[str, Any]] exc_info = exc_info_from_error(exc_info) hint = event_hint_with_exc_info(exc_info) + # TODO: do not use extract_stack() but get the real stack frames (instead of summaries with extract_stack) + # to get all the information about the frames full_stack = traceback.extract_stack() + + # TODO: add an option "add_full_stack" to the client options to add the full stack to the event (defaults to True) + + # TODO: add an option "max_stack_frames" to the client options to limit the number of stack frames (defaults to 50?) + return ( { "level": "error", From 3645218e34783664db04879cfd17e5d2132698cf Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Thu, 24 Oct 2024 17:25:46 +0200 Subject: [PATCH 04/12] Have full frames of the full stack trace --- sentry_sdk/utils.py | 64 ++++++++++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 8839b2d9ad..741e2c9d5f 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -819,10 +819,10 @@ def single_exception_from_error_tuple( for stackframe in full_stack: stackframe_id = { - "filename": stackframe.filename, - "line": stackframe.line, - "lineno": stackframe.lineno, - "name": stackframe.name, + "filename": stackframe["abs_path"], + "line": stackframe["context_line"], + "lineno": stackframe["lineno"], + "name": stackframe["function"], } found = False @@ -839,19 +839,7 @@ def single_exception_from_error_tuple( break if not found: - frames.append( - { - "filename": os.path.basename(stackframe.filename), - "abs_path": stackframe.filename, - "function": stackframe.name, - "module": None, - "lineno": stackframe.lineno, - "pre_context": [], - "context_line": stackframe.line, - "post_context": [], - "vars": {}, - } - ) + frames.append(stackframe) frames.reverse() from pprint import pprint @@ -1153,6 +1141,42 @@ def exc_info_from_error(error): return exc_info +def get_full_stack(): + # type: () -> List[Dict[str, Any]] + """ + Returns a serialized representation of the full stack from the first frame that is not in sentry_sdk. + """ + try: + # Raise an exception to capture the current stack + raise Exception + except: + # Get the current stack frame + _, _, tb = sys.exc_info() + + # Get the frame one level up (skipping this function's frame) + frame = tb.tb_frame.f_back + + stack_info = [] + + # Walk up the stack + while frame: + in_sdk = False + try: + if "sentry_sdk" in frame.f_code.co_filename: + in_sdk = True + except Exception: + pass + + if not in_sdk: + stack_info.append(serialize_frame(frame)) + + frame = frame.f_back + + stack_info.reverse() + + return stack_info + + def event_from_exception( exc_info, # type: Union[BaseException, ExcInfo] client_options=None, # type: Optional[Dict[str, Any]] @@ -1161,12 +1185,10 @@ def event_from_exception( # type: (...) -> Tuple[Event, Dict[str, Any]] exc_info = exc_info_from_error(exc_info) hint = event_hint_with_exc_info(exc_info) - # TODO: do not use extract_stack() but get the real stack frames (instead of summaries with extract_stack) - # to get all the information about the frames - full_stack = traceback.extract_stack() + full_stack = get_full_stack() # TODO: add an option "add_full_stack" to the client options to add the full stack to the event (defaults to True) - + # TODO: add an option "max_stack_frames" to the client options to limit the number of stack frames (defaults to 50?) return ( From 31d059c5d233401ba7864c8d6d8520dcbe06689a Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Thu, 24 Oct 2024 19:40:17 +0200 Subject: [PATCH 05/12] Added full frames to full_stack --- sentry_sdk/utils.py | 71 ++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 741e2c9d5f..07cac051d0 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -738,7 +738,7 @@ def single_exception_from_error_tuple( exception_id=None, # type: Optional[int] parent_id=None, # type: Optional[int] source=None, # type: Optional[str] - full_stack=None, + full_stack=None, # type: Optional[list[dict[str, Any]]] ): # type: (...) -> Dict[str, Any] """ @@ -809,43 +809,34 @@ def single_exception_from_error_tuple( ] if frames: - # TODO: insert missing frames from full_stack into frames - # elements of frames list look like this: - # {'filename': 'main.py', 'abs_path': '/Users/antonpirker/code/testing-sentry/test-plain-python-missing-stack-frames/main.py', 'function': 'foo', 'module': '__main__', 'lineno': 19, 'pre_context': [' foo()', '', '', 'def foo():', ' try:'], 'context_line': ' bar()', 'post_context': [' except Exception as e:', ' capture_exception(e)', '', '', 'def bar():'], 'vars': {'e': "Exception('1 some exception')"}} - # elements of full_stack are instances of type FrameSummary - - full_stack.reverse() - frames.reverse() - - for stackframe in full_stack: - stackframe_id = { - "filename": stackframe["abs_path"], - "line": stackframe["context_line"], - "lineno": stackframe["lineno"], - "name": stackframe["function"], + if not full_stack: + new_frames = frames + else: + # Add the missing frames from full_stack + frame_ids = { + ( + frame["abs_path"], + frame["context_line"], + frame["lineno"], + frame["function"], + ) + for frame in frames } - found = False - for frame in frames: - frame_id = { - "filename": frame["abs_path"], - "line": frame["context_line"], - "lineno": frame["lineno"], - "name": frame["function"], - } - - if stackframe_id == frame_id: - found = True - break - - if not found: - frames.append(stackframe) - - frames.reverse() - from pprint import pprint + new_frames = [ + stackframe + for stackframe in full_stack + if ( + stackframe["abs_path"], + stackframe["context_line"], + stackframe["lineno"], + stackframe["function"], + ) + not in frame_ids + ] + new_frames.extend(frames) - pprint(frames) - exception_value["stacktrace"] = {"frames": frames} + exception_value["stacktrace"] = {"frames": new_frames} return exception_value @@ -900,6 +891,7 @@ def exceptions_from_error( exception_id=0, # type: int parent_id=0, # type: int source=None, # type: Optional[str] + full_stack=None, # type: Optional[list[dict[str, Any]]] ): # type: (...) -> Tuple[int, List[Dict[str, Any]]] """ @@ -919,6 +911,7 @@ def exceptions_from_error( exception_id=exception_id, parent_id=parent_id, source=source, + full_stack=full_stack, ) exceptions = [parent] @@ -944,6 +937,7 @@ def exceptions_from_error( mechanism=mechanism, exception_id=exception_id, source="__cause__", + full_stack=full_stack, ) exceptions.extend(child_exceptions) @@ -965,6 +959,7 @@ def exceptions_from_error( mechanism=mechanism, exception_id=exception_id, source="__context__", + full_stack=full_stack, ) exceptions.extend(child_exceptions) @@ -981,6 +976,7 @@ def exceptions_from_error( exception_id=exception_id, parent_id=parent_id, source="exceptions[%s]" % idx, + full_stack=full_stack, ) exceptions.extend(child_exceptions) @@ -991,7 +987,7 @@ def exceptions_from_error_tuple( exc_info, # type: ExcInfo client_options=None, # type: Optional[Dict[str, Any]] mechanism=None, # type: Optional[Dict[str, Any]] - full_stack=None, + full_stack=None, # type: Optional[list[dict[str, Any]]] ): # type: (...) -> List[Dict[str, Any]] exc_type, exc_value, tb = exc_info @@ -1154,6 +1150,9 @@ def get_full_stack(): _, _, tb = sys.exc_info() # Get the frame one level up (skipping this function's frame) + if tb is None: + return [] + frame = tb.tb_frame.f_back stack_info = [] From 4dbf6617bbb5b478b9c360b6bcded7924ef343e2 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Sat, 16 Nov 2024 13:31:09 +0100 Subject: [PATCH 06/12] Improvements --- sentry_sdk/consts.py | 2 ++ sentry_sdk/utils.py | 31 +++++++++++++------------------ 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index ae32294d05..20fd981aae 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -550,6 +550,8 @@ def __init__( cert_file=None, # type: Optional[str] key_file=None, # type: Optional[str] custom_repr=None, # type: Optional[Callable[..., Optional[str]]] + add_full_stack=False, # type: bool + max_stack_frames=50, # type: Optional[int] ): # type: (...) -> None pass diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 07cac051d0..2257a6fd06 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -806,7 +806,7 @@ def single_exception_from_error_tuple( custom_repr=custom_repr, ) for tb in iter_stacks(tb) - ] + ] # type: List[Dict[str, Any]] if frames: if not full_stack: @@ -836,6 +836,13 @@ def single_exception_from_error_tuple( ] new_frames.extend(frames) + # Limit the number of frames + max_stack_frames = ( + client_options.get("max_stack_frames") if client_options else None + ) + if max_stack_frames is not None: + new_frames = new_frames[:max_stack_frames] + exception_value["stacktrace"] = {"frames": new_frames} return exception_value @@ -1142,22 +1149,10 @@ def get_full_stack(): """ Returns a serialized representation of the full stack from the first frame that is not in sentry_sdk. """ - try: - # Raise an exception to capture the current stack - raise Exception - except: - # Get the current stack frame - _, _, tb = sys.exc_info() - - # Get the frame one level up (skipping this function's frame) - if tb is None: - return [] - - frame = tb.tb_frame.f_back - stack_info = [] # Walk up the stack + frame = sys._getframe(1) # type: Optional[FrameType] while frame: in_sdk = False try: @@ -1184,11 +1179,11 @@ def event_from_exception( # type: (...) -> Tuple[Event, Dict[str, Any]] exc_info = exc_info_from_error(exc_info) hint = event_hint_with_exc_info(exc_info) - full_stack = get_full_stack() - # TODO: add an option "add_full_stack" to the client options to add the full stack to the event (defaults to True) - - # TODO: add an option "max_stack_frames" to the client options to limit the number of stack frames (defaults to 50?) + if client_options and client_options["add_full_stack"]: + full_stack = get_full_stack() + else: + full_stack = None return ( { From 04f79ed0d37d70c346b99486473e126bad9adeef Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Sat, 16 Nov 2024 13:33:08 +0100 Subject: [PATCH 07/12] linting --- sentry_sdk/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 2257a6fd06..a7766f29a8 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -15,7 +15,6 @@ from decimal import Decimal from functools import partial, partialmethod, wraps from numbers import Real -import traceback from urllib.parse import parse_qs, unquote, urlencode, urlsplit, urlunsplit try: From e4f33972b9f0ed76261047397efc724e04dbf0c7 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Sat, 16 Nov 2024 13:59:02 +0100 Subject: [PATCH 08/12] Make it more resilient againt misuse --- sentry_sdk/consts.py | 7 +++++-- sentry_sdk/utils.py | 13 ++++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 20fd981aae..a6531c6c30 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -6,6 +6,9 @@ # up top to prevent circular import due to integration import DEFAULT_MAX_VALUE_LENGTH = 1024 +DEFAULT_MAX_STACK_FRAMES = 100 +DEFAULT_ADD_FULL_STACK = False + # Also needs to be at the top to prevent circular import class EndpointType(Enum): @@ -550,8 +553,8 @@ def __init__( cert_file=None, # type: Optional[str] key_file=None, # type: Optional[str] custom_repr=None, # type: Optional[Callable[..., Optional[str]]] - add_full_stack=False, # type: bool - max_stack_frames=50, # type: Optional[int] + add_full_stack=DEFAULT_ADD_FULL_STACK, # type: bool + max_stack_frames=DEFAULT_MAX_STACK_FRAMES, # type: Optional[int] ): # type: (...) -> None pass diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index a7766f29a8..f7f12ecb14 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -26,7 +26,12 @@ import sentry_sdk from sentry_sdk._compat import PY37 -from sentry_sdk.consts import DEFAULT_MAX_VALUE_LENGTH, EndpointType +from sentry_sdk.consts import ( + DEFAULT_ADD_FULL_STACK, + DEFAULT_MAX_STACK_FRAMES, + DEFAULT_MAX_VALUE_LENGTH, + EndpointType, +) from typing import TYPE_CHECKING @@ -837,7 +842,9 @@ def single_exception_from_error_tuple( # Limit the number of frames max_stack_frames = ( - client_options.get("max_stack_frames") if client_options else None + client_options.get("max_stack_frames", DEFAULT_MAX_STACK_FRAMES) + if client_options + else None ) if max_stack_frames is not None: new_frames = new_frames[:max_stack_frames] @@ -1179,7 +1186,7 @@ def event_from_exception( exc_info = exc_info_from_error(exc_info) hint = event_hint_with_exc_info(exc_info) - if client_options and client_options["add_full_stack"]: + if client_options and client_options.get("add_full_stack", DEFAULT_ADD_FULL_STACK): full_stack = get_full_stack() else: full_stack = None From 39db548b371668c2b63464f863a881ca9671d1dc Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Sat, 16 Nov 2024 14:15:10 +0100 Subject: [PATCH 09/12] Cleanup --- sentry_sdk/utils.py | 73 +++++++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index f7f12ecb14..594e2f74a9 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -816,38 +816,7 @@ def single_exception_from_error_tuple( if not full_stack: new_frames = frames else: - # Add the missing frames from full_stack - frame_ids = { - ( - frame["abs_path"], - frame["context_line"], - frame["lineno"], - frame["function"], - ) - for frame in frames - } - - new_frames = [ - stackframe - for stackframe in full_stack - if ( - stackframe["abs_path"], - stackframe["context_line"], - stackframe["lineno"], - stackframe["function"], - ) - not in frame_ids - ] - new_frames.extend(frames) - - # Limit the number of frames - max_stack_frames = ( - client_options.get("max_stack_frames", DEFAULT_MAX_STACK_FRAMES) - if client_options - else None - ) - if max_stack_frames is not None: - new_frames = new_frames[:max_stack_frames] + new_frames = merge_stack_frames(frames, full_stack, client_options) exception_value["stacktrace"] = {"frames": new_frames} @@ -1177,6 +1146,46 @@ def get_full_stack(): return stack_info +def merge_stack_frames(frames, full_stack, client_options): + # type: (List[Dict[str, Any]], List[Dict[str, Any]], Optional[Dict[str, Any]]) -> List[Dict[str, Any]] + """ + Add the missing frames from full_stack to frames and return the merged list. + """ + frame_ids = { + ( + frame["abs_path"], + frame["context_line"], + frame["lineno"], + frame["function"], + ) + for frame in frames + } + + new_frames = [ + stackframe + for stackframe in full_stack + if ( + stackframe["abs_path"], + stackframe["context_line"], + stackframe["lineno"], + stackframe["function"], + ) + not in frame_ids + ] + new_frames.extend(frames) + + # Limit the number of frames + max_stack_frames = ( + client_options.get("max_stack_frames", DEFAULT_MAX_STACK_FRAMES) + if client_options + else None + ) + if max_stack_frames is not None: + new_frames = new_frames[:max_stack_frames] + + return new_frames + + def event_from_exception( exc_info, # type: Union[BaseException, ExcInfo] client_options=None, # type: Optional[Dict[str, Any]] From 10d7a498b880d6d17603da528aba363b811fa227 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Sat, 16 Nov 2024 14:39:51 +0100 Subject: [PATCH 10/12] Added tests --- sentry_sdk/utils.py | 2 +- tests/test_full_stack_frames.py | 103 ++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 tests/test_full_stack_frames.py diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 594e2f74a9..6313d08206 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -1181,7 +1181,7 @@ def merge_stack_frames(frames, full_stack, client_options): else None ) if max_stack_frames is not None: - new_frames = new_frames[:max_stack_frames] + new_frames = new_frames[len(new_frames) - max_stack_frames :] return new_frames diff --git a/tests/test_full_stack_frames.py b/tests/test_full_stack_frames.py new file mode 100644 index 0000000000..ad0826cd10 --- /dev/null +++ b/tests/test_full_stack_frames.py @@ -0,0 +1,103 @@ +import sentry_sdk + + +def test_full_stack_frames_default(sentry_init, capture_events): + sentry_init() + events = capture_events() + + def foo(): + try: + bar() + except Exception as e: + sentry_sdk.capture_exception(e) + + def bar(): + raise Exception("This is a test exception") + + foo() + + (event,) = events + frames = event["exception"]["values"][0]["stacktrace"]["frames"] + + assert len(frames) == 2 + assert frames[-1]["function"] == "bar" + assert frames[-2]["function"] == "foo" + + +def test_full_stack_frames_enabled(sentry_init, capture_events): + sentry_init( + add_full_stack=True, + ) + events = capture_events() + + def foo(): + try: + bar() + except Exception as e: + sentry_sdk.capture_exception(e) + + def bar(): + raise Exception("This is a test exception") + + foo() + + (event,) = events + frames = event["exception"]["values"][0]["stacktrace"]["frames"] + + assert len(frames) > 2 + assert frames[-1]["function"] == "bar" + assert frames[-2]["function"] == "foo" + assert frames[-3]["function"] == "foo" + assert frames[-4]["function"] == "test_full_stack_frames_enabled" + + +def test_full_stack_frames_enabled_truncated(sentry_init, capture_events): + sentry_init( + add_full_stack=True, + max_stack_frames=3, + ) + events = capture_events() + + def foo(): + try: + bar() + except Exception as e: + sentry_sdk.capture_exception(e) + + def bar(): + raise Exception("This is a test exception") + + foo() + + (event,) = events + frames = event["exception"]["values"][0]["stacktrace"]["frames"] + + assert len(frames) == 3 + assert frames[-1]["function"] == "bar" + assert frames[-2]["function"] == "foo" + assert frames[-3]["function"] == "foo" + + +def test_full_stack_frames_default_no_truncation_happening(sentry_init, capture_events): + sentry_init( + max_stack_frames=1, # this is ignored if add_full_stack=False (which is the default) + ) + events = capture_events() + + def foo(): + try: + bar() + except Exception as e: + sentry_sdk.capture_exception(e) + + def bar(): + raise Exception("This is a test exception") + + foo() + + (event,) = events + frames = event["exception"]["values"][0]["stacktrace"]["frames"] + + assert len(frames) == 2 + assert frames[-1]["function"] == "bar" + assert frames[-2]["function"] == "foo" From 9dae6f847828bb22d25a8de6aeb0550b8db31a0e Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Wed, 27 Nov 2024 12:22:38 +0100 Subject: [PATCH 11/12] trying something --- sentry_sdk/utils.py | 32 ++++---------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 6313d08206..ae6e7538ac 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -1119,33 +1119,6 @@ def exc_info_from_error(error): return exc_info -def get_full_stack(): - # type: () -> List[Dict[str, Any]] - """ - Returns a serialized representation of the full stack from the first frame that is not in sentry_sdk. - """ - stack_info = [] - - # Walk up the stack - frame = sys._getframe(1) # type: Optional[FrameType] - while frame: - in_sdk = False - try: - if "sentry_sdk" in frame.f_code.co_filename: - in_sdk = True - except Exception: - pass - - if not in_sdk: - stack_info.append(serialize_frame(frame)) - - frame = frame.f_back - - stack_info.reverse() - - return stack_info - - def merge_stack_frames(frames, full_stack, client_options): # type: (List[Dict[str, Any]], List[Dict[str, Any]], Optional[Dict[str, Any]]) -> List[Dict[str, Any]] """ @@ -1196,7 +1169,10 @@ def event_from_exception( hint = event_hint_with_exc_info(exc_info) if client_options and client_options.get("add_full_stack", DEFAULT_ADD_FULL_STACK): - full_stack = get_full_stack() + full_stack = current_stacktrace( + include_local_variables=client_options["include_local_variables"], + max_value_length=client_options["max_value_length"], + )["frames"] else: full_stack = None From 59602427a7ef553358f0cccc442899bc3b6e6eb5 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Wed, 27 Nov 2024 14:05:51 +0100 Subject: [PATCH 12/12] Use new clickhouse gh action --- .github/workflows/test-integrations-dbs.yml | 4 ++-- scripts/split-tox-gh-actions/templates/test_group.jinja | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-integrations-dbs.yml b/.github/workflows/test-integrations-dbs.yml index 1612dfb432..a3ba66bc96 100644 --- a/.github/workflows/test-integrations-dbs.yml +++ b/.github/workflows/test-integrations-dbs.yml @@ -57,7 +57,7 @@ jobs: with: python-version: ${{ matrix.python-version }} allow-prereleases: true - - uses: getsentry/action-clickhouse-in-ci@v1 + - uses: getsentry/action-clickhouse-in-ci@v1.1 - name: Setup Test Env run: | pip install "coverage[toml]" tox @@ -152,7 +152,7 @@ jobs: with: python-version: ${{ matrix.python-version }} allow-prereleases: true - - uses: getsentry/action-clickhouse-in-ci@v1 + - uses: getsentry/action-clickhouse-in-ci@v1.1 - name: Setup Test Env run: | pip install "coverage[toml]" tox diff --git a/scripts/split-tox-gh-actions/templates/test_group.jinja b/scripts/split-tox-gh-actions/templates/test_group.jinja index 4560a7d42d..b2de0d5393 100644 --- a/scripts/split-tox-gh-actions/templates/test_group.jinja +++ b/scripts/split-tox-gh-actions/templates/test_group.jinja @@ -51,7 +51,7 @@ python-version: {% raw %}${{ matrix.python-version }}{% endraw %} allow-prereleases: true {% if needs_clickhouse %} - - uses: getsentry/action-clickhouse-in-ci@v1 + - uses: getsentry/action-clickhouse-in-ci@v1.1 {% endif %} {% if needs_redis %}