Skip to content

Commit

Permalink
Enable showing stacktraces locals
Browse files Browse the repository at this point in the history
  • Loading branch information
jperelli authored and tim-schilling committed Dec 20, 2019
1 parent a6b64ca commit fd50ce3
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 12 deletions.
1 change: 1 addition & 0 deletions debug_toolbar/settings.py
Expand Up @@ -24,6 +24,7 @@
# Panel options
"EXTRA_SIGNALS": [],
"ENABLE_STACKTRACES": True,
"ENABLE_STACKTRACES_LOCALS": False,
"HIDE_IN_STACKTRACES": (
"socketserver",
"threading",
Expand Down
7 changes: 6 additions & 1 deletion debug_toolbar/static/debug_toolbar/css/toolbar.css
Expand Up @@ -619,13 +619,18 @@
color: #000;
font-weight: bold;
}
#djDebug .djdt-stack span.djdt-path {
#djDebug .djdt-stack span.djdt-path,
#djDebug .djdt-stack pre.djdt-locals,
#djDebug .djdt-stack pre.djdt-locals span {
color: #777;
font-weight: normal;
}
#djDebug .djdt-stack span.djdt-code {
font-weight: normal;
}
#djDebug .djdt-stack pre.djdt-locals {
margin: 0 27px 27px 27px;
}

#djDebug .djdt-width-20 {
width: 20%;
Expand Down
@@ -0,0 +1,3 @@
{% 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>)
<span class="djdt-code">{{s.4}}</span>
{% if show_locals %}<pre class="djdt-locals">{{s.5|pprint}}</pre>{% endif %}{% endfor %}
30 changes: 19 additions & 11 deletions debug_toolbar/utils.py
Expand Up @@ -8,7 +8,7 @@
import django
from django.core.exceptions import ImproperlyConfigured
from django.template import Node
from django.utils.html import escape
from django.template.loader import render_to_string
from django.utils.safestring import mark_safe

from debug_toolbar import settings as dt_settings
Expand Down Expand Up @@ -59,28 +59,36 @@ def tidy_stacktrace(stack):
if omit_path(os.path.realpath(path)):
continue
text = "".join(text).strip() if text else ""
trace.append((path, line_no, func_name, text))
frame_locals = (
frame.f_locals
if dt_settings.get_config()["ENABLE_STACKTRACES_LOCALS"]
else None
)
trace.append((path, line_no, func_name, text, frame_locals))
return trace


def render_stacktrace(trace):
stacktrace = []
for frame in trace:
params = (escape(v) for v in chain(frame[0].rsplit(os.path.sep, 1), frame[1:]))
params = (v for v in chain(frame[0].rsplit(os.path.sep, 1), frame[1:]))
params_dict = {str(idx): v for idx, v in enumerate(params)}
try:
stacktrace.append(
'<span class="djdt-path">%(0)s/</span>'
'<span class="djdt-file">%(1)s</span>'
' in <span class="djdt-func">%(3)s</span>'
'(<span class="djdt-lineno">%(2)s</span>)\n'
' <span class="djdt-code">%(4)s</span>' % params_dict
)
stacktrace.append(params_dict)
except KeyError:
# This frame doesn't have the expected format, so skip it and move
# on to the next one
continue
return mark_safe("\n".join(stacktrace))

return mark_safe(
render_to_string(
"debug_toolbar/panels/sql_stacktrace.html",
{
"stacktrace": stacktrace,
"show_locals": dt_settings.get_config()["ENABLE_STACKTRACES_LOCALS"],
},
)
)


def get_template_info():
Expand Down
1 change: 1 addition & 0 deletions docs/changes.rst
Expand Up @@ -6,6 +6,7 @@ UNRELEASED

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

2.1 (2019-11-12)
----------------
Expand Down
17 changes: 17 additions & 0 deletions docs/configuration.rst
Expand Up @@ -139,6 +139,23 @@ Panel options
calls. Enabling stacktraces can increase the CPU time used when executing
queries.

* ``ENABLE_STACKTRACES_LOCALS``

Default: ``False``

Panels: cache, SQL

If set to ``True``, this will show locals() for each stacktrace piece of
code for SQL queries and cache calls.
Enabling stacktraces locals will increase the CPU time used when executing
queries and will give too verbose information in most cases, but is useful
for debugging complex cases.

.. caution::
This will expose all members from each frame of the stacktrace. This can
potentially expose sensitive or private information. It's advised to only
use this configuration locally.

* ``HIDE_IN_STACKTRACES``

Default::
Expand Down
24 changes: 24 additions & 0 deletions tests/panels/test_sql.py
Expand Up @@ -215,6 +215,30 @@ def test_insert_content(self):
self.assertIn("café", self.panel.content)
self.assertValidHTML(self.panel.content)

@override_settings(DEBUG_TOOLBAR_CONFIG={"ENABLE_STACKTRACES_LOCALS": True})
def test_insert_locals(self):
"""
Test that the panel inserts locals() content.
"""
local_var = "<script>alert('test');</script>" # noqa
list(User.objects.filter(username="café".encode("utf-8")))
response = self.panel.process_request(self.request)
self.panel.generate_stats(self.request, response)
self.assertIn("local_var", self.panel.content)
# Verify the escape logic works
self.assertNotIn("<script>alert", self.panel.content)
self.assertIn("&lt;script&gt;alert", self.panel.content)
self.assertIn("djdt-locals", self.panel.content)

def test_not_insert_locals(self):
"""
Test that the panel does not insert locals() content.
"""
list(User.objects.filter(username="café".encode("utf-8")))
response = self.panel.process_request(self.request)
self.panel.generate_stats(self.request, response)
self.assertNotIn("djdt-locals", self.panel.content)

@unittest.skipUnless(
connection.vendor == "postgresql", "Test valid only on PostgreSQL"
)
Expand Down

1 comment on commit fd50ce3

@jperelli
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR for reference on this commit #1203

Please sign in to comment.