Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #6862 -- Refactored debug traceback extraction into an easy-to-…

…use class.

Aside from being a little easier to read and use, this means you could subclass
a request/response handler class (from django.core.handlers) to add your own
traceback extraction handling in non-DEBUG environments and reuse this code.
Thanks, Ned Batchelder.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@7927 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit e7e4b8b0f774b119bc1c46a62a97e51d7c8a35e3 1 parent 3b37c81
Malcolm Tredinnick authored July 15, 2008

Showing 1 changed file with 202 additions and 169 deletions. Show diff stats Hide diff stats

  1. 371  django/views/debug.py
371  django/views/debug.py
@@ -19,42 +19,6 @@ def linebreak_iter(template_source):
19 19
         p = template_source.find('\n', p+1)
20 20
     yield len(template_source) + 1
21 21
 
22  
-def get_template_exception_info(exc_type, exc_value, tb):
23  
-    origin, (start, end) = exc_value.source
24  
-    template_source = origin.reload()
25  
-    context_lines = 10
26  
-    line = 0
27  
-    upto = 0
28  
-    source_lines = []
29  
-    before = during = after = ""
30  
-    for num, next in enumerate(linebreak_iter(template_source)):
31  
-        if start >= upto and end <= next:
32  
-            line = num
33  
-            before = escape(template_source[upto:start])
34  
-            during = escape(template_source[start:end])
35  
-            after = escape(template_source[end:next])
36  
-        source_lines.append( (num, escape(template_source[upto:next])) )
37  
-        upto = next
38  
-    total = len(source_lines)
39  
-
40  
-    top = max(1, line - context_lines)
41  
-    bottom = min(total, line + 1 + context_lines)
42  
-
43  
-    template_info = {
44  
-        'message': exc_value.args[0],
45  
-        'source_lines': source_lines[top:bottom],
46  
-        'before': before,
47  
-        'during': during,
48  
-        'after': after,
49  
-        'top': top,
50  
-        'bottom': bottom,
51  
-        'total': total,
52  
-        'line': line,
53  
-        'name': origin.name,
54  
-    }
55  
-    exc_info = hasattr(exc_value, 'exc_info') and exc_value.exc_info or (exc_type, exc_value, tb)
56  
-    return exc_info + (template_info,)
57  
-
58 22
 def get_safe_settings():
59 23
     "Returns a dictionary of the settings module, with sensitive settings blurred out."
60 24
     settings_dict = {}
@@ -71,102 +35,212 @@ def technical_500_response(request, exc_type, exc_value, tb):
71 35
     Create a technical server error response. The last three arguments are
72 36
     the values returned from sys.exc_info() and friends.
73 37
     """
74  
-    html = get_traceback_html(request, exc_type, exc_value, tb)
  38
+    reporter = ExceptionReporter(request, exc_type, exc_value, tb)
  39
+    html = reporter.get_traceback_html()
75 40
     return HttpResponseServerError(html, mimetype='text/html')
76 41
 
77  
-def get_traceback_html(request, exc_type, exc_value, tb):
78  
-    "Return HTML code for traceback."
79  
-    template_info = None
80  
-    template_does_not_exist = False
81  
-    loader_debug_info = None
82  
-
83  
-    # Handle deprecated string exceptions
84  
-    if isinstance(exc_type, basestring):
85  
-        exc_value = Exception('Deprecated String Exception: %r' % exc_type)
86  
-        exc_type = type(exc_value)
87  
-
88  
-    if issubclass(exc_type, TemplateDoesNotExist):
89  
-        from django.template.loader import template_source_loaders
90  
-        template_does_not_exist = True
91  
-        loader_debug_info = []
92  
-        for loader in template_source_loaders:
  42
+class ExceptionReporter:
  43
+    """
  44
+    A class to organize and coordinate reporting on exceptions.
  45
