blob: 8084464fa6fec8d9feae807a7b823d9471a5544f [file] [log] [blame]
Ka-Ping Yee6b5a48d2001-08-18 04:04:50 +00001"""Handle exceptions in CGI scripts by formatting tracebacks into nice HTML.
2
3To enable this module, do:
4
5 import cgitb; cgitb.enable()
6
7at the top of your CGI 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
12By default, tracebacks are displayed but not written to files.
13
14Alternatively, if you have caught an exception and want cgitb to display it
15for you, call cgitb.handle(). The optional argument to handle() is a 3-item
16tuple (etype, evalue, etb) just like the value of sys.exc_info()."""
17
18__author__ = 'Ka-Ping Yee'
19__version__ = '$Revision$'
20
21def reset():
22 """Return a string that resets the CGI and browser to a known state."""
23 return '''<!--: spam
24Content-Type: text/html
25
26<body bgcolor="#f0f0ff"><font color="#f0f0ff" size="-5"> -->
27<body bgcolor="#f0f0ff"><font color="#f0f0ff" size="-5"> --> -->
28</font> </font> </font> </script> </object> </blockquote> </pre>
29</table> </table> </table> </table> </table> </font> </font> </font>'''
30
31def html(etype, evalue, etb, context=5):
32 """Return a nice HTML document describing the traceback."""
33 import sys, os, types, time, traceback
34 import keyword, tokenize, linecache, inspect, pydoc
35
36 if type(etype) is types.ClassType:
37 etype = etype.__name__
38 pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
39 date = time.ctime(time.time())
40 head = '<body bgcolor="#f0f0ff">' + pydoc.html.heading(
41 '<big><big><strong>%s</strong></big></big>' % str(etype),
42 '#ffffff', '#aa55cc', pyver + '<br>' + date) + '''
43<p>A problem occurred in a Python script.
44Here is the sequence of function calls leading up to
45the error, with the most recent (innermost) call last.'''
46
47 indent = '<tt><small>' + '&nbsp;' * 5 + '</small>&nbsp;</tt>'
48 frames = []
49 records = inspect.getinnerframes(etb, context)
50 for frame, file, lnum, func, lines, index in records:
51 file = file and os.path.abspath(file) or '?'
52 link = '<a href="file:%s">%s</a>' % (file, pydoc.html.escape(file))
53 args, varargs, varkw, locals = inspect.getargvalues(frame)
54 if func == '?':
55 call = ''
56 else:
57 def eqrepr(value): return '=' + pydoc.html.repr(value)
58 call = 'in <strong>%s</strong>' % func + inspect.formatargvalues(
59 args, varargs, varkw, locals, formatvalue=eqrepr)
60
61 names = []
62 def tokeneater(type, token, start, end, line):
63 if type == tokenize.NAME and token not in keyword.kwlist:
64 if token not in names: names.append(token)
65 if type == tokenize.NEWLINE: raise IndexError
66 def linereader(lnum=[lnum]):
67 line = linecache.getline(file, lnum[0])
68 lnum[0] += 1
69 return line
70
71 try:
72 tokenize.tokenize(linereader, tokeneater)
73 except IndexError: pass
74 lvals = []
75 for name in names:
76 if name in frame.f_code.co_varnames:
77 if locals.has_key(name):
78 value = pydoc.html.repr(locals[name])
79 else:
80 value = '<em>undefined</em>'
81 name = '<strong>%s</strong>' % name
82 else:
83 if frame.f_globals.has_key(name):
84 value = pydoc.html.repr(frame.f_globals[name])
85 else:
86 value = '<em>undefined</em>'
87 name = '<em>global</em> <strong>%s</strong>' % name
88 lvals.append('%s&nbsp;= %s' % (name, value))
89 if lvals:
90 lvals = indent + '''
91<small><font color="#909090">%s</font></small><br>''' % (', '.join(lvals))
92 else:
93 lvals = ''
94
95 level = '''
96<table width="100%%" bgcolor="#d8bbff" cellspacing=0 cellpadding=2 border=0>
97<tr><td>%s %s</td></tr></table>\n''' % (link, call)
98 excerpt = []
99 if index is not None:
100 i = lnum - index
101 for line in lines:
102 num = '<small><font color="#909090">%s</font></small>' % (
103 '&nbsp;' * (5-len(str(i))) + str(i))
104 line = '<tt>%s&nbsp;%s</tt>' % (num, pydoc.html.preformat(line))
105 if i == lnum:
106 line = '''
107<table width="100%%" bgcolor="#ffccee" cellspacing=0 cellpadding=0 border=0>
108<tr><td>%s</td></tr></table>\n''' % line
109 excerpt.append('\n' + line)
110 if i == lnum:
111 excerpt.append(lvals)
112 i = i + 1
113 frames.append('<p>' + level + '\n'.join(excerpt))
114
115 exception = ['<p><strong>%s</strong>: %s' % (str(etype), str(evalue))]
116 if type(evalue) is types.InstanceType:
117 for name in dir(evalue):
118 value = pydoc.html.repr(getattr(evalue, name))
119 exception.append('\n<br>%s%s&nbsp;=\n%s' % (indent, name, value))
120
121 import traceback
122 plaintrace = ''.join(traceback.format_exception(etype, evalue, etb))
123
124 return head + ''.join(frames) + ''.join(exception) + '''
125
126
127<!-- The above is a description of an error that occurred in a Python program.
128 It is formatted for display in a Web browser because it appears that
129 we are running in a CGI environment. In case you are viewing this
130 message outside of a Web browser, here is the original error traceback:
131
132%s
133-->
134''' % plaintrace
135
136class Hook:
137 def __init__(self, display=1, logdir=None):
138 self.display = display # send tracebacks to browser if true
139 self.logdir = logdir # log tracebacks to files if not None
140
141 def __call__(self, etype, evalue, etb):
142 """This hook can replace sys.excepthook (for Python 2.1 or higher)."""
143 self.handle((etype, evalue, etb))
144
145 def handle(self, info=None):
146 import sys, os
147 info = info or sys.exc_info()
148 text = 0
149 print reset()
150
151 try:
152 doc = html(*info)
153 except: # just in case something goes wrong
154 import traceback
155 doc = ''.join(traceback.format_exception(*info))
156 text = 1
157
158 if self.display:
159 if text:
160 doc = doc.replace('&', '&amp;').replace('<', '&lt;')
161 print '<pre>', doc, '</pre>'
162 else:
163 print doc
164 else:
165 print '<p>A problem occurred in a Python script.'
166
167 if self.logdir is not None:
168 import tempfile
169 name = tempfile.mktemp(['.html', '.txt'][text])
170 path = os.path.join(self.logdir, os.path.basename(name))
171 try:
172 file = open(path, 'w')
173 file.write(doc)
174 file.close()
175 print '<p>%s contains the description of this error.' % path
176 except:
177 print '<p>Tried to write to %s, but failed.' % path
178
179handler = Hook().handle
180def enable(display=1, logdir=None):
181 import sys
182 sys.excepthook = Hook(display, logdir)