Login

DebugFooter middleware with syntax highlighting and code inspection

Author:
monolar
Posted:
June 20, 2008
Language:
Python
Version:
.96
Score:
9 (after 9 ratings)

based on Snippet 799 but added code inspection capabilities. Credit goes to django-logging for the actual inspection code.

Got the idea from the This Week in Django Podcast ;)

This adds the filename, lineno, functionname and the actual python-code line which caused a sql statement to be executed.

Note that i am adding keys to 'connection.queries' dict on request, which may be dangerous, so use with care!

The code inspection functionality can be toggled via FRAME_INSPECT.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import time
from django.dispatch import dispatcher
from django.core.signals import request_started
from django.test.signals import template_rendered
from django.conf import settings
from django.db import connection
from django.utils.encoding import force_unicode
from django.utils.safestring import mark_safe

# Frame inspection code credit goes to django-logging (http://code.google.com/p/django-logging/)
FRAME_INSPECT = True
if FRAME_INSPECT:
    import inspect
    import django
    from django.contrib import admin
    _django_path = django.__file__.split('__init__')[0]
    _admin_path = admin.__file__.split('__init__')[0]

USE_PYGMENTS = False
try:
    import textwrap
    from pygments import highlight
    from pygments.formatters import HtmlFormatter
    from pygments.lexers import SqlLexer
    from pygments.lexers import PythonLexer
    from pygments.styles import get_style_by_name    
    USE_PYGMENTS = True
except ImportError:
    pass

if USE_PYGMENTS:
    WRAP = 120
    SQL_STYLE = get_style_by_name('colorful')
    PYTHON_STYLE = get_style_by_name('friendly')

HEIGHT = '240px' # or '100%' if full height is wished

TEMPLATE = """
<div id="debug" style="clear:both;">
<a href="#debugbox"
    onclick="this.style.display = 'none';   
        document.getElementById('debugbox').style.display = 'block';
        return false;"
    style="font-size: small; color: red; text-decoration: none; display: block; margin: 12px;"
>+</a>

<div style="display: none;clear: both; border: 1px solid red; padding: 12px; {{ height }} margin: 12px; overflow: auto;"
     id="debugbox">

<p>Server-time taken: {{ server_time|floatformat:"5" }} seconds</p>
<p>Templates used:</p>
{% if templates %}
<ol>
    {% for template in templates %}
        <li><strong>{{ template.0 }}</strong> loaded from <samp>{{ template.1 }}</samp></li>
    {% endfor %}
</ol>
{% else %}
    None
{% endif %}
<p>Template path:</p> 
{% if template_dirs %}
    <ol>
    {% for template in template_dirs %}
        <li><samp>{{ template }}</samp></li>
    {% endfor %}
    </ol>
{% else %}
    None
{% endif %}
<p>SQL executed:</p>
{% if sql %}
<ol>
{% for query in sql %}
    <li><pre>{{ query.sql|linebreaksbr }}</pre>
        <p>took {{ query.time|floatformat:"3" }} seconds<br/>
           {% if inspect %}module: <code>{{ query.module }}</code><br/>
                           file: <samp>{{ query.filename }}</samp><br/>
                           line: <code>{{ query.lineno }}</code><br/>
                           function: <code>{{ query.function }}</code>
                           
                           <pre>{{ query.source|linebreaksbr }}</pre>{% endif %}</p>
    </li>
{% endfor %}
</ol>
<p>Total SQL time: {{ sql_total }}</p>
{% else %}
    None
{% endif %}
</div>
</div>
</body>
"""

# Monkeypatch instrumented test renderer from django.test.utils - we could use
# django.test.utils.setup_test_environment for this but that would also set up
# e-mail interception, which we don't want
from django.test.utils import instrumented_test_render
from django.template import Template, Context
if Template.render != instrumented_test_render:
    Template.original_render = Template.render
    Template.render = instrumented_test_render