+    """
  46
+    def __init__(self, request, exc_type, exc_value, tb):
  47
+        self.request = request
  48
+        self.exc_type = exc_type
  49
+        self.exc_value = exc_value
  50
+        self.tb = tb
  51
+
  52
+        self.template_info = None
  53
+        self.template_does_not_exist = False
  54
+        self.loader_debug_info = None
  55
+
  56
+        # Handle deprecated string exceptions
  57
+        if isinstance(self.exc_type, basestring):
  58
+            self.exc_value = Exception('Deprecated String Exception: %r' % self.exc_type)
  59
+            self.exc_type = type(self.exc_value)
  60
+
  61
+    def get_traceback_html(self):
  62
+        "Return HTML code for traceback."
  63
+
  64
+        if issubclass(self.exc_type, TemplateDoesNotExist):
  65
+            from django.template.loader import template_source_loaders
  66
+            self.template_does_not_exist = True
  67
+            self.loader_debug_info = []
  68
+            for loader in template_source_loaders:
  69
+                try:
  70
+                    source_list_func = getattr(__import__(loader.__module__, {}, {}, ['get_template_sources']), 'get_template_sources')
  71
+                    # NOTE: This assumes exc_value is the name of the template that
  72
+                    # the loader attempted to load.
  73
+                    template_list = [{'name': t, 'exists': os.path.exists(t)} \
  74
+                        for t in source_list_func(str(self.exc_value))]
  75
+                except (ImportError, AttributeError):
  76
+                    template_list = []
  77
+                self.loader_debug_info.append({
  78
+                    'loader': loader.__module__ + '.' + loader.__name__,
  79
+                    'templates': template_list,
  80
+                })
  81
+        if settings.TEMPLATE_DEBUG and hasattr(self.exc_value, 'source'):
  82
+            self.get_template_exception_info()
  83
+
  84
+        frames = self.get_traceback_frames()
  85
+
  86
+        unicode_hint = ''
  87
+        if issubclass(self.exc_type, UnicodeError):
  88
+            start = getattr(self.exc_value, 'start', None)
  89
+            end = getattr(self.exc_value, 'end', None)
  90
+            if start is not None and end is not None:
  91
+                unicode_str = self.exc_value.args[1]
  92
+                unicode_hint = smart_unicode(unicode_str[max(start-5, 0):min(end+5, len(unicode_str))], 'ascii', errors='replace')
  93
+        from django import get_version
  94
+        t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template')
  95
+        c = Context({
  96
+            'exception_type': self.exc_type.__name__,
  97
+            'exception_value': smart_unicode(self.exc_value, errors='replace'),
  98
+            'unicode_hint': unicode_hint,
  99
+            'frames': frames,
  100
+            'lastframe': frames[-1],
  101
+            'request': self.request,
  102
+            'request_protocol': self.request.is_secure() and "https" or "http",
  103
+            'settings': get_safe_settings(),
  104
+            'sys_executable': sys.executable,
  105
+            'sys_version_info': '%d.%d.%d' % sys.version_info[0:3],
  106
+            'server_time': datetime.datetime.now(),
  107
+            'django_version_info': get_version(),
  108
+            'sys_path' : sys.path,
  109
+            'template_info': self.template_info,
  110
+            'template_does_not_exist': self.template_does_not_exist,
  111
+            'loader_debug_info': self.loader_debug_info,
  112
+        })
  113
+        return t.render(c)
  114
+
  115
+    def get_template_exception_info(self):
  116
+        origin, (start, end) = self.exc_value.source
  117
+        template_source = origin.reload()
  118
+        context_lines = 10
  119
+        line = 0
  120
+        upto = 0
  121
+        source_lines = []
  122
+        before = during = after = ""
  123
+        for num, next in enumerate(linebreak_iter(template_source)):
  124
+            if start >= upto and end <= next:
  125
+                line = num
  126
+                before = escape(template_source[upto:start])
  127
+                during = escape(template_source[start:end])
  128
+                after = escape(template_source[end:next])
  129
+            source_lines.append( (num, escape(template_source[upto:next])) )
  130
+            upto = next
  131
+        total = len(source_lines)
  132
+
  133
+        top = max(1, line - context_lines)
  134
+        bottom = min(total, line + 1 + context_lines)
  135
+
  136
+        self.template_info = {
  137
+            'message': self.exc_value.args[0],
  138
+            'source_lines': source_lines[top:bottom],
  139
+            'before': before,
  140
+            'during': during,
  141
+            'after': after,
  142
+            'top': top,
  143
+            'bottom': bottom,
  144
+            'total': total,
  145
+            'line': line,
  146
+            'name': origin.name,
  147
+        }
  148
+        if hasattr(self.exc_value, 'exc_info') and self.exc_value.exc_info:
  149
+            exc_type, exc_value, tb = self.exc_value.exc_info
  150
+
  151
+    def _get_lines_from_file(self, filename, lineno, context_lines, loader=None, module_name=None):
  152
+        """
  153
+        Returns context_lines before and after lineno from file.
  154
+        Returns (pre_context_lineno, pre_context, context_line, post_context).
  155
