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(pydoc.html.escape(func)) 128 if func != "<module>": 129 call += 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 for name in dir(evalue): 176 if name[:1] == '_': continue 177 value = pydoc.html.repr(getattr(evalue, name)) 178 exception.append('\n<br>%s%s =\n%s' % (indent, name, value)) 179 180 return head + ''.join(frames) + ''.join(exception) + ''' 181 182 183<!-- The above is a description of an error in a Python program, formatted 184 for a Web browser because the 'cgitb' module was enabled. In case you 185 are not reading this in a Web browser, here is the original traceback: 186 187%s 188--> 189''' % pydoc.html.escape( 190 ''.join(traceback.format_exception(etype, evalue, etb))) 191 192def text(einfo, context=5): 193 """Return a plain text document describing a given traceback.""" 194 etype, evalue, etb = einfo 195 if isinstance(etype, type): 196 etype = etype.__name__ 197 pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable 198 date = time.ctime(time.time()) 199 head = "%s\n%s\n%s\n" % (str(etype), pyver, date) + ''' 200A problem occurred in a Python script. Here is the sequence of 201function calls leading up to the error, in the order they occurred. 202''' 203 204 frames = [] 205 records = inspect.getinnerframes(etb, context) 206 for frame, file, lnum, func, lines, index in records: 207 file = file and os.path.abspath(file) or '?' 208 args, varargs, varkw, locals = inspect.getargvalues(frame) 209 call = '' 210 if func != '?': 211 call = 'in ' + func 212 if func != "<module>": 213 call += 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 for name in dir(evalue): 247 value = pydoc.text.repr(getattr(evalue, name)) 248 exception.append('\n%s%s = %s' % (" "*4, name, value)) 249 250 return head + ''.join(frames) + ''.join(exception) + ''' 251 252The above is a description of an error in a Python program. Here is 253the original traceback: 254 255%s 256''' % ''.join(traceback.format_exception(etype, evalue, etb)) 257 258class Hook: 259 """A hook to replace sys.excepthook that shows tracebacks in HTML.""" 260 261 def __init__(self, display=1, logdir=None, context=5, file=None, 262 format="html"): 263 self.display = display # send tracebacks to browser if true 264 self.logdir = logdir # log tracebacks to files if not None 265 self.context = context # number of source code lines per frame 266 self.file = file or sys.stdout # place to send the output 267 self.format = format 268 269 def __call__(self, etype, evalue, etb): 270 self.handle((etype, evalue, etb)) 271 272 def handle(self, info=None): 273 info = info or sys.exc_info() 274 if self.format == "html": 275 self.file.write(reset()) 276 277 formatter = (self.format=="html") and html or text 278 plain = False 279 try: 280 doc = formatter(info, self.context) 281 except: # just in case something goes wrong 282 doc = ''.join(traceback.format_exception(*info)) 283 plain = True 284 285 if self.display: 286 if plain: 287 doc = pydoc.html.escape(doc) 288 self.file.write('<pre>' + doc + '</pre>\n') 289 else: 290 self.file.write(doc + '\n') 291 else: 292 self.file.write('<p>A problem occurred in a Python script.\n') 293 294 if self.logdir is not None: 295 suffix = ['.txt', '.html'][self.format=="html"] 296 (fd, path) = tempfile.mkstemp(suffix=suffix, dir=self.logdir) 297 298 try: 299 with os.fdopen(fd, 'w') as file: 300 file.write(doc) 301 msg = '%s contains the description of this error.' % path 302 except: 303 msg = 'Tried to save traceback to %s, but failed.' % path 304 305 if self.format == 'html': 306 self.file.write('<p>%s</p>\n' % msg) 307 else: 308 self.file.write(msg + '\n') 309 try: 310 self.file.flush() 311 except: pass 312 313handler = Hook().handle 314def enable(display=1, logdir=None, context=5, format="html"): 315 """Install an exception handler that formats tracebacks as HTML. 316 317 The optional argument 'display' can be set to 0 to suppress sending the 318 traceback to the browser, and 'logdir' can be set to a directory to cause 319 tracebacks to be written to files there.""" 320 sys.excepthook = Hook(display=display, logdir=logdir, 321 context=context, format=format) 322