diff --git a/debug_toolbar/panels/cache.py b/debug_toolbar/panels/cache.py index 4ca36a387..f5ceea513 100644 --- a/debug_toolbar/panels/cache.py +++ b/debug_toolbar/panels/cache.py @@ -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, ) diff --git a/debug_toolbar/panels/sql/tracking.py b/debug_toolbar/panels/sql/tracking.py index 8a15977de..b166e592d 100644 --- a/debug_toolbar/panels/sql/tracking.py +++ b/debug_toolbar/panels/sql/tracking.py @@ -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"], diff --git a/debug_toolbar/utils.py b/debug_toolbar/utils.py index 32aa27420..bd74e6eed 100644 --- a/debug_toolbar/utils.py +++ b/debug_toolbar/utils.py @@ -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 @@ -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 @@ -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(): diff --git a/tests/test_utils.py b/tests/test_utils.py index d884b050a..31a67a6c1 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -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, ) @@ -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()