11import inspect
2+ import linecache
23import os .path
34import sys
5+ import warnings
46from importlib import import_module
57from pprint import pformat
68
79import django
10+ from asgiref .local import Local
811from django .core .exceptions import ImproperlyConfigured
912from django .template import Node
1013from django .utils .html import format_html
1821 threading = None
1922
2023
24+ _local_data = Local ()
25+
26+
2127# Figure out some paths
2228django_path = os .path .realpath (os .path .dirname (django .__file__ ))
2329
@@ -44,6 +50,15 @@ def omit_path(path):
4450 return any (path .startswith (hidden_path ) for hidden_path in hidden_paths )
4551
4652
53+ def _stack_trace_deprecation_warning ():
54+ warnings .warn (
55+ "get_stack() and tidy_stacktrace() are deprecated in favor of"
56+ " get_stack_trace()" ,
57+ DeprecationWarning ,
58+ stacklevel = 2 ,
59+ )
60+
61+
4762def tidy_stacktrace (stack ):
4863 """
4964 Clean up stacktrace and remove all entries that are excluded by the
@@ -52,6 +67,8 @@ def tidy_stacktrace(stack):
5267 ``stack`` should be a list of frame tuples from ``inspect.stack()`` or
5368 ``debug_toolbar.utils.get_stack()``.
5469 """
70+ _stack_trace_deprecation_warning ()
71+
5572 trace = []
5673 for frame , path , line_no , func_name , text in (f [:5 ] for f in stack ):
5774 if omit_path (os .path .realpath (path )):
@@ -234,6 +251,8 @@ def get_stack(context=1):
234251
235252 Modified version of ``inspect.stack()`` which calls our own ``getframeinfo()``
236253 """
254+ _stack_trace_deprecation_warning ()
255+
237256 frame = sys ._getframe (1 )
238257 framelist = []
239258 while frame :
@@ -242,6 +261,99 @@ def get_stack(context=1):
242261 return framelist
243262
244263
264+ def _stack_frames (depth = 1 ):
265+ frame = inspect .currentframe ()
266+ while frame is not None :
267+ if depth > 0 :
268+ depth -= 1
269+ else :
270+ yield frame
271+ frame = frame .f_back
272+
273+
274+ class _StackTraceRecorder :
275+ def __init__ (self , excluded_paths ):
276+ self .excluded_paths = excluded_paths
277+ self .filename_cache = {}
278+ self .is_excluded_cache = {}
279+
280+ def get_source_file (self , frame ):
281+ frame_filename = frame .f_code .co_filename
282+
283+ value = self .filename_cache .get (frame_filename )
284+ if value is None :
285+ filename = inspect .getsourcefile (frame )
286+ if filename is None :
287+ is_source = False
288+ filename = frame_filename
289+ else :
290+ is_source = True
291+ # Ensure linecache validity the first time this recorder
292+ # encounters the filename in this frame.
293+ linecache .checkcache (filename )
294+ value = (filename , is_source )
295+ self .filename_cache [frame_filename ] = value
296+
297+ return value
298+
299+ def is_excluded_path (self , path ):
300+ excluded = self .is_excluded_cache .get (path )
301+ if excluded is None :
302+ resolved_path = os .path .realpath (path )
303+ excluded = any (
304+ resolved_path .startswith (excluded_path )
305+ for excluded_path in self .excluded_paths
306+ )
307+ self .is_excluded_cache [path ] = excluded
308+ return excluded
309+
310+ def get_stack_trace (self , include_locals = False , depth = 1 ):
311+ trace = []
312+ for frame in _stack_frames (depth = depth + 1 ):
313+ filename , is_source = self .get_source_file (frame )
314+
315+ if self .is_excluded_path (filename ):
316+ continue
317+
318+ line_no = frame .f_lineno
319+ func_name = frame .f_code .co_name
320+
321+ if is_source :
322+ module = inspect .getmodule (frame , filename )
323+ module_globals = module .__dict__ if module is not None else None
324+ source_line = linecache .getline (
325+ filename , line_no , module_globals
326+ ).strip ()
327+ else :
328+ source_line = ""
329+
330+ frame_locals = frame .f_locals if include_locals else None
331+
332+ trace .append ((filename , line_no , func_name , source_line , frame_locals ))
333+ trace .reverse ()
334+ return trace
335+
336+
337+ def get_stack_trace (depth = 1 ):
338+ config = dt_settings .get_config ()
339+ if config ["ENABLE_STACKTRACES" ]:
340+ stack_trace_recorder = getattr (_local_data , "stack_trace_recorder" , None )
341+ if stack_trace_recorder is None :
342+ stack_trace_recorder = _StackTraceRecorder (hidden_paths )
343+ _local_data .stack_trace_recorder = stack_trace_recorder
344+ return stack_trace_recorder .get_stack_trace (
345+ include_locals = config ["ENABLE_STACKTRACES_LOCALS" ],
346+ depth = depth ,
347+ )
348+ else :
349+ return []
350+
351+
352+ def clear_stack_trace_caches ():
353+ if hasattr (_local_data , "stack_trace_recorder" ):
354+ del _local_data .stack_trace_recorder
355+
356+
245357class ThreadCollector :
246358 def __init__ (self ):
247359 if threading is None :
0 commit comments