Skip to content

Commit

Permalink
chore(di): include globals in snapshots (#9184)
Browse files Browse the repository at this point in the history
We include the value of referenced globals in enriched log messages. We
use the `statics` fields for carrying this extra information.

## Checklist

- [x] Change(s) are motivated and described in the PR description
- [x] Testing strategy is described if automated tests are not included
in the PR
- [x] Risks are described (performance impact, potential for breakage,
maintainability)
- [x] Change is maintainable (easy to change, telemetry, documentation)
- [x] [Library release note
guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html)
are followed or label `changelog/no-changelog` is set
- [x] Documentation is included (in-code, generated user docs, [public
corp docs](https://github.com/DataDog/documentation/))
- [x] Backport labels are set (if
[applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting))
- [x] If this PR changes the public interface, I've notified
`@DataDog/apm-tees`.

## Reviewer Checklist

- [x] Title is accurate
- [x] All changes are related to the pull request's stated goal
- [x] Description motivates each change
- [x] Avoids breaking
[API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces)
changes
- [x] Testing strategy adequately addresses listed risks
- [x] Change is maintainable (easy to change, telemetry, documentation)
- [x] Release note makes sense to a user of the library
- [x] Author has acknowledged and discussed the performance implications
of this PR as reported in the benchmarks PR comment
- [x] 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)
  • Loading branch information
P403n1x87 authored May 8, 2024
1 parent 69f91ee commit 0346f71
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 11 deletions.
2 changes: 1 addition & 1 deletion ddtrace/debugging/_safety.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def get_locals(frame: FrameType) -> Iterator[Tuple[str, Any]]:

def get_globals(frame: FrameType) -> Iterator[Tuple[str, Any]]:
nonlocal_names = frame.f_code.co_names
_globals = globals()
_globals = frame.f_globals

return ((name, _globals[name]) for name in nonlocal_names if name in _globals)

Expand Down
22 changes: 18 additions & 4 deletions ddtrace/debugging/_signal/snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
def _capture_context(
arguments: List[Tuple[str, Any]],
_locals: List[Tuple[str, Any]],
_globals: List[Tuple[str, Any]],
throwable: ExcInfoType,
limits: CaptureLimits = DEFAULT_CAPTURE_LIMITS,
) -> Dict[str, Any]:
Expand All @@ -50,18 +51,29 @@ def timeout(_):
"arguments": utils.capture_pairs(
arguments, limits.max_level, limits.max_len, limits.max_size, limits.max_fields, timeout
)
if arguments is not None
if arguments
else {},
"locals": utils.capture_pairs(
_locals, limits.max_level, limits.max_len, limits.max_size, limits.max_fields, timeout
)
if _locals is not None
if _locals
else {},
"staticFields": utils.capture_pairs(
_globals, limits.max_level, limits.max_len, limits.max_size, limits.max_fields, timeout
)
if _globals
else {},
"throwable": utils.capture_exc_info(throwable),
}


_EMPTY_CAPTURED_CONTEXT = _capture_context([], [], (None, None, None), DEFAULT_CAPTURE_LIMITS)
_EMPTY_CAPTURED_CONTEXT = _capture_context(
arguments=[],
_locals=[],
_globals=[],
throwable=(None, None, None),
limits=DEFAULT_CAPTURE_LIMITS,
)


@attr.s
Expand Down Expand Up @@ -121,6 +133,7 @@ def enter(self):
self.entry_capture = _capture_context(
_args,
[],
[],
(None, None, None),
limits=probe.limits,
)
Expand Down Expand Up @@ -154,7 +167,7 @@ def exit(self, retval, exc_info, duration):

if probe.take_snapshot:
self.return_capture = _capture_context(
self.args or _safety.get_args(self.frame), _locals, exc_info, limits=probe.limits
self.args or _safety.get_args(self.frame), _locals, [], exc_info, limits=probe.limits
)
self.duration = duration
self.state = SignalState.DONE
Expand All @@ -179,6 +192,7 @@ def line(self):
self.line_capture = _capture_context(
self.args or _safety.get_args(frame),
_safety.get_locals(frame),
_safety.get_globals(frame),
sys.exc_info(),
limits=probe.limits,
)
Expand Down
1 change: 1 addition & 0 deletions tests/debugging/test_debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -1197,6 +1197,7 @@ def test_debugger_redacted_identifiers():
"size": 3,
},
},
"staticFields": {"SensitiveData": {"type": "type", "value": "<class 'tests.submod.stuff.SensitiveData'>"}},
"throwable": None,
}

