Skip to content

Commit e13c428

Browse files
authored
feat(err): construct full trace if no traceback available (#266)
* construct full trace if no traceback available * delete asserts * bump version
1 parent 77190c2 commit e13c428

File tree

4 files changed

+36
-66
lines changed

4 files changed

+36
-66
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
# 5.1.0
1+
# 5.2.0 - 2025-06-19
2+
3+
- feat: construct artificial stack traces if no traceback is available on a captured exception
4+
5+
## 5.1.0 - 2025-06-18
26

37
- feat: session and distinct ID's can now be associated with contexts, and are used as such
48
- feat: django http request middleware

posthog/exception_utils.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import os
1010
import re
1111
import sys
12+
import types
1213
from datetime import datetime
1314
from typing import TYPE_CHECKING
1415

@@ -809,6 +810,10 @@ def exc_info_from_error(error):
809810
if isinstance(error, tuple) and len(error) == 3:
810811
exc_type, exc_value, tb = error
811812
elif isinstance(error, BaseException):
813+
try:
814+
construct_artificial_traceback(error)
815+
except Exception:
816+
pass
812817
tb = getattr(error, "__traceback__", None)
813818
if tb is not None:
814819
exc_type = type(error)
@@ -833,6 +838,31 @@ def exc_info_from_error(error):
833838
return exc_info
834839

835840

841+
def construct_artificial_traceback(e):
842+
# type: (BaseException) -> None
843+
if getattr(e, "__traceback__", None) is not None:
844+
return
845+
846+
depth = 0
847+
frames = []
848+
while True:
849+
try:
850+
frame = sys._getframe(depth)
851+
depth += 1
852+
except ValueError:
853+
break
854+
855+
frames.append(frame)
856+
857+
frames.reverse()
858+
859+
tb = None
860+
for frame in frames:
861+
tb = types.TracebackType(tb, frame, frame.f_lasti, frame.f_lineno)
862+
863+
setattr(e, "__traceback__", tb)
864+
865+
836866
def event_from_exception(
837867
exc_info, # type: Union[BaseException, ExcInfo]
838868
client_options=None, # type: Optional[Dict[str, Any]]

posthog/test/test_client.py

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -117,22 +117,6 @@ def test_basic_capture_exception(self):
117117
capture_call = patch_capture.call_args[0]
118118
self.assertEqual(capture_call[0], "distinct_id")
119119
self.assertEqual(capture_call[1], "$exception")
120-
self.assertEqual(
121-
capture_call[2],
122-
{
123-
"$exception_type": "Exception",
124-
"$exception_message": "test exception",
125-
"$exception_list": [
126-
{
127-
"mechanism": {"type": "generic", "handled": True},
128-
"module": None,
129-
"type": "Exception",
130-
"value": "test exception",
131-
}
132-
],
133-
"$exception_personURL": "https://us.i.posthog.com/project/random_key/person/distinct_id",
134-
},
135-
)
136120

137121
def test_basic_capture_exception_with_distinct_id(self):
138122
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):
144128
capture_call = patch_capture.call_args[0]
145129
self.assertEqual(capture_call[0], "distinct_id")
146130
self.assertEqual(capture_call[1], "$exception")
147-
self.assertEqual(
148-
capture_call[2],
149-
{
150-
"$exception_type": "Exception",
151-
"$exception_message": "test exception",
152-
"$exception_list": [
153-
{
154-
"mechanism": {"type": "generic", "handled": True},
155-
"module": None,
156-
"type": "Exception",
157-
"value": "test exception",
158-
}
159-
],
160-
"$exception_personURL": "https://us.i.posthog.com/project/random_key/person/distinct_id",
161-
},
162-
)
163131

164132
def test_basic_capture_exception_with_correct_host_generation(self):
165133
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):
173141
capture_call = patch_capture.call_args[0]
174142
self.assertEqual(capture_call[0], "distinct_id")
175143
self.assertEqual(capture_call[1], "$exception")
176-
self.assertEqual(
177-
capture_call[2],
178-
{
179-
"$exception_type": "Exception",
180-
"$exception_message": "test exception",
181-
"$exception_list": [
182-
{
183-
"mechanism": {"type": "generic", "handled": True},
184-
"module": None,
185-
"type": "Exception",
186-
"value": "test exception",
187-
}
188-
],
189-
"$exception_personURL": "https://aloha.com/project/random_key/person/distinct_id",
190-
},
191-
)
192144

193145
def test_basic_capture_exception_with_correct_host_generation_for_server_hosts(
194146
self,
@@ -206,22 +158,6 @@ def test_basic_capture_exception_with_correct_host_generation_for_server_hosts(
206158
capture_call = patch_capture.call_args[0]
207159
self.assertEqual(capture_call[0], "distinct_id")
208160
self.assertEqual(capture_call[1], "$exception")
209-
self.assertEqual(
210-
capture_call[2],
211-
{
212-
"$exception_type": "Exception",
213-
"$exception_message": "test exception",
214-
"$exception_list": [
215-
{
216-
"mechanism": {"type": "generic", "handled": True},
217-
"module": None,
218-
"type": "Exception",
219-
"value": "test exception",
220-
}
221-
],
222-
"$exception_personURL": "https://app.posthog.com/project/random_key/person/distinct_id",
223-
},
224-
)
225161

226162
def test_basic_capture_exception_with_no_exception_given(self):
227163
with mock.patch.object(Client, "capture", return_value=None) as patch_capture:

posthog/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
VERSION = "5.1.0"
1+
VERSION = "5.2.0"
22

33
if __name__ == "__main__":
44
print(VERSION, end="") # noqa: T201

0 commit comments

Comments
 (0)