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