# MONSTER monkey-patch
old_template_init = Template.__init__
def new_template_init(self, template_string, origin=None, name='<Unknown Template>'):
    old_template_init(self, template_string, origin, name)
    self.origin = origin
Template.__init__ = new_template_init

if FRAME_INSPECT:
    class SqlLoggingList(list):
            def append(self, object):
                # Try to find the meaningful frame, rather than just using one from
                # the innards of the Django DB code.
                frame = inspect.currentframe().f_back
                while frame.f_back and frame.f_code.co_filename.startswith(_django_path):
                    if frame.f_code.co_filename.startswith(_admin_path):
                        break
                    frame = frame.f_back
                object['filename'] = frame.f_code.co_filename
                object['lineno'] = frame.f_lineno
                object['function'] = frame.f_code.co_name
                try:
                    object['module'] = inspect.getmodule(frame).__name__
                except:
                    object['module'] = "unknown"
                try:
                    source_lines = inspect.getsourcelines(frame)
                    object['source'] = highlight_python(source_lines[0][object['lineno']-source_lines[1]])
                except:
                    object['source'] = "N/A"
                list.append(self, object)
            
class DebugFooter(object):
    
    def process_request(self, request):
        self.time_started = time.time()
        self.templates_used = []
        self.contexts_used = []
        self.sql_offset_start = len(connection.queries)
        if FRAME_INSPECT:
            connection.queries = SqlLoggingList(connection.queries)
        dispatcher.connect(
            self._storeRenderedTemplates, signal=template_rendered
        )
        
    def process_response(self, request, response):
        # Only include debug info for text/html pages not accessed via Ajax
        if 'text/html' not in response['Content-Type']:
            return response
        if request.is_ajax():
            return response
        if not settings.DEBUG:
            return response
        if response.status_code != 200:
            return response
        
        templates = [
            (t.name, t.origin and t.origin.name or 'No origin')
            for t in self.templates_used
        ]
        sql_queries = connection.queries[self.sql_offset_start:]
        
        # Reformat sql queries a bit
        sql_total = 0.0
        
        for query in sql_queries:
            query['sql'] = highlight_sql(query['sql'])
            sql_total += float(query['time'])
        
        #import pdb; pdb.set_trace()
        
        
        debug_content = Template(TEMPLATE).render(Context({
            'server_time': time.time() - self.time_started,
            'templates': templates,
            'sql': sql_queries,
            'sql_total': sql_total,
            'template_dirs': settings.TEMPLATE_DIRS,
            'height': 'height: %s;' % (HEIGHT),
            'inspect': FRAME_INSPECT,
        }))
        
        content = response.content
        response.content = force_unicode(content).replace('</body>', debug_content)
        
        #import pdb; pdb.set_trace()
        return response
    
    def _storeRenderedTemplates(self, signal, sender, template, context):
        self.templates_used.append(template)
        self.contexts_used.append(context)

def highlight_sql(sql):
    if USE_PYGMENTS:
        sql = mark_safe(highlight(textwrap.fill(sql, WRAP),
                                  SqlLexer(encoding='utf-8'),
                                  HtmlFormatter(encoding='utf-8',
                                                noclasses=True,
                                                style=SQL_STYLE)
                                  )
                        )
    else:
        sql = reformat_sql(sql)
    return sql
    
def reformat_sql(sql):
    sql = sql.replace('`,`', '`, `')
    sql = sql.replace('` FROM `', '` \n  FROM `')
    sql = sql.replace('` WHERE ', '` \n  WHERE ')
    sql = sql.replace(' ORDER BY ', ' \n  ORDER BY ')
    return sql

def highlight_python(source):
    if USE_PYGMENTS:
        source = mark_safe(highlight(textwrap.fill(source, WRAP),
                                     PythonLexer(encoding='utf-8'),
                                     HtmlFormatter(encoding='utf-8',
                                                   noclasses=True,
                                                   style=PYTHON_STYLE)
                                     )
                           )
    return source

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 6 months ago

Comments

Please login first before commenting.