+        """
  156
+        source = None
  157
+        if loader is not None and hasattr(loader, "get_source"):
  158
+            source = loader.get_source(module_name)
  159
+            if source is not None:
  160
+                source = source.splitlines()
  161
+        if source is None:
93 162
             try:
94  
-                source_list_func = getattr(__import__(loader.__module__, {}, {}, ['get_template_sources']), 'get_template_sources')
95  
-                # NOTE: This assumes exc_value is the name of the template that
96  
-                # the loader attempted to load.
97  
-                template_list = [{'name': t, 'exists': os.path.exists(t)} \
98  
-                    for t in source_list_func(str(exc_value))]
99  
-            except (ImportError, AttributeError):
100  
-                template_list = []
101  
-            loader_debug_info.append({
102  
-                'loader': loader.__module__ + '.' + loader.__name__,
103  
-                'templates': template_list,
104  
-            })
105  
-    if settings.TEMPLATE_DEBUG and hasattr(exc_value, 'source'):
106  
-        exc_type, exc_value, tb, template_info = get_template_exception_info(exc_type, exc_value, tb)
107  
-    frames = []
108  
-    while tb is not None:
109  
-        # support for __traceback_hide__ which is used by a few libraries
110  
-        # to hide internal frames.
111  
-        if tb.tb_frame.f_locals.get('__traceback_hide__'):
  163
+                f = open(filename)
  164
+                try:
  165
+                    source = f.readlines()
  166
+                finally:
  167
+                    f.close()
  168
+            except (OSError, IOError):
  169
+                pass
  170
+        if source is None:
  171
+            return None, [], None, []
  172
+
  173
+        encoding = 'ascii'
  174
+        for line in source[:2]:
  175
+            # File coding may be specified. Match pattern from PEP-263
  176
+            # (http://www.python.org/dev/peps/pep-0263/)
  177
+            match = re.search(r'coding[:=]\s*([-\w.]+)', line)
  178
+            if match:
  179
+                encoding = match.group(1)
  180
+                break
  181
+        source = [unicode(sline, encoding, 'replace') for sline in source]
  182
+
  183
+        lower_bound = max(0, lineno - context_lines)
  184
+        upper_bound = lineno + context_lines
  185
+
  186
+        pre_context = [line.strip('\n') for line in source[lower_bound:lineno]]
  187
+        context_line = source[lineno].strip('\n')
  188
+        post_context = [line.strip('\n') for line in source[lineno+1:upper_bound]]
  189
+
  190
+        return lower_bound, pre_context, context_line, post_context
  191
+
  192
+    def get_traceback_frames(self):
  193
+        frames = []
  194
+        tb = self.tb
  195
+        while tb is not None:
  196
+            # support for __traceback_hide__ which is used by a few libraries
  197
+            # to hide internal frames.
  198
+            if tb.tb_frame.f_locals.get('__traceback_hide__'):
  199
+                tb = tb.tb_next
  200
+                continue
  201
+            filename = tb.tb_frame.f_code.co_filename
  202
+            function = tb.tb_frame.f_code.co_name
  203
+            lineno = tb.tb_lineno - 1
  204
+            loader = tb.tb_frame.f_globals.get('__loader__')
  205
+            module_name = tb.tb_frame.f_globals.get('__name__')
  206
+            pre_context_lineno, pre_context, context_line, post_context = self._get_lines_from_file(filename, lineno, 7, loader, module_name)
  207
+            if pre_context_lineno is not None:
  208
+                frames.append({
  209
+                    'tb': tb,
  210
+                    'filename': filename,
  211
+                    'function': function,
  212
+                    'lineno': lineno + 1,
  213
+                    'vars': tb.tb_frame.f_locals.items(),
  214
+                    'id': id(tb),
  215
+                    'pre_context': pre_context,
  216
+                    'context_line': context_line,
  217
+                    'post_context': post_context,
  218
+                    'pre_context_lineno': pre_context_lineno + 1,
  219
+                })
112 220
             tb = tb.tb_next
113  
-            continue
114  
-        filename = tb.tb_frame.f_code.co_filename
115  
-        function = tb.tb_frame.f_code.co_name
116  
-        lineno = tb.tb_lineno - 1
117  
-        loader = tb.tb_frame.f_globals.get('__loader__')
118  
-        module_name = tb.tb_frame.f_globals.get('__name__')
119  
-        pre_context_lineno, pre_context, context_line, post_context = _get_lines_from_file(filename, lineno, 7, loader, module_name)
120  
-        if pre_context_lineno is not None:
121  
-            frames.append({
122  
-                'tb': tb,
123  
-                'filename': filename,
124  
-                'function': function,
125  
-                'lineno': lineno + 1,
126  
-                'vars': tb.tb_frame.f_locals.items(),
127  
-                'id': id(tb),
128  
-                'pre_context': pre_context,
129  
-                'context_line': context_line,
130  
-                'post_context': post_context,
131  
-                'pre_context_lineno': pre_context_lineno + 1,
132  
-            })
133  
-        tb = tb.tb_next
134  
-
135  
-    if not frames:
136  
-        frames = [{
137  
-            'filename': '&lt;unknown&gt;',
138  
-            'function': '?',
139  
-            'lineno': '?',
140  
-        }]
141  
-
142  
-    unicode_hint = ''
143  
-    if issubclass(exc_type, UnicodeError):
144  
-        start = getattr(exc_value, 'start', None)
145  
-        end = getattr(exc_value, 'end', None)
146  
-        if start is not None and end is not None:
147  
-            unicode_str = exc_value.args[1]
148  
-            unicode_hint = smart_unicode(unicode_str[max(start-5, 0):min(end+5, len(unicode_str))], 'ascii', errors='replace')
149  
-    from django import get_version
150  
-    t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template')
151  
-    c = Context({
152  
-        'exception_type': exc_type.__name__,
153  
-        'exception_value': smart_unicode(exc_value, errors='replace'),
154  
-        'unicode_hint': unicode_hint,
155  
-        'frames': frames,
156  
-        'lastframe': frames[-1],
157  
-        'request': request,
158  
-        'request_protocol': request.is_secure() and "https" or "http",
159  
-        'settings': get_safe_settings(),
160  
-        'sys_executable': sys.executable,
161  
-        'sys_version_info': '%d.%d.%d' % sys.version_info[0:3],
162  
-        'server_time': datetime.datetime.now(),
163  
-        'django_version_info': get_version(),
164  
-        'sys_path' : sys.path,
165  
-        'template_info': template_info,
166  
-        'template_does_not_exist': template_does_not_exist,
167  
-        'loader_debug_info': loader_debug_info,
168  
-    })
169  
-    return t.render(c)
  221
+
  222
+        if not frames:
  223
+            frames = [{
  224
+                'filename': '&lt;unknown&gt;',
  225
+                'function': '?',
  226
+                'lineno': '?',
  227
+                'context_line': '???',
  228
+            }]
  229
+
  230
+        return frames
  231
+
  232
+    def format_exception(self):
  233
+        """
  234
