Skip to content

Tweak get_stack_trace() API #1637

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion debug_toolbar/panels/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def _record_call(self, cache, name, original_method, args, kwargs):
return_value=value,
args=args,
kwargs=kwargs,
trace=get_stack_trace(),
trace=get_stack_trace(skip=2),
template_info=get_template_info(),
backend=cache,
)
Expand Down
2 changes: 1 addition & 1 deletion debug_toolbar/panels/sql/tracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def _record(self, method, sql, params):
"raw_sql": sql,
"params": _params,
"raw_params": params,
"stacktrace": get_stack_trace(),
"stacktrace": get_stack_trace(skip=2),
"start_time": start_time,
"stop_time": stop_time,
"is_slow": duration > dt_settings.get_config()["SQL_WARNING_THRESHOLD"],
Expand Down
50 changes: 33 additions & 17 deletions debug_toolbar/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,11 +246,12 @@ def get_stack(context=1):
return framelist


def _stack_frames(depth=1):
def _stack_frames(*, skip=0):
skip += 1 # Skip the frame for this generator.
frame = inspect.currentframe()
while frame is not None:
if depth > 0:
depth -= 1
if skip > 0:
skip -= 1
else:
yield frame
frame = frame.f_back
Expand Down Expand Up @@ -279,9 +280,10 @@ def get_source_file(self, frame):

return value

def get_stack_trace(self, *, excluded_modules=None, include_locals=False, depth=1):
def get_stack_trace(self, *, excluded_modules=None, include_locals=False, skip=0):
trace = []
for frame in _stack_frames(depth=depth + 1):
skip += 1 # Skip the frame for this method.
for frame in _stack_frames(skip=skip):
if _is_excluded_frame(frame, excluded_modules):
continue

Expand All @@ -306,20 +308,34 @@ def get_stack_trace(self, *, excluded_modules=None, include_locals=False, depth=
return trace


def get_stack_trace(*, depth=1):
def get_stack_trace(*, skip=0):
"""
Return a processed stack trace for the current call stack.

If the ``ENABLE_STACKTRACES`` setting is False, return an empty :class:`list`.
Otherwise return a :class:`list` of processed stack frame tuples (file name, line
number, function name, source line, frame locals) for the current call stack. The
first entry in the list will be for the bottom of the stack and the last entry will
be for the top of the stack.

``skip`` is an :class:`int` indicating the number of stack frames above the frame
for this function to omit from the stack trace. The default value of ``0`` means
that the entry for the caller of this function will be the last entry in the
returned stack trace.
"""
config = dt_settings.get_config()
if config["ENABLE_STACKTRACES"]:
stack_trace_recorder = getattr(_local_data, "stack_trace_recorder", None)
if stack_trace_recorder is None:
stack_trace_recorder = _StackTraceRecorder()
_local_data.stack_trace_recorder = stack_trace_recorder
return stack_trace_recorder.get_stack_trace(
excluded_modules=config["HIDE_IN_STACKTRACES"],
include_locals=config["ENABLE_STACKTRACES_LOCALS"],
depth=depth,
)
else:
if not config["ENABLE_STACKTRACES"]:
return []
skip += 1 # Skip the frame for this function.
stack_trace_recorder = getattr(_local_data, "stack_trace_recorder", None)
if stack_trace_recorder is None:
stack_trace_recorder = _StackTraceRecorder()
_local_data.stack_trace_recorder = stack_trace_recorder
return stack_trace_recorder.get_stack_trace(
excluded_modules=config["HIDE_IN_STACKTRACES"],
include_locals=config["ENABLE_STACKTRACES_LOCALS"],
skip=skip,
)


def clear_stack_trace_caches():
Expand Down
18 changes: 18 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import unittest

from django.test import override_settings

import debug_toolbar.utils
from debug_toolbar.utils import (
get_name_from_obj,
get_stack,
get_stack_trace,
render_stacktrace,
tidy_stacktrace,
)
Expand Down Expand Up @@ -55,6 +59,20 @@ def test_importlib_path_issue_1612(self):


class StackTraceTestCase(unittest.TestCase):
@override_settings(DEBUG_TOOLBAR_CONFIG={"HIDE_IN_STACKTRACES": []})
def test_get_stack_trace_skip(self):
stack_trace = get_stack_trace(skip=-1)
self.assertTrue(len(stack_trace) > 2)
self.assertEqual(stack_trace[-1][0], debug_toolbar.utils.__file__)
self.assertEqual(stack_trace[-1][2], "get_stack_trace")
self.assertEqual(stack_trace[-2][0], __file__)
self.assertEqual(stack_trace[-2][2], "test_get_stack_trace_skip")

stack_trace = get_stack_trace()
self.assertTrue(len(stack_trace) > 1)
self.assertEqual(stack_trace[-1][0], __file__)
self.assertEqual(stack_trace[-1][2], "test_get_stack_trace_skip")

def test_deprecated_functions(self):
with self.assertWarns(DeprecationWarning):
stack = get_stack()
Expand Down