Ticket #6862: 6862_stack_reporting.diff

File 6862_stack_reporting.diff, 17.2 KB (added by Ned Batchelder, 17 years ago)

The patch

  • django/core/handlers/base.py

     
    133133
    134134    def _get_traceback(self, exc_info=None):
    135135        "Helper function to return the traceback as a string"
    136         import traceback
    137         return '\n'.join(traceback.format_exception(*(exc_info or sys.exc_info())))
     136        exc_info = exc_info or sys.exc_info()
     137        # Try to use the ExceptionReporter
     138        try:
     139            from django.views import debug
     140            reporter = debug.ExceptionReporter(None, *exc_info)
     141            lines = reporter.format_exception()
     142        except:
     143            # If anything goes wrong, fall back to less capable traceback.
     144            import traceback
     145            lines = traceback.format_exception(*exc_info)
     146        return '\n'.join(lines)
    138147
    139148    def apply_response_fixes(self, request, response):
    140149        """
  • django/views/debug.py

     
    1919        p = template_source.find('\n', p+1)
    2020    yield len(template_source) + 1
    2121
    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 
    5822def get_safe_settings():
    5923    "Returns a dictionary of the settings module, with sensitive settings blurred out."
    6024    settings_dict = {}
     
    7135    Create a technical server error response. The last three arguments are
    7236    the values returned from sys.exc_info() and friends.
    7337    """
    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()
    7540    return HttpResponseServerError(html, mimetype='text/html')
    7641
    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:
     42class 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:
    93162            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                })
    112220            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
    170244
    171245def technical_404_response(request, exception):
    172246    "Create a technical 404 error response. The exception should be the Http404."
     
    199273    })
    200274    return HttpResponse(t.render(c), mimetype='text/html')
    201275
    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 
    243276#
    244277# Templates are embedded in the file so that we know the error handler will
    245278# always work even if the template loader is broken.
Back to Top