PageRenderTime 43ms CodeModel.GetById 16ms app.highlight 23ms RepoModel.GetById 1ms app.codeStats 0ms

/Lib/cgitb.py

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