Skip to content

Commit d7927e6

Browse files
authored
fix(er): include nonlocals in snapshots (DataDog#11894)
We include nonlocal variables in snapshots to provide for better visibility into exception occurrences. ## 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 8702cab commit d7927e6

File tree

4 files changed

+33
-5
lines changed

4 files changed

+33
-5
lines changed

Diff for: ddtrace/debugging/_safety.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from inspect import CO_VARARGS
22
from inspect import CO_VARKEYWORDS
3+
from itertools import chain
34
from types import FrameType
45
from typing import Any
56
from typing import Dict
@@ -23,11 +24,11 @@ def get_args(frame: FrameType) -> Iterator[Tuple[str, Any]]:
2324

2425
def get_locals(frame: FrameType) -> Iterator[Tuple[str, Any]]:
2526
code = frame.f_code
27+
_locals = frame.f_locals
2628
nargs = code.co_argcount + bool(code.co_flags & CO_VARARGS) + bool(code.co_flags & CO_VARKEYWORDS)
27-
names = code.co_varnames[nargs:]
28-
values = (frame.f_locals.get(name) for name in names)
29-
30-
return zip(names, values)
29+
return (
30+
(name, _locals.get(name)) for name in chain(code.co_varnames[nargs:], code.co_freevars, code.co_cellvars)
31+
) # include freevars and cellvars
3132

3233

3334
def get_globals(frame: FrameType) -> Iterator[Tuple[str, Any]]:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
fixes:
3+
- |
4+
exception replay: include missing nonlocal variables in snapshot log messages.

Diff for: tests/debugging/exception/test_replay.py

+20
Original file line numberDiff line numberDiff line change
@@ -294,3 +294,23 @@ def c(foo=42):
294294
self.assert_span_count(6)
295295
# no new snapshots
296296
assert len(uploader.collector.queue) == 3
297+
298+
def test_debugger_exception_in_closure(self):
299+
def b():
300+
with self.trace("b"):
301+
nonloc = 4
302+
303+
def a(v):
304+
if nonloc:
305+
raise ValueError("hello", v)
306+
307+
a(nonloc)
308+
309+
with exception_replay() as uploader:
310+
with with_rate_limiter(RateLimiter(limit_rate=1, raise_on_exceed=False)):
311+
with pytest.raises(ValueError):
312+
b()
313+
314+
assert all(
315+
s.line_capture["locals"]["nonloc"] == {"type": "int", "value": "4"} for s in uploader.collector.queue
316+
)

Diff for: tests/debugging/test_safety.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ def assert_args(args):
1515
assert set(dict(_safety.get_args(inspect.currentframe().f_back)).keys()) == args
1616

1717
def assert_locals(_locals):
18-
assert set(dict(_safety.get_locals(inspect.currentframe().f_back)).keys()) == _locals
18+
assert set(dict(_safety.get_locals(inspect.currentframe().f_back)).keys()) == _locals | {
19+
"assert_args",
20+
"assert_locals",
21+
}
1922

2023
def assert_globals(_globals):
2124
assert set(dict(_safety.get_globals(inspect.currentframe().f_back)).keys()) == _globals

0 commit comments

Comments
 (0)