diff --git a/CHANGELOG.md b/CHANGELOG.md index c8d6db3d..006dac68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ -# 5.1.0 +# 5.2.0 - 2025-06-19 + +- feat: construct artificial stack traces if no traceback is available on a captured exception + +## 5.1.0 - 2025-06-18 - feat: session and distinct ID's can now be associated with contexts, and are used as such - feat: django http request middleware diff --git a/posthog/exception_utils.py b/posthog/exception_utils.py index fc8901ad..62e804b6 100644 --- a/posthog/exception_utils.py +++ b/posthog/exception_utils.py @@ -9,6 +9,7 @@ import os import re import sys +import types from datetime import datetime from typing import TYPE_CHECKING @@ -809,6 +810,10 @@ def exc_info_from_error(error): if isinstance(error, tuple) and len(error) == 3: exc_type, exc_value, tb = error elif isinstance(error, BaseException): + try: + construct_artificial_traceback(error) + except Exception: + pass tb = getattr(error, "__traceback__", None) if tb is not None: exc_type = type(error) @@ -833,6 +838,31 @@ def exc_info_from_error(error): return exc_info +def construct_artificial_traceback(e): + # type: (BaseException) -> None + if getattr(e, "__traceback__", None) is not None: + return + + depth = 0 + frames = [] + while True: + try: + frame = sys._getframe(depth) + depth += 1 + except ValueError: + break + + frames.append(frame) + + frames.reverse() + + tb = None + for frame in frames: + tb = types.TracebackType(tb, frame, frame.f_lasti, frame.f_lineno) + + setattr(e, "__traceback__", tb) + + def event_from_exception( exc_info, # type: Union[BaseException, ExcInfo] client_options=None, # type: Optional[Dict[str, Any]] diff --git a/posthog/test/test_client.py b/posthog/test/test_client.py index e0ab2a22..ab799798 100644 --- a/posthog/test/test_client.py +++ b/posthog/test/test_client.py @@ -117,22 +117,6 @@ def test_basic_capture_exception(self): capture_call = patch_capture.call_args[0] self.assertEqual(capture_call[0], "distinct_id") self.assertEqual(capture_call[1], "$exception") - self.assertEqual( - capture_call[2], - { - "$exception_type": "Exception", - "$exception_message": "test exception", - "$exception_list": [ - { - "mechanism": {"type": "generic", "handled": True}, - "module": None, - "type": "Exception", - "value": "test exception", - } - ], - "$exception_personURL": "https://us.i.posthog.com/project/random_key/person/distinct_id", - }, - ) def test_basic_capture_exception_with_distinct_id(self): with mock.patch.object(Client, "capture", return_value=None) as patch_capture: @@ -144,22 +128,6 @@ def test_basic_capture_exception_with_distinct_id(self): capture_call = patch_capture.call_args[0] self.assertEqual(capture_call[0], "distinct_id") self.assertEqual(capture_call[1], "$exception") - self.assertEqual( - capture_call[2], - { - "$exception_type": "Exception", - "$exception_message": "test exception", - "$exception_list": [ - { - "mechanism": {"type": "generic", "handled": True}, - "module": None, - "type": "Exception", - "value": "test exception", - } - ], - "$exception_personURL": "https://us.i.posthog.com/project/random_key/person/distinct_id", - }, - ) def test_basic_capture_exception_with_correct_host_generation(self): with mock.patch.object(Client, "capture", return_value=None) as patch_capture: @@ -173,22 +141,6 @@ def test_basic_capture_exception_with_correct_host_generation(self): capture_call = patch_capture.call_args[0] self.assertEqual(capture_call[0], "distinct_id") self.assertEqual(capture_call[1], "$exception") - self.assertEqual( - capture_call[2], - { - "$exception_type": "Exception", - "$exception_message": "test exception", - "$exception_list": [ - { - "mechanism": {"type": "generic", "handled": True}, - "module": None, - "type": "Exception", - "value": "test exception", - } - ], - "$exception_personURL": "https://aloha.com/project/random_key/person/distinct_id", - }, - ) def test_basic_capture_exception_with_correct_host_generation_for_server_hosts( self, @@ -206,22 +158,6 @@ def test_basic_capture_exception_with_correct_host_generation_for_server_hosts( capture_call = patch_capture.call_args[0] self.assertEqual(capture_call[0], "distinct_id") self.assertEqual(capture_call[1], "$exception") - self.assertEqual( - capture_call[2], - { - "$exception_type": "Exception", - "$exception_message": "test exception", - "$exception_list": [ - { - "mechanism": {"type": "generic", "handled": True}, - "module": None, - "type": "Exception", - "value": "test exception", - } - ], - "$exception_personURL": "https://app.posthog.com/project/random_key/person/distinct_id", - }, - ) def test_basic_capture_exception_with_no_exception_given(self): with mock.patch.object(Client, "capture", return_value=None) as patch_capture: diff --git a/posthog/version.py b/posthog/version.py index ab0c9f5d..96d5127c 100644 --- a/posthog/version.py +++ b/posthog/version.py @@ -1,4 +1,4 @@ -VERSION = "5.1.0" +VERSION = "5.2.0" if __name__ == "__main__": print(VERSION, end="") # noqa: T201