cgitb.py revision 83205972a2d027517ebedd827d1c19a5b0264c0a
16b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee"""Handle exceptions in CGI scripts by formatting tracebacks into nice HTML. 26b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee 36b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping YeeTo enable this module, do: 46b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee 56b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee import cgitb; cgitb.enable() 66b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee 76b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yeeat the top of your CGI script. The optional arguments to enable() are: 86b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee 96b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee display - if true, tracebacks are displayed in the web browser 106b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee logdir - if set, tracebacks are written to files in this directory 1183205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee context - number of lines of source code to show for each stack frame 126b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee 1383205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping YeeBy default, tracebacks are displayed but not saved, and context is 5. 146b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee 156b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping YeeAlternatively, if you have caught an exception and want cgitb to display it 166b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yeefor you, call cgitb.handle(). The optional argument to handle() is a 3-item 176b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yeetuple (etype, evalue, etb) just like the value of sys.exc_info().""" 186b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee 196b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee__author__ = 'Ka-Ping Yee' 206b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee__version__ = '$Revision$' 216b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee 226b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yeedef reset(): 236b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee """Return a string that resets the CGI and browser to a known state.""" 246b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee return '''<!--: spam 256b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping YeeContent-Type: text/html 266b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee 2783205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee<body bgcolor="#f0f0f8"><font color="#f0f0f8" size="-5"> --> 2883205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee<body bgcolor="#f0f0f8"><font color="#f0f0f8" size="-5"> --> --> 296b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee</font> </font> </font> </script> </object> </blockquote> </pre> 306b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee</table> </table> </table> </table> </table> </font> </font> </font>''' 316b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee 3283205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee__UNDEF__ = [] # a special sentinel object 3383205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yeedef small(text): return '<small>' + text + '</small>' 3483205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yeedef strong(text): return '<strong>' + text + '</strong>' 3583205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yeedef grey(text): return '<font color="#909090">' + text + '</font>' 3683205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee 3783205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yeedef lookup(name, frame, locals): 3883205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee """Find the value for a given name in the given environment.""" 3983205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee if name in locals: 4083205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee return 'local', locals[name] 4183205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee if name in frame.f_globals: 4283205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee return 'global', frame.f_globals[name] 4383205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee return None, __UNDEF__ 4483205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee 4583205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yeedef scanvars(reader, frame, locals): 4683205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee """Scan one logical line of Python and look up values of variables used.""" 4783205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee import tokenize, keyword 4883205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee vars, lasttoken, parent, prefix = [], None, None, '' 4983205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee for ttype, token, start, end, line in tokenize.generate_tokens(reader): 5083205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee if ttype == tokenize.NEWLINE: break 5183205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee if ttype == tokenize.NAME and token not in keyword.kwlist: 5283205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee if lasttoken == '.': 5383205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee if parent is not __UNDEF__: 5483205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee value = getattr(parent, token, __UNDEF__) 5583205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee vars.append((prefix + token, prefix, value)) 5683205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee else: 5783205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee where, value = lookup(token, frame, locals) 5883205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee vars.append((token, where, value)) 5983205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee elif token == '.': 6083205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee prefix += lasttoken + '.' 6183205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee parent = value 6283205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee else: 6383205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee parent, prefix = None, '' 6483205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee lasttoken = token 6583205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee return vars 6683205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee 6783205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yeedef html((etype, evalue, etb), context=5): 6883205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee """Return a nice HTML document describing a given traceback.""" 6983205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee import sys, os, types, time, traceback, linecache, inspect, pydoc 706b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee 716b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee if type(etype) is types.ClassType: 726b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee etype = etype.__name__ 736b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable 746b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee date = time.ctime(time.time()) 7583205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee head = '<body bgcolor="#f0f0f8">' + pydoc.html.heading( 766b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee '<big><big><strong>%s</strong></big></big>' % str(etype), 7783205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee '#ffffff', '#6622aa', pyver + '<br>' + date) + ''' 7883205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee<p>A problem occurred in a Python script. Here is the sequence of 7983205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yeefunction calls leading up to the error, in the order they occurred.''' 806b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee 8183205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee indent = '<tt>' + small(' ' * 5) + ' </tt>' 826b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee frames = [] 836b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee records = inspect.getinnerframes(etb, context) 846b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee for frame, file, lnum, func, lines, index in records: 856b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee file = file and os.path.abspath(file) or '?' 8683205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee link = '<a href="file://%s">%s</a>' % (file, pydoc.html.escape(file)) 876b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee args, varargs, varkw, locals = inspect.getargvalues(frame) 8883205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee call = '' 8983205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee if func != '?': 9083205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee call = 'in ' + strong(func) + \ 9183205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee inspect.formatargvalues(args, varargs, varkw, locals, 9283205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee formatvalue=lambda value: '=' + pydoc.html.repr(value)) 9383205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee 9483205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee highlight = {} 9583205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee def reader(lnum=[lnum]): 9683205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee highlight[lnum[0]] = 1 9783205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee try: return linecache.getline(file, lnum[0]) 9883205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee finally: lnum[0] += 1 9983205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee vars = scanvars(reader, frame, locals) 10083205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee 10183205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee rows = ['<tr><td bgcolor="#d8bbff">%s%s %s</td></tr>' % 10283205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee ('<big> </big>', link, call)] 1036b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee if index is not None: 1046b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee i = lnum - index 1056b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee for line in lines: 10683205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee num = small(' ' * (5-len(str(i))) + str(i)) + ' ' 10783205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee line = '<tt>%s%s</tt>' % (num, pydoc.html.preformat(line)) 10883205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee if i in highlight: 10983205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee rows.append('<tr><td bgcolor="#ffccee">%s</td></tr>' % line) 11083205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee else: 11183205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee rows.append('<tr><td>%s</td></tr>' % grey(line)) 11283205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee i += 1 11383205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee 11483205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee done, dump = {}, [] 11583205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee for name, where, value in vars: 11683205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee if name in done: continue 11783205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee done[name] = 1 11883205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee if value is not __UNDEF__: 11983205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee if where == 'global': name = '<em>global</em> ' + strong(name) 12083205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee elif where == 'local': name = strong(name) 12183205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee else: name = where + strong(name.split('.')[-1]) 12283205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee dump.append('%s = %s' % (name, pydoc.html.repr(value))) 12383205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee else: 12483205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee dump.append(name + ' <em>undefined</em>') 12583205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee 12683205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee rows.append('<tr><td>%s</td></tr>' % small(grey(', '.join(dump)))) 12783205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee frames.append('''<p> 12883205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee<table width="100%%" cellspacing=0 cellpadding=0 border=0> 12983205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee%s</table>''' % '\n'.join(rows)) 13083205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee 13183205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee exception = ['<p>%s: %s' % (strong(str(etype)), str(evalue))] 1326b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee if type(evalue) is types.InstanceType: 1336b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee for name in dir(evalue): 1346b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee value = pydoc.html.repr(getattr(evalue, name)) 1356b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee exception.append('\n<br>%s%s =\n%s' % (indent, name, value)) 1366b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee 1376b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee import traceback 1386b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee return head + ''.join(frames) + ''.join(exception) + ''' 1396b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee 1406b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee 14183205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee<!-- The above is a description of an error in a Python program, formatted 14283205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee for a Web browser because the 'cgitb' module was enabled. In case you 14383205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee are not reading this in a Web browser, here is the original traceback: 1446b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee 1456b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee%s 1466b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee--> 14783205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee''' % ''.join(traceback.format_exception(etype, evalue, etb)) 1486b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee 1496b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yeeclass Hook: 15083205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee """A hook to replace sys.excepthook that shows tracebacks in HTML.""" 15183205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee 15283205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee def __init__(self, display=1, logdir=None, context=5): 1536b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee self.display = display # send tracebacks to browser if true 1546b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee self.logdir = logdir # log tracebacks to files if not None 15583205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee self.context = context # number of source code lines per frame 1566b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee 1576b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee def __call__(self, etype, evalue, etb): 1586b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee self.handle((etype, evalue, etb)) 1596b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee 1606b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee def handle(self, info=None): 16183205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee import sys 1626b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee info = info or sys.exc_info() 1636b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee print reset() 1646b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee 1656b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee try: 16683205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee text, doc = 0, html(info, self.context) 1676b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee except: # just in case something goes wrong 1686b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee import traceback 16983205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee text, doc = 1, ''.join(traceback.format_exception(*info)) 1706b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee 1716b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee if self.display: 1726b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee if text: 1736b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee doc = doc.replace('&', '&').replace('<', '<') 17483205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee print '<pre>' + doc + '</pre>' 1756b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee else: 1766b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee print doc 1776b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee else: 1786b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee print '<p>A problem occurred in a Python script.' 1796b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee 1806b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee if self.logdir is not None: 18183205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee import os, tempfile 1826b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee name = tempfile.mktemp(['.html', '.txt'][text]) 1836b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee path = os.path.join(self.logdir, os.path.basename(name)) 1846b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee try: 1856b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee file = open(path, 'w') 1866b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee file.write(doc) 1876b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee file.close() 18883205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee print '<p> %s contains the description of this error.' % path 1896b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee except: 19083205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee print '<p> Tried to save traceback to %s, but failed.' % path 1916b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee 1926b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yeehandler = Hook().handle 19383205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yeedef enable(display=1, logdir=None, context=5): 19483205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee """Install an exception handler that formats tracebacks as HTML. 19583205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee 19683205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee The optional argument 'display' can be set to 0 to suppress sending the 19783205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee traceback to the browser, and 'logdir' can be set to a directory to cause 19883205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee tracebacks to be written to files there.""" 1996b5a48d48ea15c1364b2fbc8552f50ebf72fa64aKa-Ping Yee import sys 20083205972a2d027517ebedd827d1c19a5b0264c0aKa-Ping Yee sys.excepthook = Hook(display, logdir, context) 201