1"""More comprehensive traceback formatting for Python scripts. 2 3To enable this module, do: 4 5 import cgitb; cgitb.enable() 6 7at the top of your script. The optional arguments to enable() are: 8 9 display - if true, tracebacks are displayed in the web browser 10 logdir - if set, tracebacks are written to files in this directory 11 context - number of lines of source code to show for each stack frame 12 format - 'text' or 'html' controls the output format 13 14By default, tracebacks are displayed but not saved, the context is 5 lines 15and the output format is 'html' (for backwards compatibility with the 16original use of this module) 17 18Alternatively, if you have caught an exception and want cgitb to display it 19for you, call cgitb.handler(). The optional argument to handler() is a 203-item tuple (etype, evalue, etb) just like the value of sys.exc_info(). 21The default handler displays output as HTML. 22 23""" 24import inspect 25import keyword 26import linecache 27import os 28import pydoc 29import sys 30import tempfile 31import time 32import tokenize 33import traceback 34 35def reset(): 36 """Return a string that resets the CGI and browser to a known state.""" 37 return '''<!--: spam 38Content-Type: text/html 39 40<body bgcolor="#f0f0f8"><font color="#f0f0f8" size="-5"> --> 41<body bgcolor="#f0f0f8"><font color="#f0f0f8" size="-5"> --> --> 42</font> </font> </font> </script> </object> </blockquote> </pre> 43</table> </table> </table> </table> </table> </font> </font> </font>''' 44 45__UNDEF__ = [] # a special sentinel object 46def small(text): 47 if text: 48 return '<small>' + text + '</small>' 49 else: 50 return '' 51 52def strong(text): 53 if text: 54 return '<strong>' + text + '</strong>' 55 else: 56 return '' 57 58def grey(text): 59 if text: 60 return '<font color="#909090">' + text + '</font>' 61 else: 62 return '' 63 64def lookup(name, frame, locals): 65 """Find the value for a given name in the given environment.""" 66 if name in locals: 67 return 'local', locals[name] 68 if name in frame.f_globals: 69 return 'global', frame.f_globals[name] 70 if '__builtins__' in frame.f_globals: 71 builtins = frame.f_globals['__builtins__'] 72 if type(builtins) is type({}): 73 if name in builtins: 74 return 'builtin', builtins[name] 75 else: 76 if hasattr(builtins, name): 77 return 'builtin', getattr(builtins, name) 78 return None, __UNDEF__ 79 80def scanvars(reader, frame, locals): 81 """Scan one logical line of Python and look up values of variables used.""" 82 vars, lasttoken, parent, prefix, value = [], None, None, '', __UNDEF__ 83 for ttype, token, start, end, line in tokenize.generate_tokens(reader): 84 if ttype == tokenize.NEWLINE: break 85 if ttype == tokenize.NAME and token not in keyword.kwlist: 86 if lasttoken == '.': 87 if parent is not __UNDEF__: 88 value = getattr(parent, token, __UNDEF__) 89 vars.append((prefix + token, prefix, value)) 90 else: 91 where, value = lookup(token, frame, locals) 92 vars.append((token, where, value)) 93 elif token == '.': 94 prefix += lasttoken + '.' 95 parent = value 96 else: 97 parent, prefix = None, '' 98 lasttoken = token 99 return vars 100 101def html(einfo, context=5): 102 """Return a nice HTML document describing a given traceback.""" 103 etype, evalue, etb = einfo 104 if isinstance(etype, type): 105 etype = etype.__name__ 106 pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable 107 date = time.ctime(time.time()) 108 head = '<body bgcolor="#f0f0f8">' + pydoc.html.heading( 109 '<big><big>%s</big></big>' % 110 strong(pydoc.html.escape(str(etype))), 111 '#ffffff', '#6622aa', pyver + '<br>' + date) + ''' 112<p>A problem occurred in a Python script. Here is the sequence of 113function calls leading up to the error, in the order they occurred.</p>''' 114 115 indent = '<tt>' + small(' ' * 5) + ' </tt>' 116 frames = [] 117 records = inspect.getinnerframes(etb, context) 118 for frame, file, lnum, func, lines, index in records: 119 if file: 120 file = os.path.abspath(file) 121 link = '<a href="file://%s">%s</a>' % (file, pydoc.html.escape(file)) 122 else: 123 file = link = '?' 124 args, varargs, varkw, locals = inspect.getargvalues(frame) 125 call = '' 126 if func != '?': 127 call = 'in ' + strong(func) + \ 128 inspect.formatargvalues(args, varargs, varkw, locals, 129 formatvalue=lambda value: '=' + pydoc.html.repr(value)) 130 131 highlight = {} 132 def reader(lnum=[lnum]): 133 highlight[lnum[0]] = 1 134 try: return linecache.getline(file, lnum[0]) 135 finally: lnum[0] += 1 136 vars = scanvars(reader, frame, locals) 137 138 rows = ['<tr><td bgcolor="#d8bbff">%s%s %s</td></tr>' % 139 ('<big> </big>', link, call)] 140 if index is not None: 141 i = lnum - index 142 for line in lines: 143 num = small(' ' * (5-len(str(i))) + str(i)) + ' ' 144 if i in highlight: 145 line = '<tt>=>%s%s</tt>' % (num, pydoc.html.preformat(line)) 146 rows.append('<tr><td bgcolor="#ffccee">%s</td></tr>' % line) 147 else: 148 line = '<tt> %s%s</tt>' % (num, pydoc.html.preformat(line)) 149 rows.append('<tr><td>%s</td></tr>' % grey(line)) 150 i += 1 151 152 done, dump = {}, [] 153 for name, where, value in vars: 154 if name in done: continue 155 done[name] = 1 156 if value is not __UNDEF__: 157 if where in ('global', 'builtin'): 158 name = ('<em>%s</em> ' % where) + strong(name) 159 elif where == 'local': 160 name = strong(name) 161 else: 162 name = where + strong(name.split('.')[-1]) 163 dump.append('%s = %s' % (name, pydoc.html.repr(value))) 164 else: 165 dump.append(name + ' <em>undefined</em>') 166 167 rows.append('<tr><td>%s</td></tr>' % small(grey(', '.join(dump)))) 168 frames.append(''' 169<table width="100%%" cellspacing=0 cellpadding=0 border=0> 170%s</table>''' % '\n'.join(rows)) 171 172 exception = ['<p>%s: %s' % (strong(pydoc.html.escape(str(etype))), 173 pydoc.html.escape(str(evalue)))] 174 for name in dir(evalue): 175 if name[:1] == '_': continue 176 value = pydoc.html.repr(getattr(evalue, name)) 177 exception.append('\n<br>%s%s =\n%s' % (indent, name, value)) 178 179 return head + ''.join(frames) + ''.join(exception) + ''' 180 181 182<!-- The above is a description of an error in a Python program, formatted 183 for a Web browser because the 'cgitb' module was enabled. In case you 184 are not reading this in a Web browser, here is the original traceback: 185 186%s 187--> 188''' % pydoc.html.escape( 189 ''.join(traceback.format_exception(etype, evalue, etb))) 190 191def text(einfo, context=5): 192 """Return a plain text document describing a given traceback.""" 193 etype, evalue, etb = einfo 194 if isinstance(etype, type): 195 etype = etype.__name__ 196 pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable 197 date = time.ctime(time.time()) 198 head = "%s\n%s\n%s\n" % (str(etype), pyver, date) + ''' 199A problem occurred in a Python script. Here is the sequence of 200function calls leading up to the error, in the order they occurred. 201''' 202 203 frames = [] 204 records = inspect.getinnerframes(etb, context) 205 for frame, file, lnum, func, lines, index in records: 206 file = file and os.path.abspath(file) or '?' 207 args, varargs, varkw, locals = inspect.getargvalues(frame) 208 call = '' 209 if func != '?': 210 call = 'in ' + func + \ 211 inspect.formatargvalues(args, varargs, varkw, locals, 212 formatvalue=lambda value: '=' + pydoc.text.repr(value)) 213 214 highlight = {} 215 def reader(lnum=[lnum]): 216 highlight[lnum[0]] = 1 217 try: return linecache.getline(file, lnum[0]) 218 finally: lnum[0] += 1 219 vars = scanvars(reader, frame, locals) 220 221 rows = [' %s %s' % (file, call)] 222 if index is not None: 223 i = lnum - index 224 for line in lines: 225 num = '%5d ' % i 226 rows.append(num+line.rstrip()) 227 i += 1 228 229 done, dump = {}, [] 230 for name, where, value in vars: 231 if name in done: continue 232 done[name] = 1 233 if value is not __UNDEF__: 234 if where == 'global': name = 'global ' + name 235 elif where != 'local': name = where + name.split('.')[-1] 236 dump.append('%s = %s' % (name, pydoc.text.repr(value))) 237 else: 238 dump.append(name + ' undefined') 239 240 rows.append('\n'.join(dump)) 241 frames.append('\n%s\n' % '\n'.join(rows)) 242 243 exception = ['%s: %s' % (str(etype), str(evalue))] 244 for name in dir(evalue): 245 value = pydoc.text.repr(getattr(evalue, name)) 246 exception.append('\n%s%s = %s' % (" "*4, name, value)) 247 248 return head + ''.join(frames) + ''.join(exception) + ''' 249 250The above is a description of an error in a Python program. Here is 251the original traceback: 252 253%s 254''' % ''.join(traceback.format_exception(etype, evalue, etb)) 255 256class Hook: 257 """A hook to replace sys.excepthook that shows tracebacks in HTML.""" 258 259 def __init__(self, display=1, logdir=None, context=5, file=None, 260 format="html"): 261 self.display = display # send tracebacks to browser if true 262 self.logdir = logdir # log tracebacks to files if not None 263 self.context = context # number of source code lines per frame 264 self.file = file or sys.stdout # place to send the output 265 self.format = format 266 267 def __call__(self, etype, evalue, etb): 268 self.handle((etype, evalue, etb)) 269 270 def handle(self, info=None): 271 info = info or sys.exc_info() 272 if self.format == "html": 273 self.file.write(reset()) 274 275 formatter = (self.format=="html") and html or text 276 plain = False 277 try: 278 doc = formatter(info, self.context) 279 except: # just in case something goes wrong 280 doc = ''.join(traceback.format_exception(*info)) 281 plain = True 282 283 if self.display: 284 if plain: 285 doc = doc.replace('&', '&').replace('<', '<') 286 self.file.write('<pre>' + doc + '</pre>\n') 287 else: 288 self.file.write(doc + '\n') 289 else: 290 self.file.write('<p>A problem occurred in a Python script.\n') 291 292 if self.logdir is not None: 293 suffix = ['.txt', '.html'][self.format=="html"] 294 (fd, path) = tempfile.mkstemp(suffix=suffix, dir=self.logdir) 295 296 try: 297 with os.fdopen(fd, 'w') as file: 298 file.write(doc) 299 msg = '%s contains the description of this error.' % path 300 except: 301 msg = 'Tried to save traceback to %s, but failed.' % path 302 303 if self.format == 'html': 304 self.file.write('<p>%s</p>\n' % msg) 305 else: 306 self.file.write(msg + '\n') 307 try: 308 self.file.flush() 309 except: pass 310 311handler = Hook().handle 312def enable(display=1, logdir=None, context=5, format="html"): 313 """Install an exception handler that formats tracebacks as HTML. 314 315 The optional argument 'display' can be set to 0 to suppress sending the 316 traceback to the browser, and 'logdir' can be set to a directory to cause 317 tracebacks to be written to files there.""" 318 sys.excepthook = Hook(display=display, logdir=logdir, 319 context=context, format=format) 320