• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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('&nbsp;' * 5) + '&nbsp;</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>&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    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&nbsp;=\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