Expand Down
13 changes: 7 additions & 6 deletions tests/debugging/test_encoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,13 @@ def c():


def test_capture_context_default_level():
context = _capture_context([("self", tree)], [], (None, None, None), CaptureLimits(max_level=0))
context = _capture_context([("self", tree)], [], [], (None, None, None), CaptureLimits(max_level=0))
self = context["arguments"]["self"]
assert self["fields"]["root"]["notCapturedReason"] == "depth"


def test_capture_context_one_level():
context = _capture_context([("self", tree)], [], (None, None, None), CaptureLimits(max_level=1))
context = _capture_context([("self", tree)], [], [], (None, None, None), CaptureLimits(max_level=1))
self = context["arguments"]["self"]

assert self["fields"]["root"]["fields"]["left"] == {"notCapturedReason": "depth", "type": "Node"}
Expand All @@ -152,13 +152,13 @@ def test_capture_context_one_level():


def test_capture_context_two_level():
context = _capture_context([("self", tree)], [], (None, None, None), CaptureLimits(max_level=2))
context = _capture_context([("self", tree)], [], [], (None, None, None), CaptureLimits(max_level=2))
self = context["arguments"]["self"]
assert self["fields"]["root"]["fields"]["left"]["fields"]["right"] == {"notCapturedReason": "depth", "type": "Node"}


def test_capture_context_three_level():
context = _capture_context([("self", tree)], [], (None, None, None), CaptureLimits(max_level=3))
context = _capture_context([("self", tree)], [], [], (None, None, None), CaptureLimits(max_level=3))
self = context["arguments"]["self"]
assert self["fields"]["root"]["fields"]["left"]["fields"]["right"]["fields"]["right"]["isNull"], context
assert self["fields"]["root"]["fields"]["left"]["fields"]["right"]["fields"]["left"]["isNull"], context
Expand All @@ -169,11 +169,12 @@ def test_capture_context_exc():
try:
raise Exception("test", "me")
except Exception:
context = _capture_context([], [], sys.exc_info())
context = _capture_context([], [], [], sys.exc_info())
exc = context.pop("throwable")
assert context == {
"arguments": {},
"locals": {},
"staticFields": {},
}
assert exc["message"] == "'test', 'me'"
assert exc["type"] == "Exception"
Expand All @@ -190,7 +191,7 @@ def test_batch_json_encoder():
# to test that we can handle unicode strings.
cake = "After the test there will be ✨ 🍰 ✨ in the annex"

buffer_size = 30 * (1 << 10)
buffer_size = 30 * (1 << 20)
queue = SignalQueue(encoder=LogSignalJsonEncoder(None), buffer_size=buffer_size)

s.line()
Expand Down
13 changes: 13 additions & 0 deletions tests/debugging/test_safety.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,19 @@
from ddtrace.debugging import _safety


GLOBAL_VALUE = 42


def test_get_args():
def assert_args(args):
assert set(dict(_safety.get_args(inspect.currentframe().f_back)).keys()) == args

def assert_locals(_locals):
assert set(dict(_safety.get_locals(inspect.currentframe().f_back)).keys()) == _locals

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

def arg_and_kwargs(a, **kwargs):
assert_args({"a", "kwargs"})
assert_locals(set())
Expand All @@ -30,10 +36,17 @@ def args(*ars):
assert_args({"ars"})
assert_locals(set())

def referenced_globals():
global GLOBAL_VALUE
a = GLOBAL_VALUE >> 1 # noqa

assert_globals({"GLOBAL_VALUE"})

arg_and_kwargs(1, b=2)
arg_and_args_and_kwargs(1, 42, b=2)
args_and_kwargs()
args()
referenced_globals()


# ---- Side effects ----
Expand Down

0 comments on commit 0346f71

Please sign in to comment.