0001"""Custom error middleware subclasses, used for error theme
0002
0003These error middleware sub-classes are used mainly to provide skinning
0004for the Paste middleware. In the future this entire module will likely
0005be little more than a template, as Paste will get the skinning functionality.
0006
0007The only additional thing besides skinning supplied, is the Template traceback
0008information.
0009"""
0010import cgi
0011import logging
0012import sys
0013
0014from paste.evalexception.middleware import *
0015from paste.exceptions.formatter import *
0016
0017import pylons
0018from pylons.middleware import media_path
0019from pylons.util import get_prefix
0020
0021__all__ = []
0022
0023log = logging.getLogger(__name__)
0024
0025
0026class Supplement(errormiddleware.Supplement):
0027 """This is a supplement used to display standard WSGI information in
0028 the traceback.
0029 """
0030 def extraData(self):
0031 data = {}
0032 cgi_vars = data[('extra', 'CGI Variables')] = {}
0033 wsgi_vars = data[('extra', 'WSGI Variables')] = {}
0034 # XXX: Legacy: hiding paste.config
0035 hide_vars = ['paste.config', 'wsgi.errors', 'wsgi.input',
0036 'wsgi.multithread', 'wsgi.multiprocess',
0037 'wsgi.run_once', 'wsgi.version',
0038 'wsgi.url_scheme']
0039 for name, value in self.environ.items():
0040 if name.upper() == name:
0041 if value:
0042 cgi_vars[name] = value
0043 elif name not in hide_vars:
0044 wsgi_vars[name] = value
0045 if self.environ['wsgi.version'] != (1, 0):
0046 wsgi_vars['wsgi.version'] = self.environ['wsgi.version']
0047 proc_desc = tuple([int(bool(self.environ[key]))
0048 for key in ('wsgi.multiprocess',
0049 'wsgi.multithread',
0050 'wsgi.run_once')])
0051 wsgi_vars['wsgi process'] = self.process_combos[proc_desc]
0052 wsgi_vars['application'] = self.middleware.application
0053 data[('extra', 'Configuration')] = pylons.config.current_conf()
0054
0055 # Add any extra sections here
0056
0057 return data
0058
0059
0060class HTMLFormatter(formatter.HTMLFormatter):
0061 def format_collected_data(self, exc_data):
0062 general_data = {}
0063 if self.show_extra_data:
0064 for name, value_list in exc_data.extra_data.items():
0065 if isinstance(name, tuple):
0066 importance, title = name
0067 else:
0068 importance, title = 'normal', name
0069 for value in value_list:
0070 general_data[(importance, name)] = self.format_extra_data(
0071 importance, title, value)
0072 lines = []
0073 frames = self.filter_frames(exc_data.frames)
0074 for frame in frames:
0075 sup = frame.supplement
0076 if sup:
0077 if sup.object:
0078 general_data[('important', 'object')] = self.format_sup_object(
0079 sup.object)
0080 if sup.source_url:
0081 general_data[('important', 'source_url')] = self.format_sup_url(
0082 sup.source_url)
0083 if sup.line:
0084 lines.append(self.format_sup_line_pos(sup.line, sup.column))
0085 if sup.expression:
0086 lines.append(self.format_sup_expression(sup.expression))
0087 if sup.warnings:
0088 for warning in sup.warnings:
0089 lines.append(self.format_sup_warning(warning))
0090 if sup.info:
0091 lines.extend(self.format_sup_info(sup.info))
0092 if frame.supplement_exception:
0093 lines.append('Exception in supplement:')
0094 lines.append(self.quote_long(frame.supplement_exception))
0095 if frame.traceback_info:
0096 lines.append(self.format_traceback_info(frame.traceback_info))
0097 filename = frame.filename
0098 if filename and self.trim_source_paths:
0099 for path, repl in self.trim_source_paths:
0100 if filename.startswith(path):
0101 filename = repl + filename[len(path):]
0102 break
0103 lines.append(self.format_source_line(filename or '?', frame))
0104 source = frame.get_source_line()
0105 long_source = frame.get_source_line(2)
0106 if source:
0107 lines.append(self.format_long_source(
0108 source, long_source))
0109 exc_info = self.format_exception_info(
0110 exc_data.exception_type,
0111 exc_data.exception_value)
0112 data_by_importance = {'important': [], 'normal': [],
0113 'supplemental': [], 'extra': []}
0114 for (importance, name), value in general_data.items():
0115 data_by_importance[importance].append(
0116 (name, value))
0117 for value in data_by_importance.itervalues():
0118 value.sort()
0119 return self.format_combine(data_by_importance, lines, exc_info)
0120
0121 def format_extra_data(self, importance, title, value):
0122 if isinstance(value, str):
0123 s = self.pretty_string_repr(value)
0124 if '\n' in s:
0125 return '%s:<br><pre>%s</pre>' % (title, self.quote(s))
0126 else:
0127 return '%s: <tt>%s</tt>' % (title, self.quote(s))
0128 elif isinstance(value, dict):
0129 return self.zebra_table(title, value)
0130 elif (isinstance(value, (list, tuple))
0131 and self.long_item_list(value)):
0132 return '%s: <tt>[<br>\n %s]</tt>' % (
0133 title, ',<br> '.join(map(self.quote, map(repr, value))))
0134 else:
0135 return '%s: <tt>%s</tt>' % (title, self.quote(repr(value)))
0136
0137 def zebra_table(self, title, rows, table_class="variables"):
0138 if isinstance(rows, dict):
0139 rows = rows.items()
0140 rows.sort()
0141 table = ['<table class="%s">' % table_class,
0142 '<tr class="header"><th colspan="2">%s</th></tr>'
0143 % self.quote(title)]
0144 odd = False
0145 for name, value in rows:
0146 try:
0147 value = repr(value)
0148 except Exception, e:
0149 value = 'Cannot print: %s' % e
0150 odd = not odd
0151 table.append(
0152 '<tr class="%s"><td>%s</td>'
0153 % (odd and 'odd' or 'even', self.quote(name)))
0154 table.append(
0155 '<td><tt>%s</tt></td></tr>'
0156 % make_wrappable(self.quote(value)))
0157 table.append('</table>')
0158 return '\n'.join(table)
0159
0160 def format_combine(self, data_by_importance, lines, exc_info):
0161
0162 lines[:0] = [value for n, value in data_by_importance['important']]
0163 lines.append(exc_info)
0164 for name in 'normal', 'supplemental':
0165 lines.extend([value for n, value in data_by_importance[name]])
0166
0167 extra_data = []
0168 if data_by_importance['extra']:
0169 #extra_data.append(
0170 # '<script type="text/javascript">\nshow_button(\'extra_data\', \'extra data\');\n</script>\n' +
0171 # '<div id="extra_data" class="hidden-data">\n')
0172 extra_data.extend([value for n, value in data_by_importance['extra']])
0173 #extra_data.append('</div>')
0174 extra_data_text = self.format_combine_lines(extra_data)
0175 text = self.format_combine_lines(lines)
0176 if self.include_reusable:
0177 return str(error_css + hide_display_js + text), extra_data
0178 else:
0179 # Usually because another error is already on this page,
0180 # and so the js & CSS are unneeded
0181 return text, extra_data
0182
0183
0184class InvalidTemplate(Exception):
0185 pass
0186
0187
0188class PylonsEvalException(EvalException):
0189
0190 def __init__(self, application, global_conf=None, xmlhttp_key=None,
0191 error_template=error_template, **errorparams):
0192 self.application = application
0193 self.error_template=error_template
0194 self.debug_infos = {}
0195 if xmlhttp_key is None:
0196 if global_conf is None:
0197 xmlhttp_key = '_'
0198 else:
0199 xmlhttp_key = global_conf.get('xmlhttp_key', '_')
0200 self.xmlhttp_key = xmlhttp_key
0201 self.errorparams = errorparams
0202 self.errorparams['debug_mode'] = self.errorparams['debug']
0203 del self.errorparams['debug']
0204
0205 for s in ['head','traceback_data','extra_data','template_data']:
0206 if "%("+s+")s" not in self.error_template:
0207 raise InvalidTemplate("Could not find %s in template"%("%("+s+")s"))
0208 try:
0209 error_template%{'head': '',
0210 'traceback_data': '',
0211 'extra_data':'',
0212 'template_data':'',
0213 'set_tab':'',
0214 'prefix':''}
0215 except:
0216 raise Exception('Invalid template. Please ensure all % signs are properly '
0217 'quoted as %% and no extra substitution strings are present.')
0218
0219 def media(self, environ, start_response):
0220 """Handle Pylons specific media content @ /_debug/media/pylons"""
0221 if environ['PATH_INFO'].startswith('/pylons'):
0222 request.path_info_pop(environ)
0223 app = urlparser.StaticURLParser(media_path)
0224 return app(environ, start_response)
0225 else:
0226 return super(self.__class__, self).media(environ, start_response)
0227 media.exposed = True
0228
0229 def respond(self, environ, start_response):
0230 if environ.get('paste.throw_errors'):
0231 return self.application(environ, start_response)
0232 base_path = request.construct_url(environ, with_path_info=False,
0233 with_query_string=False)
0234 environ['paste.throw_errors'] = True
0235 started = []
0236 def detect_start_response(status, headers, exc_info=None):
0237 try:
0238 return start_response(status, headers, exc_info)
0239 except:
0240 raise
0241 else:
0242 started.append(True)
0243 try:
0244 __traceback_supplement__ = Supplement, self, environ
0245 app_iter = self.application(environ, detect_start_response)
0246 try:
0247 return_iter = list(app_iter)
0248 return return_iter
0249 finally:
0250 if hasattr(app_iter, 'close'):
0251 app_iter.close()
0252 except:
0253 exc_info = sys.exc_info()
0254 for expected in environ.get('paste.expected_exceptions', []):
0255 if isinstance(exc_info[1], expected):
0256 raise
0257
0258 # Tell the Registry to save its StackedObjectProxies current state
0259 # for later restoration
0260 registry.restorer.save_registry_state(environ)
0261
0262 count = get_debug_count(environ)
0263 view_uri = self.make_view_url(environ, base_path, count)
0264 if not started:
0265 headers = [('content-type', 'text/html')]
0266 headers.append(('X-Debug-URL', view_uri))
0267 start_response('500 Internal Server Error',
0268 headers,
0269 exc_info)
0270 environ['wsgi.errors'].write('Debug at: %s\n' % view_uri)
0271
0272 exc_data = collector.collect_exception(*exc_info)
0273 #debug_info = PylonsDebugInfo(count, exc_info, exc_data, base_path,
0274 debug_info = PylonsDebugInfo(count, exc_info, exc_data,
0275 get_prefix(environ, warn=False),
0276 environ, view_uri, error_template)
0277 assert count not in self.debug_infos
0278 self.debug_infos[count] = debug_info
0279
0280 if self.xmlhttp_key:
0281 get_vars = wsgilib.parse_querystring(environ)
0282 if dict(get_vars).get(self.xmlhttp_key):
0283 exc_data = collector.collect_exception(*exc_info)
0284 html = formatter.format_html(
0285 exc_data, include_hidden_frames=False,
0286 include_reusable=False, show_extra_data=False)
0287 return [html]
0288
0289 # @@: it would be nice to deal with bad content types here
0290 return debug_info.content()
0291
0292
0293def myghty_html_data(exc_value):
0294 if hasattr(exc_value, 'htmlformat'):
0295 return exc_value.htmlformat()[333:-14]
0296 if hasattr(exc_value, 'mtrace'):
0297 return exc_value.mtrace.htmlformat()[333:-14]
0298
0299
0300template_error_formatters = [myghty_html_data]
0301
0302
0303try:
0304 import mako.exceptions
0305except ImportError:
0306 pass
0307else:
0308 def mako_html_data(exc_value):
0309 if isinstance(exc_value, (mako.exceptions.CompileException, mako.exceptions.SyntaxException)):
0310 return mako.exceptions.html_error_template().render(full=False,
0311 css=False)
0312
0313 template_error_formatters.insert(0,mako_html_data)
0314
0315
0316class PylonsDebugInfo(DebugInfo):
0317 def __init__(self, counter, exc_info, exc_data, base_path,
0318 environ, view_uri, error_template):
0319 DebugInfo.__init__(self, counter, exc_info, exc_data, base_path,
0320 environ, view_uri)
0321 self.error_template = error_template
0322
0323 def content(self):
0324 html, extra_data = format_eval_html(self.exc_data, self.base_path, self.counter)
0325 head_html = (formatter.error_css + formatter.hide_display_js)
0326 head_html += self.eval_javascript(self.counter)
0327 repost_button = make_repost_button(self.environ)
0328 template_data = '<p>No Template information available.</p>'
0329 tab = 'traceback_data'
0330
0331 for formatter_ in template_error_formatters:
0332 result = formatter_(self.exc_value)
0333 if result:
0334 tab = 'template_data'
0335 template_data = result
0336 break
0337
0338 head_html = (error_head_template % {'prefix':self.base_path}) + head_html
0339
0340 traceback_data = error_traceback_template % {
0341 'prefix':self.base_path,
0342 'body':html,
0343 'repost_button': repost_button or '',
0344 }
0345
0346 extra_data = """<h1 class="first"><a name="content"></a>Extra Data</h1>""" + '\n'.join(extra_data)
0348 page = self.error_template % {
0349 'head': head_html,
0350 'traceback_data': traceback_data,
0351 'extra_data':extra_data,
0352 'template_data':template_data.replace('<h2>',
0353 '<h1 class="first">').replace('</h2>',
0354 '</h1>'),
0355 'set_tab':tab,
0356 'prefix':self.base_path,
0357 }
0358 return [page]
0359
0360 def eval_javascript(self, counter):
0361 base_path = self.base_path + '/_debug'
0362 return (
0363 '<script type="text/javascript" src="%s/mochikit/MochiKit.js">'
0364 '</script>\n'
0365 '<script type="text/javascript" src="%s/media/debug.js">'
0366 '</script>\n'
0367 '<script type="text/javascript">\n'
0368 'debug_base = %r;\n'
0369 'debug_count = %r;\n'
0370 '</script>\n'
0371 % (base_path, base_path, base_path, counter))
0372
0373
0374class EvalHTMLFormatter(HTMLFormatter):
0375
0376 def __init__(self, base_path, counter, **kw):
0377 super(EvalHTMLFormatter, self).__init__(**kw)
0378 self.base_path = base_path
0379 self.counter = counter
0380
0381 def format_source_line(self, filename, frame):
0382 line = formatter.HTMLFormatter.format_source_line(
0383 self, filename, frame)
0384 return (line +
0385 ' <a href="#" class="switch_source" '
0386 'tbid="%s" onClick="return showFrame(this)"> '
0387 '<img src="%s/_debug/media/pylons/img/plus.jpg" border=0 width=9 '
0388 'height=9> </a>'
0389 % (frame.tbid, self.base_path))
0390
0391
0392def format_eval_html(exc_data, base_path, counter):
0393 short_formatter = EvalHTMLFormatter(
0394 base_path=base_path,
0395 counter=counter,
0396 include_reusable=False)
0397 short_er, extra_data = short_formatter.format_collected_data(exc_data)
0398 short_text_er = formatter.format_text(exc_data, show_extra_data=False)
0399 long_formatter = EvalHTMLFormatter(
0400 base_path=base_path,
0401 counter=counter,
0402 show_hidden_frames=True,
0403 show_extra_data=False,
0404 include_reusable=False)
0405 long_er, extra_data_none = long_formatter