mirror of
				https://github.com/django/django.git
				synced 2025-10-30 17:16:10 +00:00 
			
		
		
		
	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
This commit is contained in:
		| @@ -19,42 +19,6 @@ def linebreak_iter(template_source): | |||||||
|         p = template_source.find('\n', p+1) |         p = template_source.find('\n', p+1) | ||||||
|     yield len(template_source) + 1 |     yield len(template_source) + 1 | ||||||
|  |  | ||||||
| def get_template_exception_info(exc_type, exc_value, tb): |  | ||||||
|     origin, (start, end) = exc_value.source |  | ||||||
|     template_source = origin.reload() |  | ||||||
|     context_lines = 10 |  | ||||||
|     line = 0 |  | ||||||
|     upto = 0 |  | ||||||
|     source_lines = [] |  | ||||||
|     before = during = after = "" |  | ||||||
|     for num, next in enumerate(linebreak_iter(template_source)): |  | ||||||
|         if start >= upto and end <= next: |  | ||||||
|             line = num |  | ||||||
|             before = escape(template_source[upto:start]) |  | ||||||
|             during = escape(template_source[start:end]) |  | ||||||
|             after = escape(template_source[end:next]) |  | ||||||
|         source_lines.append( (num, escape(template_source[upto:next])) ) |  | ||||||
|         upto = next |  | ||||||
|     total = len(source_lines) |  | ||||||
|  |  | ||||||
|     top = max(1, line - context_lines) |  | ||||||
|     bottom = min(total, line + 1 + context_lines) |  | ||||||
|  |  | ||||||
|     template_info = { |  | ||||||
|         'message': exc_value.args[0], |  | ||||||
|         'source_lines': source_lines[top:bottom], |  | ||||||
|         'before': before, |  | ||||||
|         'during': during, |  | ||||||
|         'after': after, |  | ||||||
|         'top': top, |  | ||||||
|         'bottom': bottom, |  | ||||||
|         'total': total, |  | ||||||
|         'line': line, |  | ||||||
|         'name': origin.name, |  | ||||||
|     } |  | ||||||
|     exc_info = hasattr(exc_value, 'exc_info') and exc_value.exc_info or (exc_type, exc_value, tb) |  | ||||||
|     return exc_info + (template_info,) |  | ||||||
|  |  | ||||||
| def get_safe_settings(): | def get_safe_settings(): | ||||||
|     "Returns a dictionary of the settings module, with sensitive settings blurred out." |     "Returns a dictionary of the settings module, with sensitive settings blurred out." | ||||||
|     settings_dict = {} |     settings_dict = {} | ||||||
| @@ -71,102 +35,212 @@ def technical_500_response(request, exc_type, exc_value, tb): | |||||||
|     Create a technical server error response. The last three arguments are |     Create a technical server error response. The last three arguments are | ||||||
|     the values returned from sys.exc_info() and friends. |     the values returned from sys.exc_info() and friends. | ||||||
|     """ |     """ | ||||||
|     html = get_traceback_html(request, exc_type, exc_value, tb) |     reporter = ExceptionReporter(request, exc_type, exc_value, tb) | ||||||
|  |     html = reporter.get_traceback_html() | ||||||
|     return HttpResponseServerError(html, mimetype='text/html') |     return HttpResponseServerError(html, mimetype='text/html') | ||||||
|  |  | ||||||
| def get_traceback_html(request, exc_type, exc_value, tb): | class ExceptionReporter: | ||||||
|     "Return HTML code for traceback." |     """ | ||||||
|     template_info = None |     A class to organize and coordinate reporting on exceptions. | ||||||
|     template_does_not_exist = False |     """ | ||||||
|     loader_debug_info = None |     def __init__(self, request, exc_type, exc_value, tb): | ||||||
|  |         self.request = request | ||||||
|  |         self.exc_type = exc_type | ||||||
|  |         self.exc_value = exc_value | ||||||
|  |         self.tb = tb | ||||||
|  |  | ||||||
|     # Handle deprecated string exceptions |         self.template_info = None | ||||||
|     if isinstance(exc_type, basestring): |         self.template_does_not_exist = False | ||||||
|         exc_value = Exception('Deprecated String Exception: %r' % exc_type) |         self.loader_debug_info = None | ||||||
|         exc_type = type(exc_value) |  | ||||||
|  |  | ||||||
|     if issubclass(exc_type, TemplateDoesNotExist): |         # Handle deprecated string exceptions | ||||||
|         from django.template.loader import template_source_loaders |         if isinstance(self.exc_type, basestring): | ||||||
|         template_does_not_exist = True |             self.exc_value = Exception('Deprecated String Exception: %r' % self.exc_type) | ||||||
|         loader_debug_info = [] |             self.exc_type = type(self.exc_value) | ||||||
|         for loader in template_source_loaders: |  | ||||||
|  |     def get_traceback_html(self): | ||||||
|  |         "Return HTML code for traceback." | ||||||
|  |  | ||||||
|  |         if issubclass(self.exc_type, TemplateDoesNotExist): | ||||||
|  |             from django.template.loader import template_source_loaders | ||||||
|  |             self.template_does_not_exist = True | ||||||
|  |             self.loader_debug_info = [] | ||||||
|  |             for loader in template_source_loaders: | ||||||
|  |                 try: | ||||||
|  |                     source_list_func = getattr(__import__(loader.__module__, {}, {}, ['get_template_sources']), 'get_template_sources') | ||||||
|  |                     # NOTE: This assumes exc_value is the name of the template that | ||||||
|  |                     # the loader attempted to load. | ||||||
|  |                     template_list = [{'name': t, 'exists': os.path.exists(t)} \ | ||||||
|  |                         for t in source_list_func(str(self.exc_value))] | ||||||
|  |                 except (ImportError, AttributeError): | ||||||
|  |                     template_list = [] | ||||||
|  |                 self.loader_debug_info.append({ | ||||||
|  |                     'loader': loader.__module__ + '.' + loader.__name__, | ||||||
|  |                     'templates': template_list, | ||||||
|  |                 }) | ||||||
|  |         if settings.TEMPLATE_DEBUG and hasattr(self.exc_value, 'source'): | ||||||
|  |             self.get_template_exception_info() | ||||||
|  |  | ||||||
|  |         frames = self.get_traceback_frames() | ||||||
|  |  | ||||||
|  |         unicode_hint = '' | ||||||
|  |         if issubclass(self.exc_type, UnicodeError): | ||||||
|  |             start = getattr(self.exc_value, 'start', None) | ||||||
|  |             end = getattr(self.exc_value, 'end', None) | ||||||
|  |             if start is not None and end is not None: | ||||||
|  |                 unicode_str = self.exc_value.args[1] | ||||||
|  |                 unicode_hint = smart_unicode(unicode_str[max(start-5, 0):min(end+5, len(unicode_str))], 'ascii', errors='replace') | ||||||
|  |         from django import get_version | ||||||
|  |         t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template') | ||||||
|  |         c = Context({ | ||||||
|  |             'exception_type': self.exc_type.__name__, | ||||||
|  |             'exception_value': smart_unicode(self.exc_value, errors='replace'), | ||||||
|  |             'unicode_hint': unicode_hint, | ||||||
|  |             'frames': frames, | ||||||
|  |             'lastframe': frames[-1], | ||||||
|  |             'request': self.request, | ||||||
|  |             'request_protocol': self.request.is_secure() and "https" or "http", | ||||||
|  |             'settings': get_safe_settings(), | ||||||
|  |             'sys_executable': sys.executable, | ||||||
|  |             'sys_version_info': '%d.%d.%d' % sys.version_info[0:3], | ||||||
|  |             'server_time': datetime.datetime.now(), | ||||||
|  |             'django_version_info': get_version(), | ||||||
|  |             'sys_path' : sys.path, | ||||||
|  |             'template_info': self.template_info, | ||||||
|  |             'template_does_not_exist': self.template_does_not_exist, | ||||||
|  |             'loader_debug_info': self.loader_debug_info, | ||||||
|  |         }) | ||||||
|  |         return t.render(c) | ||||||
|  |  | ||||||
|  |     def get_template_exception_info(self): | ||||||
|  |         origin, (start, end) = self.exc_value.source | ||||||
|  |         template_source = origin.reload() | ||||||
|  |         context_lines = 10 | ||||||
|  |         line = 0 | ||||||
|  |         upto = 0 | ||||||
|  |         source_lines = [] | ||||||
|  |         before = during = after = "" | ||||||
|  |         for num, next in enumerate(linebreak_iter(template_source)): | ||||||
|  |             if start >= upto and end <= next: | ||||||
|  |                 line = num | ||||||
|  |                 before = escape(template_source[upto:start]) | ||||||
|  |                 during = escape(template_source[start:end]) | ||||||
|  |                 after = escape(template_source[end:next]) | ||||||
|  |             source_lines.append( (num, escape(template_source[upto:next])) ) | ||||||
|  |             upto = next | ||||||
|  |         total = len(source_lines) | ||||||
|  |  | ||||||
|  |         top = max(1, line - context_lines) | ||||||
|  |         bottom = min(total, line + 1 + context_lines) | ||||||
|  |  | ||||||
|  |         self.template_info = { | ||||||
|  |             'message': self.exc_value.args[0], | ||||||
|  |             'source_lines': source_lines[top:bottom], | ||||||
|  |             'before': before, | ||||||
|  |             'during': during, | ||||||
|  |             'after': after, | ||||||
|  |             'top': top, | ||||||
|  |             'bottom': bottom, | ||||||
|  |             'total': total, | ||||||
|  |             'line': line, | ||||||
|  |             'name': origin.name, | ||||||
|  |         } | ||||||
|  |         if hasattr(self.exc_value, 'exc_info') and self.exc_value.exc_info: | ||||||
|  |             exc_type, exc_value, tb = self.exc_value.exc_info | ||||||
|  |  | ||||||
|  |     def _get_lines_from_file(self, filename, lineno, context_lines, loader=None, module_name=None): | ||||||
|  |         """ | ||||||
|  |         Returns context_lines before and after lineno from file. | ||||||
|  |         Returns (pre_context_lineno, pre_context, context_line, post_context). | ||||||
|  |         """ | ||||||
|  |         source = None | ||||||
|  |         if loader is not None and hasattr(loader, "get_source"): | ||||||
|  |             source = loader.get_source(module_name) | ||||||
|  |             if source is not None: | ||||||
|  |                 source = source.splitlines() | ||||||
|  |         if source is None: | ||||||
|             try: |             try: | ||||||
|                 source_list_func = getattr(__import__(loader.__module__, {}, {}, ['get_template_sources']), 'get_template_sources') |                 f = open(filename) | ||||||
|                 # NOTE: This assumes exc_value is the name of the template that |                 try: | ||||||
|                 # the loader attempted to load. |                     source = f.readlines() | ||||||
|                 template_list = [{'name': t, 'exists': os.path.exists(t)} \ |                 finally: | ||||||
|                     for t in source_list_func(str(exc_value))] |                     f.close() | ||||||
|             except (ImportError, AttributeError): |             except (OSError, IOError): | ||||||
|                 template_list = [] |                 pass | ||||||
|             loader_debug_info.append({ |         if source is None: | ||||||
|                 'loader': loader.__module__ + '.' + loader.__name__, |             return None, [], None, [] | ||||||
|                 'templates': template_list, |  | ||||||
|             }) |         encoding = 'ascii' | ||||||
|     if settings.TEMPLATE_DEBUG and hasattr(exc_value, 'source'): |         for line in source[:2]: | ||||||
|         exc_type, exc_value, tb, template_info = get_template_exception_info(exc_type, exc_value, tb) |             # File coding may be specified. Match pattern from PEP-263 | ||||||
|     frames = [] |             # (http://www.python.org/dev/peps/pep-0263/) | ||||||
|     while tb is not None: |             match = re.search(r'coding[:=]\s*([-\w.]+)', line) | ||||||
|         # support for __traceback_hide__ which is used by a few libraries |             if match: | ||||||
|         # to hide internal frames. |                 encoding = match.group(1) | ||||||
|         if tb.tb_frame.f_locals.get('__traceback_hide__'): |                 break | ||||||
|  |         source = [unicode(sline, encoding, 'replace') for sline in source] | ||||||
|  |  | ||||||
|  |         lower_bound = max(0, lineno - context_lines) | ||||||
|  |         upper_bound = lineno + context_lines | ||||||
|  |  | ||||||
|  |         pre_context = [line.strip('\n') for line in source[lower_bound:lineno]] | ||||||
|  |         context_line = source[lineno].strip('\n') | ||||||
|  |         post_context = [line.strip('\n') for line in source[lineno+1:upper_bound]] | ||||||
|  |  | ||||||
|  |         return lower_bound, pre_context, context_line, post_context | ||||||
|  |  | ||||||
|  |     def get_traceback_frames(self): | ||||||
|  |         frames = [] | ||||||
|  |         tb = self.tb | ||||||
|  |         while tb is not None: | ||||||
|  |             # support for __traceback_hide__ which is used by a few libraries | ||||||
|  |             # to hide internal frames. | ||||||
|  |             if tb.tb_frame.f_locals.get('__traceback_hide__'): | ||||||
|  |                 tb = tb.tb_next | ||||||
|  |                 continue | ||||||
|  |             filename = tb.tb_frame.f_code.co_filename | ||||||
|  |             function = tb.tb_frame.f_code.co_name | ||||||
|  |             lineno = tb.tb_lineno - 1 | ||||||
|  |             loader = tb.tb_frame.f_globals.get('__loader__') | ||||||
|  |             module_name = tb.tb_frame.f_globals.get('__name__') | ||||||
|  |             pre_context_lineno, pre_context, context_line, post_context = self._get_lines_from_file(filename, lineno, 7, loader, module_name) | ||||||
|  |             if pre_context_lineno is not None: | ||||||
|  |                 frames.append({ | ||||||
|  |                     'tb': tb, | ||||||
|  |                     'filename': filename, | ||||||
|  |                     'function': function, | ||||||
|  |                     'lineno': lineno + 1, | ||||||
|  |                     'vars': tb.tb_frame.f_locals.items(), | ||||||
|  |                     'id': id(tb), | ||||||
|  |                     'pre_context': pre_context, | ||||||
|  |                     'context_line': context_line, | ||||||
|  |                     'post_context': post_context, | ||||||
|  |                     'pre_context_lineno': pre_context_lineno + 1, | ||||||
|  |                 }) | ||||||
|             tb = tb.tb_next |             tb = tb.tb_next | ||||||
|             continue |  | ||||||
|         filename = tb.tb_frame.f_code.co_filename |  | ||||||
|         function = tb.tb_frame.f_code.co_name |  | ||||||
|         lineno = tb.tb_lineno - 1 |  | ||||||
|         loader = tb.tb_frame.f_globals.get('__loader__') |  | ||||||
|         module_name = tb.tb_frame.f_globals.get('__name__') |  | ||||||
|         pre_context_lineno, pre_context, context_line, post_context = _get_lines_from_file(filename, lineno, 7, loader, module_name) |  | ||||||
|         if pre_context_lineno is not None: |  | ||||||
|             frames.append({ |  | ||||||
|                 'tb': tb, |  | ||||||
|                 'filename': filename, |  | ||||||
|                 'function': function, |  | ||||||
|                 'lineno': lineno + 1, |  | ||||||
|                 'vars': tb.tb_frame.f_locals.items(), |  | ||||||
|                 'id': id(tb), |  | ||||||
|                 'pre_context': pre_context, |  | ||||||
|                 'context_line': context_line, |  | ||||||
|                 'post_context': post_context, |  | ||||||
|                 'pre_context_lineno': pre_context_lineno + 1, |  | ||||||
|             }) |  | ||||||
|         tb = tb.tb_next |  | ||||||
|  |  | ||||||
|     if not frames: |         if not frames: | ||||||
|         frames = [{ |             frames = [{ | ||||||
|             'filename': '<unknown>', |                 'filename': '<unknown>', | ||||||
|             'function': '?', |                 'function': '?', | ||||||
|             'lineno': '?', |                 'lineno': '?', | ||||||
|         }] |                 'context_line': '???', | ||||||
|  |             }] | ||||||
|  |  | ||||||
|  |         return frames | ||||||
|  |  | ||||||
|  |     def format_exception(self): | ||||||
|  |         """ | ||||||
|  |         Return the same data as from traceback.format_exception. | ||||||
|  |         """ | ||||||
|  |         import traceback | ||||||
|  |         frames = self.get_traceback_frames() | ||||||
|  |         tb = [ (f['filename'], f['lineno'], f['function'], f['context_line']) for f in frames ] | ||||||
|  |         list = ['Traceback (most recent call last):\n'] | ||||||
|  |         list += traceback.format_list(tb) | ||||||
|  |         list += traceback.format_exception_only(self.exc_type, self.exc_value) | ||||||
|  |         return list | ||||||
|  |  | ||||||
|     unicode_hint = '' |  | ||||||
|     if issubclass(exc_type, UnicodeError): |  | ||||||
|         start = getattr(exc_value, 'start', None) |  | ||||||
|         end = getattr(exc_value, 'end', None) |  | ||||||
|         if start is not None and end is not None: |  | ||||||
|             unicode_str = exc_value.args[1] |  | ||||||
|             unicode_hint = smart_unicode(unicode_str[max(start-5, 0):min(end+5, len(unicode_str))], 'ascii', errors='replace') |  | ||||||
|     from django import get_version |  | ||||||
|     t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template') |  | ||||||
|     c = Context({ |  | ||||||
|         'exception_type': exc_type.__name__, |  | ||||||
|         'exception_value': smart_unicode(exc_value, errors='replace'), |  | ||||||
|         'unicode_hint': unicode_hint, |  | ||||||
|         'frames': frames, |  | ||||||
|         'lastframe': frames[-1], |  | ||||||
|         'request': request, |  | ||||||
|         'request_protocol': request.is_secure() and "https" or "http", |  | ||||||
|         'settings': get_safe_settings(), |  | ||||||
|         'sys_executable': sys.executable, |  | ||||||
|         'sys_version_info': '%d.%d.%d' % sys.version_info[0:3], |  | ||||||
|         'server_time': datetime.datetime.now(), |  | ||||||
|         'django_version_info': get_version(), |  | ||||||
|         'sys_path' : sys.path, |  | ||||||
|         'template_info': template_info, |  | ||||||
|         'template_does_not_exist': template_does_not_exist, |  | ||||||
|         'loader_debug_info': loader_debug_info, |  | ||||||
|     }) |  | ||||||
|     return t.render(c) |  | ||||||
|  |  | ||||||
| def technical_404_response(request, exception): | def technical_404_response(request, exception): | ||||||
|     "Create a technical 404 error response. The exception should be the Http404." |     "Create a technical 404 error response. The exception should be the Http404." | ||||||
| @@ -199,47 +273,6 @@ def empty_urlconf(request): | |||||||
|     }) |     }) | ||||||
|     return HttpResponse(t.render(c), mimetype='text/html') |     return HttpResponse(t.render(c), mimetype='text/html') | ||||||
|  |  | ||||||
| def _get_lines_from_file(filename, lineno, context_lines, loader=None, module_name=None): |  | ||||||
|     """ |  | ||||||
|     Returns context_lines before and after lineno from file. |  | ||||||
|     Returns (pre_context_lineno, pre_context, context_line, post_context). |  | ||||||
|     """ |  | ||||||
|     source = None |  | ||||||
|     if loader is not None and hasattr(loader, "get_source"): |  | ||||||
|         source = loader.get_source(module_name) |  | ||||||
|         if source is not None: |  | ||||||
|             source = source.splitlines() |  | ||||||
|     if source is None: |  | ||||||
|         try: |  | ||||||
|             f = open(filename) |  | ||||||
|             try: |  | ||||||
|                 source = f.readlines() |  | ||||||
|             finally: |  | ||||||
|                 f.close() |  | ||||||
|         except (OSError, IOError): |  | ||||||
|             pass |  | ||||||
|     if source is None: |  | ||||||
|         return None, [], None, [] |  | ||||||
|  |  | ||||||
|     encoding = 'ascii' |  | ||||||
|     for line in source[:2]: |  | ||||||
|         # File coding may be specified. Match pattern from PEP-263 |  | ||||||
|         # (http://www.python.org/dev/peps/pep-0263/) |  | ||||||
|         match = re.search(r'coding[:=]\s*([-\w.]+)', line) |  | ||||||
|         if match: |  | ||||||
|             encoding = match.group(1) |  | ||||||
|             break |  | ||||||
|     source = [unicode(sline, encoding, 'replace') for sline in source] |  | ||||||
|  |  | ||||||
|     lower_bound = max(0, lineno - context_lines) |  | ||||||
|     upper_bound = lineno + context_lines |  | ||||||
|  |  | ||||||
|     pre_context = [line.strip('\n') for line in source[lower_bound:lineno]] |  | ||||||
|     context_line = source[lineno].strip('\n') |  | ||||||
|     post_context = [line.strip('\n') for line in source[lineno+1:upper_bound]] |  | ||||||
|  |  | ||||||
|     return lower_bound, pre_context, context_line, post_context |  | ||||||
|  |  | ||||||
| # | # | ||||||
| # Templates are embedded in the file so that we know the error handler will | # Templates are embedded in the file so that we know the error handler will | ||||||
| # always work even if the template loader is broken. | # always work even if the template loader is broken. | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user