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