+        Return the same data as from traceback.format_exception.
  235
+        """
  236
+        import traceback
  237
+        frames = self.get_traceback_frames()
  238
+        tb = [ (f['filename'], f['lineno'], f['function'], f['context_line']) for f in frames ]
  239
+        list = ['Traceback (most recent call last):\n']
  240
+        list += traceback.format_list(tb)
  241
+        list += traceback.format_exception_only(self.exc_type, self.exc_value)
  242
+        return list
  243
+
170 244
 
171 245
 def technical_404_response(request, exception):
172 246
     "Create a technical 404 error response. The exception should be the Http404."
@@ -199,47 +273,6 @@ def empty_urlconf(request):
199 273
     })
200 274
     return HttpResponse(t.render(c), mimetype='text/html')
201 275
 
202  
-def _get_lines_from_file(filename, lineno, context_lines, loader=None, module_name=None):
203  
-    """
204  
-    Returns context_lines before and after lineno from file.
205  
-    Returns (pre_context_lineno, pre_context, context_line, post_context).
206  
-    """
207  
-    source = None
208  
-    if loader is not None and hasattr(loader, "get_source"):
209  
-        source = loader.get_source(module_name)
210  
-        if source is not None:
211  
-            source = source.splitlines()
212  
-    if source is None:
213  
-        try:
214  
-            f = open(filename)
215  
-            try:
216  
-                source = f.readlines()
217  
-            finally:
218  
-                f.close()
219  
-        except (OSError, IOError):
220  
-            pass
221  
-    if source is None:
222  
-        return None, [], None, []
223  
-
224  
-    encoding = 'ascii'
225  
-    for line in source[:2]:
226  
-        # File coding may be specified. Match pattern from PEP-263
227  
-        # (http://www.python.org/dev/peps/pep-0263/)
228  
-        match = re.search(r'coding[:=]\s*([-\w.]+)', line)
229  
-        if match:
230  
-            encoding = match.group(1)
231  
-            break
232  
-    source = [unicode(sline, encoding, 'replace') for sline in source]
233  
-
234  
-    lower_bound = max(0, lineno - context_lines)
235  
-    upper_bound = lineno + context_lines
236  
-
237  
-    pre_context = [line.strip('\n') for line in source[lower_bound:lineno]]
238  
-    context_line = source[lineno].strip('\n')
239  
-    post_context = [line.strip('\n') for line in source[lineno+1:upper_bound]]
240  
-
241  
-    return lower_bound, pre_context, context_line, post_context
242  
-
243 276
 #
244 277
 # Templates are embedded in the file so that we know the error handler will
245 278
 # always work even if the template loader is broken.

0 notes on commit e7e4b8b

Please sign in to comment.
Something went wrong with that request. Please try again.