Home | History | Annotate | Download | only in Lib
      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('&nbsp;' * 5) + '&nbsp;</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>&nbsp;</big>', link, call)]
    141         if index is not None:
    142             i = lnum - index
    143             for line in lines:
    144                 num = small('&nbsp;' * (5-len(str(i))) + str(i)) + '&nbsp;'
    145                 if i in highlight:
    146                     line = '<tt>=&gt;%s%s</tt>' % (num, pydoc.html.preformat(line))
    147                     rows.append('<tr><td bgcolor="#ffccee">%s</td></tr>' % line)
    148                 else:
    149                     line = '<tt>&nbsp;&nbsp;%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&nbsp;= %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&nbsp;=\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('&', '&amp;').replace('<', '&lt;')
    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             try:
    299                 file = os.fdopen(fd, 'w')
    300                 file.write(doc)
    301                 file.close()
    302                 msg = '<p> %s contains the description of this error.' % path
    303             except:
    304                 msg = '<p> Tried to save traceback to %s, but failed.' % path
    305             self.file.write(msg + '\n')
    306         try:
    307             self.file.flush()
    308         except: pass
    309 
    310 handler = Hook().handle
    311 def enable(display=1, logdir=None, context=5, format="html"):
    312     """Install an exception handler that formats tracebacks as HTML.
    313 
    314     The optional argument 'display' can be set to 0 to suppress sending the
    315     traceback to the browser, and 'logdir' can be set to a directory to cause
    316     tracebacks to be written to files there."""
    317     sys.excepthook = Hook(display=display, logdir=logdir,
    318                           context=context, format=format)
    319