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