Skip to content

Commit fd50ce3

Browse files
jperellitim-schilling
authored andcommitted
Enable showing stacktraces locals
1 parent a6b64ca commit fd50ce3

File tree

7 files changed

+71
-12
lines changed

7 files changed

+71
-12
lines changed

debug_toolbar/settings.py

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
# Panel options
2525
"EXTRA_SIGNALS": [],
2626
"ENABLE_STACKTRACES": True,
27+
"ENABLE_STACKTRACES_LOCALS": False,
2728
"HIDE_IN_STACKTRACES": (
2829
"socketserver",
2930
"threading",

debug_toolbar/static/debug_toolbar/css/toolbar.css

+6-1
Original file line numberDiff line numberDiff line change
@@ -619,13 +619,18 @@
619619
color: #000;
620620
font-weight: bold;
621621
}
622-
#djDebug .djdt-stack span.djdt-path {
622+
#djDebug .djdt-stack span.djdt-path,
623+
#djDebug .djdt-stack pre.djdt-locals,
624+
#djDebug .djdt-stack pre.djdt-locals span {
623625
color: #777;
624626
font-weight: normal;
625627
}
626628
#djDebug .djdt-stack span.djdt-code {
627629
font-weight: normal;
628630
}
631+
#djDebug .djdt-stack pre.djdt-locals {
632+
margin: 0 27px 27px 27px;
633+
}
629634

630635
#djDebug .djdt-width-20 {
631636
width: 20%;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{% for s in stacktrace %}<span class="djdt-path">{{s.0}}/</span><span class="djdt-file">{{s.1}}</span> in <span class="djdt-func">{{s.3}}</span>(<span class="djdt-lineno">{{s.2}}</span>)
2+
<span class="djdt-code">{{s.4}}</span>
3+
{% if show_locals %}<pre class="djdt-locals">{{s.5|pprint}}</pre>{% endif %}{% endfor %}

debug_toolbar/utils.py

+19-11
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import django
99
from django.core.exceptions import ImproperlyConfigured
1010
from django.template import Node
11-
from django.utils.html import escape
11+
from django.template.loader import render_to_string
1212
from django.utils.safestring import mark_safe
1313

1414
from debug_toolbar import settings as dt_settings
@@ -59,28 +59,36 @@ def tidy_stacktrace(stack):
5959
if omit_path(os.path.realpath(path)):
6060
continue
6161
text = "".join(text).strip() if text else ""
62-
trace.append((path, line_no, func_name, text))
62+
frame_locals = (
63+
frame.f_locals
64+
if dt_settings.get_config()["ENABLE_STACKTRACES_LOCALS"]
65+
else None
66+
)
67+
trace.append((path, line_no, func_name, text, frame_locals))
6368
return trace
6469

6570

6671
def render_stacktrace(trace):
6772
stacktrace = []
6873
for frame in trace:
69-
params = (escape(v) for v in chain(frame[0].rsplit(os.path.sep, 1), frame[1:]))
74+
params = (v for v in chain(frame[0].rsplit(os.path.sep, 1), frame[1:]))
7075
params_dict = {str(idx): v for idx, v in enumerate(params)}
7176
try:
72-
stacktrace.append(
73-
'<span class="djdt-path">%(0)s/</span>'
74-
'<span class="djdt-file">%(1)s</span>'
75-
' in <span class="djdt-func">%(3)s</span>'
76-
'(<span class="djdt-lineno">%(2)s</span>)\n'
77-
' <span class="djdt-code">%(4)s</span>' % params_dict
78-
)
77+
stacktrace.append(params_dict)
7978
except KeyError:
8079
# This frame doesn't have the expected format, so skip it and move
8180
# on to the next one
8281
continue
83-
return mark_safe("\n".join(stacktrace))
82+
83+
return mark_safe(
84+
render_to_string(
85+
"debug_toolbar/panels/sql_stacktrace.html",
86+
{
87+
"stacktrace": stacktrace,
88+
"show_locals": dt_settings.get_config()["ENABLE_STACKTRACES_LOCALS"],
89+
},
90+
)
91+
)
8492

8593

8694
def get_template_info():

docs/changes.rst

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ UNRELEASED
66

77
* Removed support for end of life Django 2.0 and 2.1.
88
* Added support for Python 3.8.
9+
* Add locals() option for sql panel.
910

1011
2.1 (2019-11-12)
1112
----------------

docs/configuration.rst

+17
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,23 @@ Panel options
139139
calls. Enabling stacktraces can increase the CPU time used when executing
140140
queries.
141141

142+
* ``ENABLE_STACKTRACES_LOCALS``
143+
144+
Default: ``False``
145+
146+
Panels: cache, SQL
147+
148+
If set to ``True``, this will show locals() for each stacktrace piece of
149+
code for SQL queries and cache calls.
150+
Enabling stacktraces locals will increase the CPU time used when executing
151+
queries and will give too verbose information in most cases, but is useful
152+
for debugging complex cases.
153+
154+
.. caution::
155+
This will expose all members from each frame of the stacktrace. This can
156+
potentially expose sensitive or private information. It's advised to only
157+
use this configuration locally.
158+
142159
* ``HIDE_IN_STACKTRACES``
143160

144161
Default::

tests/panels/test_sql.py

+24
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,30 @@ def test_insert_content(self):
215215
self.assertIn("café", self.panel.content)
216216
self.assertValidHTML(self.panel.content)
217217

218+
@override_settings(DEBUG_TOOLBAR_CONFIG={"ENABLE_STACKTRACES_LOCALS": True})
219+
def test_insert_locals(self):
220+
"""
221+
Test that the panel inserts locals() content.
222+
"""
223+
local_var = "<script>alert('test');</script>" # noqa
224+
list(User.objects.filter(username="café".encode("utf-8")))
225+
response = self.panel.process_request(self.request)
226+
self.panel.generate_stats(self.request, response)
227+
self.assertIn("local_var", self.panel.content)
228+
# Verify the escape logic works
229+
self.assertNotIn("<script>alert", self.panel.content)
230+
self.assertIn("&lt;script&gt;alert", self.panel.content)
231+
self.assertIn("djdt-locals", self.panel.content)
232+
233+
def test_not_insert_locals(self):
234+
"""
235+
Test that the panel does not insert locals() content.
236+
"""
237+
list(User.objects.filter(username="café".encode("utf-8")))
238+
response = self.panel.process_request(self.request)
239+
self.panel.generate_stats(self.request, response)
240+
self.assertNotIn("djdt-locals", self.panel.content)
241+
218242
@unittest.skipUnless(
219243
connection.vendor == "postgresql", "Test valid only on PostgreSQL"
220244
)

0 commit comments

Comments
 (0)