|
1 | 1 | import inspect
|
| 2 | +import linecache |
2 | 3 | import os.path
|
3 | 4 | import sys
|
4 | 5 | from importlib import import_module
|
5 | 6 | from pprint import pformat
|
6 | 7 |
|
7 | 8 | import django
|
| 9 | +from asgiref.local import Local |
8 | 10 | from django.core.exceptions import ImproperlyConfigured
|
9 | 11 | from django.template import Node
|
10 | 12 | from django.utils.html import format_html
|
|
18 | 20 | threading = None
|
19 | 21 |
|
20 | 22 |
|
| 23 | +_local_data = Local() |
| 24 | + |
| 25 | + |
21 | 26 | # Figure out some paths
|
22 | 27 | django_path = os.path.realpath(os.path.dirname(django.__file__))
|
23 | 28 |
|
@@ -242,6 +247,99 @@ def get_stack(context=1):
|
242 | 247 | return framelist
|
243 | 248 |
|
244 | 249 |
|
| 250 | +def _stack_frames(depth=1): |
| 251 | + frame = inspect.currentframe() |
| 252 | + while frame is not None: |
| 253 | + if depth > 0: |
| 254 | + depth -= 1 |
| 255 | + else: |
| 256 | + yield frame |
| 257 | + frame = frame.f_back |
| 258 | + |
| 259 | + |
| 260 | +class _StackTraceRecorder: |
| 261 | + def __init__(self, excluded_paths): |
| 262 | + self.excluded_paths = excluded_paths |
| 263 | + self.filename_cache = {} |
| 264 | + self.is_excluded_cache = {} |
| 265 | + |
| 266 | + def get_source_file(self, frame): |
| 267 | + frame_filename = frame.f_code.co_filename |
| 268 | + |
| 269 | + value = self.filename_cache.get(frame_filename) |
| 270 | + if value is None: |
| 271 | + filename = inspect.getsourcefile(frame) |
| 272 | + if filename is None: |
| 273 | + is_source = False |
| 274 | + filename = frame_filename |
| 275 | + else: |
| 276 | + is_source = True |
| 277 | + # Ensure linecache validity the first time this recorder |
| 278 | + # encounters the filename in this frame. |
| 279 | + linecache.checkcache(filename) |
| 280 | + value = (filename, is_source) |
| 281 | + self.filename_cache[frame_filename] = value |
| 282 | + |
| 283 | + return value |
| 284 | + |
| 285 | + def is_excluded_path(self, path): |
| 286 | + excluded = self.is_excluded_cache.get(path) |
| 287 | + if excluded is None: |
| 288 | + resolved_path = os.path.realpath(path) |
| 289 | + excluded = any( |
| 290 | + resolved_path.startswith(excluded_path) |
| 291 | + for excluded_path in self.excluded_paths |
| 292 | + ) |
| 293 | + self.is_excluded_cache[path] = excluded |
| 294 | + return excluded |
| 295 | + |
| 296 | + def get_stack_trace(self, include_locals=False, depth=1): |
| 297 | + trace = [] |
| 298 | + for frame in _stack_frames(depth=depth + 1): |
| 299 | + filename, is_source = self.get_source_file(frame) |
| 300 | + |
| 301 | + if self.is_excluded_path(filename): |
| 302 | + continue |
| 303 | + |
| 304 | + line_no = frame.f_lineno |
| 305 | + func_name = frame.f_code.co_name |
| 306 | + |
| 307 | + if is_source: |
| 308 | + module = inspect.getmodule(frame, filename) |
| 309 | + module_globals = module.__dict__ if module is not None else None |
| 310 | + source_line = linecache.getline( |
| 311 | + filename, line_no, module_globals |
| 312 | + ).strip() |
| 313 | + else: |
| 314 | + source_line = "" |
| 315 | + |
| 316 | + frame_locals = frame.f_locals if include_locals else None |
| 317 | + |
| 318 | + trace.append((filename, line_no, func_name, source_line, frame_locals)) |
| 319 | + trace.reverse() |
| 320 | + return trace |
| 321 | + |
| 322 | + |
| 323 | +def get_stack_trace(depth=1): |
| 324 | + config = dt_settings.get_config() |
| 325 | + if config["ENABLE_STACKTRACES"]: |
| 326 | + stack_trace_recorder = getattr(_local_data, "stack_trace_recorder", None) |
| 327 | + if stack_trace_recorder is None: |
| 328 | + stack_trace_recorder = _StackTraceRecorder(hidden_paths) |
| 329 | + _local_data.stack_trace_recorder = stack_trace_recorder |
| 330 | + return stack_trace_recorder.get_stack_trace( |
| 331 | + include_locals=config["ENABLE_STACKTRACES_LOCALS"], |
| 332 | + depth=depth, |
| 333 | + ) |
| 334 | + else: |
| 335 | + return [] |
| 336 | + |
| 337 | + |
| 338 | +def clear_stack_trace_caches(): |
| 339 | + if hasattr(_local_data, "stack_trace_recorder"): |
| 340 | + del _local_data.stack_trace_recorder |
| 341 | + |
| 342 | + |
245 | 343 | class ThreadCollector:
|
246 | 344 | def __init__(self):
|
247 | 345 | if threading is None:
|
|
0 commit comments