Skip to content

Commit 0035bfe

Browse files
authored
chore(di): capture exception chain (#11771)
We augment the exception fields with known exception chaining attributes to allow capturing exception chaining relations. The fields need to be added manually because they are part of the BaseException built-in fields and are not included in the object's __dict__ attribute. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)
1 parent f483beb commit 0035bfe

File tree

4 files changed

+45
-4
lines changed

4 files changed

+45
-4
lines changed

ddtrace/debugging/_signal/utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,15 @@ def capture_value(
304304
}
305305

306306
fields = get_fields(value)
307+
308+
# Capture exception chain for exceptions
309+
if _isinstance(value, BaseException):
310+
for attr in ("args", "__cause__", "__context__", "__suppress_context__"):
311+
try:
312+
fields[attr] = object.__getattribute__(value, attr)
313+
except AttributeError:
314+
pass
315+
307316
captured_fields = {
308317
n: (
309318
capture_value(v, level=level - 1, maxlen=maxlen, maxsize=maxsize, maxfields=maxfields, stopping_cond=cond)

tests/debugging/exception/test_replay.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,8 @@ def b_chain(bar):
161161
m = 4
162162
try:
163163
a(bar % m)
164-
except ValueError:
165-
raise KeyError("chain it")
164+
except ValueError as exc:
165+
raise KeyError("chain it") from exc
166166

167167
def c(foo=42):
168168
with self.trace("c"):

tests/debugging/test_debugger.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,25 @@ def test_debugger_function_probe_on_function_with_exception():
210210

211211
return_capture = snapshot_data["captures"]["return"]
212212
assert return_capture["arguments"] == {}
213-
assert return_capture["locals"] == {"@exception": {"fields": {}, "type": "Exception"}}
213+
assert return_capture["locals"] == {
214+
"@exception": {
215+
"type": "Exception",
216+
"fields": {
217+
"args": {
218+
"type": "tuple",
219+
"elements": [
220+
{"type": "str", "value": "'Hello'"},
221+
{"type": "str", "value": "'world!'"},
222+
{"type": "int", "value": "42"},
223+
],
224+
"size": 3,
225+
},
226+
"__cause__": {"type": "NoneType", "isNull": True},
227+
"__context__": {"type": "NoneType", "isNull": True},
228+
"__suppress_context__": {"type": "bool", "value": "False"},
229+
},
230+
}
231+
}
214232
assert return_capture["throwable"]["message"] == "'Hello', 'world!', 42"
215233
assert return_capture["throwable"]["type"] == "Exception"
216234

tests/debugging/test_encoding.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,21 @@ def _():
191191

192192
exc = context.pop("throwable")
193193
assert context["arguments"] == {}
194-
assert context["locals"] == {"@exception": {"type": "Exception", "fields": {}}}
194+
assert context["locals"] == {
195+
"@exception": {
196+
"type": "Exception",
197+
"fields": {
198+
"args": {
199+
"type": "tuple",
200+
"elements": [{"type": "str", "value": "'test'"}, {"type": "str", "value": "'me'"}],
201+
"size": 2,
202+
},
203+
"__cause__": {"type": "NoneType", "isNull": True},
204+
"__context__": {"type": "NoneType", "isNull": True},
205+
"__suppress_context__": {"type": "bool", "value": "False"},
206+
},
207+
}
208+
}
195209
assert exc["message"] == "'test', 'me'"
196210
assert exc["type"] == "Exception"
197211

0 commit comments

Comments
 (0)