1
2 """
3 Debugging output heavily based on colubrid.debug
4 (http://trac.pocoo.org/repos/colubrid/trunk/colubrid/debug.py@2791)
5
6 colubrid.debug is copyright 2006 by Armin Ronacher, Benjamin Wiegand,
7 Georg Brandl and licensed under the `BSD License`_
8
9 .. _BSD License: http://www.opensource.org/licenses/bsd-license.php
10 """
11 __docformat__ = 'restructuredtext en'
12
13 try:
14 import cStringIO as _string_io
15 except ImportError:
16 import StringIO as _string_io
17 import inspect as _inspect
18 import keyword as _keyword
19 import os as _os
20 import pprint as _pprint
21 import re as _re
22 import sys as _sys
23 import token as _token
24 import tokenize as _tokenize
25 import traceback as _traceback
26 from xml.sax.saxutils import escape as _escape
27
28
29 JAVASCRIPT = r'''
30 function toggleBlock(handler) {
31 if (handler.nodeName == 'H3') {
32 var table = handler;
33 do {
34 table = table.nextSibling;
35 if (typeof table == 'undefined') {
36 return;
37 }
38 }
39 while (table.nodeName != 'TABLE');
40 }
41
42 else if (handler.nodeName == 'DT') {
43 var parent = handler.parentNode;
44 var table = parent.getElementsByTagName('TABLE')[0];
45 }
46
47 var lines = table.getElementsByTagName("TR");
48 for (var i = 0; i < lines.length; i++) {
49 var line = lines[i];
50 if (line.className == 'pre' || line.className == 'post' ||
51 line.parentNode.parentNode.className == 'vars') {
52 line.style.display = (line.style.display == 'none') ? '' : 'none';
53 }
54 }
55 }
56
57 function initTB() {
58 var tb = document.getElementById('wsgi-traceback');
59 var handlers = tb.getElementsByTagName('H3');
60 for (var i = 0; i < handlers.length; i++) {
61 toggleBlock(handlers[i]);
62 handlers[i].setAttribute('onclick', 'toggleBlock(this)');
63 }
64 handlers = tb.getElementsByTagName('DT');
65 for (var i = 0; i < handlers.length; i++) {
66 toggleBlock(handlers[i]);
67 handlers[i].setAttribute('onclick', 'toggleBlock(this)');
68 }
69 }
70
71 function change_tb() {
72 interactive = document.getElementById('interactive');
73 plain = document.getElementById('plain');
74 interactive.style.display = ((interactive.style.display == 'block') | (interactive.style.display == '')) ? 'none' : 'block';
75 plain.style.display = (plain.style.display == 'block') ? 'none' : 'block';
76 }
77 '''
78
79 STYLESHEET = '''
80 body {
81 font-size:0.9em;
82 margin: 0;
83 padding: 1.3em;
84 }
85
86 * {
87 margin:0;
88 padding:0;
89 }
90
91 #wsgi-traceback {
92 margin: 1em;
93 border: 1px solid #5F9CC4;
94 background-color: #F6F6F6;
95 }
96
97 h1 {
98 background-color: #3F7CA4;
99 font-size:1.2em;
100 color:#FFFFFF;
101 padding:0.3em;
102 margin:0 0 0.2em 0;
103 }
104
105 h2 {
106 background-color:#5F9CC4;
107 font-size:1em;
108 color:#FFFFFF;
109 padding:0.3em;
110 margin:0.4em 0 0.2em 0;
111 }
112
113 h2.tb {
114 cursor:pointer;
115 }
116
117 h3 {
118 font-size:1em;
119 cursor:pointer;
120 }
121
122 h3.fn {
123 margin-top: 0.5em;
124 padding: 0.3em;
125 }
126
127 h3.fn:hover {
128 color: #777;
129 }
130
131 h3.indent {
132 margin:0 0.7em 0 0.7em;
133 font-weight:normal;
134 }
135
136 p.text {
137 padding:0.1em 0.5em 0.1em 0.5em;
138 }
139
140 p.errormsg {
141 padding:0.1em 0.5em 0.1em 0.5em;
142 }
143
144 p.errorline {
145 padding:0.1em 0.5em 0.1em 2em;
146 font-size: 0.9em;
147 }
148
149 div.frame {
150 margin: 0 2em 0 1em;
151 }
152
153 table.code {
154 margin: 0.4em 0 0 0.5em;
155 background-color:#E0E0E0;
156 width:100%;
157 font-family: monospace;
158 font-size:13px;
159 border:1px solid #C9C9C9;
160 border-collapse:collapse;
161 }
162
163 table.code td.lineno {
164 width:42px;
165 text-align:right;
166 padding:0 5px 0 0;
167 color:#444444;
168 font-weight:bold;
169 border-right:1px solid #888888;
170 }
171
172 table.code td.code {
173 background-color:#EFEFEF;
174 padding:1px 0 1px 5px;
175 white-space:pre;
176 }
177
178 table.code tr.cur td.code {
179 background-color: #fff;
180 border-top: 1px solid #ccc;
181 border-bottom: 1px solid #ccc;
182 white-space: pre;
183 }
184
185 pre.plain {
186 margin:0.5em 1em 1em 1em;
187 padding:0.5em;
188 border:1px solid #999999;
189 background-color: #FFFFFF;
190 font-family: monospace;
191 font-size: 13px;
192 }
193
194 table.vars {
195 margin:0 1.5em 0 1.5em;
196 border-collapse:collapse;
197 font-size: 0.9em;
198 }
199
200 table.vars td {
201 font-family: 'Bitstream Vera Sans Mono', 'Courier New', monospace;
202 padding: 0.3em;
203 border: 1px solid #ddd;
204 vertical-align: top;
205 background-color: white;
206 }
207
208 table.vars .name {
209 font-style: italic;
210 }
211
212 table.vars .value {
213 color: #555;
214 }
215
216 table.vars th {
217 padding: 0.2em;
218 border: 1px solid #ddd;
219 background-color: #f2f2f2;
220 text-align: left;
221 }
222
223 #plain {
224 display: none;
225 }
226
227 dl dt {
228 padding: 0.2em 0 0.2em 1em;
229 font-weight: bold;
230 cursor: pointer;
231 background-color: #ddd;
232 }
233
234 dl dt:hover {
235 background-color: #bbb; color: white;
236 }
237
238 dl dd {
239 padding: 0 0 0 2em;
240 background-color: #eee;
241 }
242
243 span.p-kw {
244 font-weight: bold;
245 color: #008800;
246 }
247
248 span.p-cmt {
249 color: #888888;
250 }
251
252 span.p-str {
253 color: #dd2200;
254 background-color: #fff0f0;
255 }
256
257 span.p-num {
258 color: #0000DD;
259 font-weight: bold;
260 }
261
262 span.p-op {
263 color: black;
264 }
265 '''
266
267
269 """
270 Return a dict of information about a given traceback.
271 """
272
273 lineno = tb.tb_lineno
274 function = tb.tb_frame.f_code.co_name
275 variables = tb.tb_frame.f_locals
276
277
278 fn = tb.tb_frame.f_globals.get('__file__')
279 if not fn:
280 fn = _os.path.realpath(
281 _inspect.getsourcefile(tb) or _inspect.getfile(tb)
282 )
283 if fn[-4:] in ('.pyc', '.pyo'):
284 fn = fn[:-1]
285
286
287 modname = tb.tb_frame.f_globals.get('__name__')
288
289
290 loader = tb.tb_frame.f_globals.get('__loader__')
291
292
293 try:
294 if not loader is None:
295 source = loader.get_source(modname)
296 else:
297 source = file(fn).read()
298 except (SystemExit, KeyboardInterrupt):
299 raise
300 except:
301 source = ''
302 pre_context, post_context = [], []
303 context_line, context_lineno = None, None
304 else:
305 parser = PythonParser(source)
306 parser.parse()
307 parsed_source = parser.get_html_output()
308 lbound = max(0, lineno - context_lines - 1)
309 ubound = lineno + context_lines
310 try:
311 context_line = parsed_source[lineno - 1]
312 pre_context = parsed_source[lbound:lineno - 1]
313 post_context = parsed_source[lineno:ubound]
314 except IndexError:
315 context_line = None
316 pre_context = post_context = [], []
317 context_lineno = lbound
318
319 return {
320 'tb': tb,
321 'filename': fn,
322 'loader': loader,
323 'function': function,
324 'lineno': lineno,
325 'vars': variables,
326 'pre_context': pre_context,
327 'context_line': context_line,
328 'post_context': post_context,
329 'context_lineno': context_lineno,
330 'source': source
331 }
332
333
334 -def debug_context(exc_info):
335 exception_type, exception_value, tb = exc_info
336
337 if tb.tb_next is not None:
338 tb = tb.tb_next
339 plaintb = ''.join(_traceback.format_exception(*exc_info))
340
341
342 frames = []
343
344
345 while tb is not None:
346 frames.append(get_frame_info(tb))
347 tb = tb.tb_next
348
349
350 if isinstance(exception_type, str):
351 extypestr = "string exception"
352 exception_value = exception_type
353 elif exception_type.__module__ == "exceptions":
354 extypestr = exception_type.__name__
355 else:
356 extypestr = str(exception_type)
357
358 return Namespace(
359 exception_type=extypestr,
360 exception_value=str(exception_value),
361 frames=frames,
362 last_frame=frames[-1],
363 plaintb=plaintb,
364 )
365
366
368 """
369 Return debug info for the request
370 """
371 context = debug_context(exc_info)
372 context.req_vars = sorted(environ.iteritems())
373 return DebugRender(context).render()
374
375
378 self.__dict__.update(kwds)
379
380
382 """
383 Simple python sourcecode highlighter.
384 Usage::
385
386 p = PythonParser(source)
387 p.parse()
388 for line in p.get_html_output():
389 print line
390 """
391
392 _KEYWORD = _token.NT_OFFSET + 1
393 _TEXT = _token.NT_OFFSET + 2
394 _classes = {
395 _token.NUMBER: 'num',
396 _token.OP: 'op',
397 _token.STRING: 'str',
398 _tokenize.COMMENT: 'cmt',
399 _token.NAME: 'id',
400 _token.ERRORTOKEN: 'error',
401 _KEYWORD: 'kw',
402 _TEXT: 'txt',
403 }
404
406 self.raw = raw.expandtabs(8).strip()
407 self.out = _string_io.StringIO()
408
410 self.lines = [0, 0]
411 pos = 0
412 while 1:
413 pos = self.raw.find('\n', pos) + 1
414 if not pos: break
415 self.lines.append(pos)
416 self.lines.append(len(self.raw))
417
418 self.pos = 0
419 text = _string_io.StringIO(self.raw)
420 try:
421 _tokenize.tokenize(text.readline, self)
422 except _tokenize.TokenError:
423 pass
424
426 """ Return line generator. """
427 def html_splitlines(lines):
428
429
430 open_tag_re = _re.compile(r'<(\w+)(\s.*)?[^/]?>')
431 close_tag_re = _re.compile(r'</(\w+)>')
432 open_tags = []
433 for line in lines:
434 for tag in open_tags:
435 line = tag.group(0) + line
436 open_tags = []
437 for tag in open_tag_re.finditer(line):
438 open_tags.append(tag)
439 open_tags.reverse()
440 for ctag in close_tag_re.finditer(line):
441 for otag in open_tags:
442 if otag.group(1) == ctag.group(1):
443 open_tags.remove(otag)
444 break
445 for tag in open_tags:
446 line += '</%s>' % tag.group(1)
447 yield line
448
449 return list(html_splitlines(self.out.getvalue().splitlines()))
450
451 - def __call__(self, toktype, toktext, (srow,scol), (erow,ecol), line):
452 oldpos = self.pos
453 newpos = self.lines[srow] + scol
454 self.pos = newpos + len(toktext)
455
456 if toktype in [_token.NEWLINE, _tokenize.NL]:
457 self.out.write('\n')
458 return
459
460 if newpos > oldpos:
461 self.out.write(self.raw[oldpos:newpos])
462
463 if toktype in [_token.INDENT, _token.DEDENT]:
464 self.pos = newpos
465 return
466
467 if _token.LPAR <= toktype and toktype <= _token.OP:
468 toktype = _token.OP
469 elif toktype == _token.NAME and _keyword.iskeyword(toktext):
470 toktype = self._KEYWORD
471 clsname = self._classes.get(toktype, 'txt')
472
473 self.out.write('<span class="code-item p-%s">' % clsname)
474 self.out.write(_escape(toktext))
475 self.out.write('</span>')
476
477
479
482
490
492 data = [
493 '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" '
494 '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
495 '<html xmlns="http://www.w3.org/1999/xhtml"><head>',
496 '<title>Python Traceback</title>',
497 '<script type="text/javascript">%s</script>' % JAVASCRIPT,
498 '<style type="text/css">%s</style>' % STYLESHEET,
499 '</head><body>',
500 '<div id="wsgi-traceback">'
501 ]
502
503 if hasattr(self.c, 'exception_type'):
504 title = _escape(self.c.exception_type)
505 exc = _escape(self.c.exception_value)
506 data += [
507 '<h1>%s</h1>' % title,
508 '<p class="errormsg">%s</p>' % exc
509 ]
510
511 if hasattr(self.c, 'last_frame'):
512 data += [
513 '<p class="errorline">%s in %s, line %s</p>' % (
514 self.c.last_frame['filename'], self.c.last_frame['function'],
515 self.c.last_frame['lineno'])
516 ]
517
518 return '\n'.join(data)
519
521 def render_line(mode, lineno, code):
522 return ''.join([
523 '<tr class="%s">' % mode,
524 '<td class="lineno">%i</td>' % lineno,
525 '<td class="code">%s</td></tr>' % code
526 ])
527
528 tmp = ['<table class="code">']
529 lineno = frame['context_lineno']
530 if not lineno is None:
531 lineno += 1
532 for l in frame['pre_context']:
533 tmp.append(render_line('pre', lineno, l))
534 lineno += 1
535 tmp.append(render_line('cur', lineno, frame['context_line']))
536 lineno += 1
537 for l in frame['post_context']:
538 tmp.append(render_line('post', lineno, l))
539 lineno += 1
540 else:
541 tmp.append(render_line('cur', 1, 'Sourcecode not available'))
542 tmp.append('</table>')
543
544 return '\n'.join(tmp)
545
547
548 if isinstance(var, basestring) or isinstance(var, float)\
549 or isinstance(var, int) or isinstance(var, long):
550 return ('<table class="vars"><tr><td class="value">%r'
551 '</td></tr></table>' % _escape(repr(var)))
552
553
554 if isinstance(var, dict) or hasattr(var, 'items'):
555 items = var.items()
556 items.sort()
557
558
559 if not items:
560 return ('<table class="vars"><tr><th>no data given'
561 '</th></tr></table>')
562
563 result = ['<table class="vars"><tr><th>Name'
564 '</th><th>Value</th></tr>']
565 for key, value in items:
566 try:
567 val = _escape(_pprint.pformat(value))
568 except (SystemExit, KeyboardInterrupt):
569 raise
570 except:
571 val = '?'
572 result.append('<tr><td class="name">%s</td><td class="value">%s'
573 '</td></tr>' % (_escape(repr(key)), val))
574 result.append('</table>')
575 return '\n'.join(result)
576
577
578 if isinstance(var, list):
579
580 if not var:
581 return ('<table class="vars"><tr><th>no data given'
582 '</th></tr></table>')
583
584 result = ['<table class="vars">']
585 for line in var:
586 try:
587 val = _escape(_pprint.pformat(line))
588 except (SystemExit, KeyboardInterrupt):
589 raise
590 except:
591 val = '?'
592 result.append('<tr><td class="value">%s</td></tr>' % (val))
593 result.append('</table>')
594 return '\n'.join(result)
595
596
597 try:
598 value = _escape(repr(var))
599 except (SystemExit, KeyboardInterrupt):
600 raise
601 except:
602 value = '?'
603 return '<table class="vars"><tr><th>%s</th></tr></table>' % value
604
606 if not hasattr(self.c, 'frames'):
607 return ''
608
609 result = ['<h2 onclick="change_tb()" class="tb">Traceback (click to switch to raw view)</h2>']
610 result.append('<div id="interactive"><p class="text">A problem occurred in your Python WSGI'
611 ' application. Here is the sequence of function calls leading up to'
612 ' the error, in the order they occurred. Click on a header to show'
613 ' context lines.</p>')
614
615 for num, frame in enumerate(self.c.frames):
616 line = [
617 '<div class="frame" id="frame-%i">' % num,
618 '<h3 class="fn">%s in %s</h3>' % (frame['function'],
619 frame['filename']),
620 self.render_code(frame),
621 ]
622
623 if frame['vars']:
624 line.append('\n'.join([
625 '<h3 class="indent">▸ local variables</h3>',
626 self.var_table(frame['vars'])
627 ]))
628
629 line.append('</div>')
630 result.append(''.join(line))
631 result.append('\n'.join([
632 '</div>',
633 self.plain()
634 ]))
635 return '\n'.join(result)
636
638 if not hasattr(self.c, 'plaintb'):
639 return ''
640 return '''
641 <div id="plain">
642 <p class="text">Here is the plain Python traceback for copy and paste:</p>
643 <pre class="plain">\n%s</pre>
644 </div>
645 ''' % self.c.plaintb
646
663
673