1 """
2 pretty debug errors
3 (part of web.py)
4
5 portions adapted from Django <djangoproject.com>
6 Copyright (c) 2005, the Lawrence Journal-World
7 Used under the modified BSD license:
8 http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
9 """
10
11 __all__ = ["debugerror", "djangoerror", "emailerrors"]
12
13 import sys, urlparse, pprint, traceback
14 from net import websafe
15 from template import Template
16 from utils import sendmail, safestr
17 import webapi as web
18
19 import os, os.path
20 whereami = os.path.join(os.getcwd(), __file__)
21 whereami = os.path.sep.join(whereami.split(os.path.sep)[:-1])
22 djangoerror_t = """\
23 $def with (exception_type, exception_value, frames)
24 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
25 <html lang="en">
26 <head>
27 <meta http-equiv="content-type" content="text/html; charset=utf-8" />
28 <meta name="robots" content="NONE,NOARCHIVE" />
29 <title>$exception_type at $ctx.path</title>
30 <style type="text/css">
31 html * { padding:0; margin:0; }
32 body * { padding:10px 20px; }
33 body * * { padding:0; }
34 body { font:small sans-serif; }
35 body>div { border-bottom:1px solid #ddd; }
36 h1 { font-weight:normal; }
37 h2 { margin-bottom:.8em; }
38 h2 span { font-size:80%; color:#666; font-weight:normal; }
39 h3 { margin:1em 0 .5em 0; }
40 h4 { margin:0 0 .5em 0; font-weight: normal; }
41 table {
42 border:1px solid #ccc; border-collapse: collapse; background:white; }
43 tbody td, tbody th { vertical-align:top; padding:2px 3px; }
44 thead th {
45 padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
46 font-weight:normal; font-size:11px; border:1px solid #ddd; }
47 tbody th { text-align:right; color:#666; padding-right:.5em; }
48 table.vars { margin:5px 0 2px 40px; }
49 table.vars td, table.req td { font-family:monospace; }
50 table td.code { width:100%;}
51 table td.code div { overflow:hidden; }
52 table.source th { color:#666; }
53 table.source td {
54 font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
55 ul.traceback { list-style-type:none; }
56 ul.traceback li.frame { margin-bottom:1em; }
57 div.context { margin: 10px 0; }
58 div.context ol {
59 padding-left:30px; margin:0 10px; list-style-position: inside; }
60 div.context ol li {
61 font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
62 div.context ol.context-line li { color:black; background-color:#ccc; }
63 div.context ol.context-line li span { float: right; }
64 div.commands { margin-left: 40px; }
65 div.commands a { color:black; text-decoration:none; }
66 #summary { background: #ffc; }
67 #summary h2 { font-weight: normal; color: #666; }
68 #explanation { background:#eee; }
69 #template, #template-not-exist { background:#f6f6f6; }
70 #template-not-exist ul { margin: 0 0 0 20px; }
71 #traceback { background:#eee; }
72 #requestinfo { background:#f6f6f6; padding-left:120px; }
73 #summary table { border:none; background:transparent; }
74 #requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
75 #requestinfo h3 { margin-bottom:-1em; }
76 .error { background: #ffc; }
77 .specific { color:#cc3300; font-weight:bold; }
78 </style>
79 <script type="text/javascript">
80 //<!--
81 function getElementsByClassName(oElm, strTagName, strClassName){
82 // Written by Jonathan Snook, http://www.snook.ca/jon;
83 // Add-ons by Robert Nyman, http://www.robertnyman.com
84 var arrElements = (strTagName == "*" && document.all)? document.all :
85 oElm.getElementsByTagName(strTagName);
86 var arrReturnElements = new Array();
87 strClassName = strClassName.replace(/\-/g, "\\-");
88 var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$$)");
89 var oElement;
90 for(var i=0; i<arrElements.length; i++){
91 oElement = arrElements[i];
92 if(oRegExp.test(oElement.className)){
93 arrReturnElements.push(oElement);
94 }
95 }
96 return (arrReturnElements)
97 }
98 function hideAll(elems) {
99 for (var e = 0; e < elems.length; e++) {
100 elems[e].style.display = 'none';
101 }
102 }
103 window.onload = function() {
104 hideAll(getElementsByClassName(document, 'table', 'vars'));
105 hideAll(getElementsByClassName(document, 'ol', 'pre-context'));
106 hideAll(getElementsByClassName(document, 'ol', 'post-context'));
107 }
108 function toggle() {
109 for (var i = 0; i < arguments.length; i++) {
110 var e = document.getElementById(arguments[i]);
111 if (e) {
112 e.style.display = e.style.display == 'none' ? 'block' : 'none';
113 }
114 }
115 return false;
116 }
117 function varToggle(link, id) {
118 toggle('v' + id);
119 var s = link.getElementsByTagName('span')[0];
120 var uarr = String.fromCharCode(0x25b6);
121 var darr = String.fromCharCode(0x25bc);
122 s.innerHTML = s.innerHTML == uarr ? darr : uarr;
123 return false;
124 }
125 //-->
126 </script>
127 </head>
128 <body>
129
130 $def dicttable (d, kls='req', id=None):
131 $ items = d and d.items() or []
132 $items.sort()
133 $:dicttable_items(items, kls, id)
134
135 $def dicttable_items(items, kls='req', id=None):
136 $if items:
137 <table class="$kls"
138 $if id: id="$id"
139 ><thead><tr><th>Variable</th><th>Value</th></tr></thead>
140 <tbody>
141 $for k, v in items:
142 <tr><td>$k</td><td class="code"><div>$prettify(v)</div></td></tr>
143 </tbody>
144 </table>
145 $else:
146 <p>No data.</p>
147
148 <div id="summary">
149 <h1>$exception_type at $ctx.path</h1>
150 <h2>$exception_value</h2>
151 <table><tr>
152 <th>Python</th>
153 <td>$frames[0].filename in $frames[0].function, line $frames[0].lineno</td>
154 </tr><tr>
155 <th>Web</th>
156 <td>$ctx.method $ctx.home$ctx.path</td>
157 </tr></table>
158 </div>
159 <div id="traceback">
160 <h2>Traceback <span>(innermost first)</span></h2>
161 <ul class="traceback">
162 $for frame in frames:
163 <li class="frame">
164 <code>$frame.filename</code> in <code>$frame.function</code>
165 $if frame.context_line is not None:
166 <div class="context" id="c$frame.id">
167 $if frame.pre_context:
168 <ol start="$frame.pre_context_lineno" class="pre-context" id="pre$frame.id">
169 $for line in frame.pre_context:
170 <li onclick="toggle('pre$frame.id', 'post$frame.id')">$line</li>
171 </ol>
172 <ol start="$frame.lineno" class="context-line"><li onclick="toggle('pre$frame.id', 'post$frame.id')">$frame.context_line <span>...</span></li></ol>
173 $if frame.post_context:
174 <ol start='${frame.lineno + 1}' class="post-context" id="post$frame.id">
175 $for line in frame.post_context:
176 <li onclick="toggle('pre$frame.id', 'post$frame.id')">$line</li>
177 </ol>
178 </div>
179
180 $if frame.vars:
181 <div class="commands">
182 <a href='#' onclick="return varToggle(this, '$frame.id')"><span>▶</span> Local vars</a>
183 $# $inspect.formatargvalues(*inspect.getargvalues(frame['tb'].tb_frame))
184 </div>
185 $:dicttable(frame.vars, kls='vars', id=('v' + str(frame.id)))
186 </li>
187 </ul>
188 </div>
189
190 <div id="requestinfo">
191 $if ctx.output or ctx.headers:
192 <h2>Response so far</h2>
193 <h3>HEADERS</h3>
194 $:dicttable_items(ctx.headers)
195
196 <h3>BODY</h3>
197 <p class="req" style="padding-bottom: 2em"><code>
198 $ctx.output
199 </code></p>
200
201 <h2>Request information</h2>
202
203 <h3>INPUT</h3>
204 $:dicttable(web.input(_unicode=False))
205
206 <h3 id="cookie-info">COOKIES</h3>
207 $:dicttable(web.cookies())
208
209 <h3 id="meta-info">META</h3>
210 $ newctx = [(k, v) for (k, v) in ctx.iteritems() if not k.startswith('_') and not isinstance(v, dict)]
211 $:dicttable(dict(newctx))
212
213 <h3 id="meta-info">ENVIRONMENT</h3>
214 $:dicttable(ctx.env)
215 </div>
216
217 <div id="explanation">
218 <p>
219 You're seeing this error because you have <code>web.config.debug</code>
220 set to <code>True</code>. Set that to <code>False</code> if you don't to see this.
221 </p>
222 </div>
223
224 </body>
225 </html>
226 """
227
228 djangoerror_r = None
229
231 def _get_lines_from_file(filename, lineno, context_lines):
232 """
233 Returns context_lines before and after lineno from file.
234 Returns (pre_context_lineno, pre_context, context_line, post_context).
235 """
236 try:
237 source = open(filename).readlines()
238 lower_bound = max(0, lineno - context_lines)
239 upper_bound = lineno + context_lines
240
241 pre_context = \
242 [line.strip('\n') for line in source[lower_bound:lineno]]
243 context_line = source[lineno].strip('\n')
244 post_context = \
245 [line.strip('\n') for line in source[lineno + 1:upper_bound]]
246
247 return lower_bound, pre_context, context_line, post_context
248 except (OSError, IOError, IndexError):
249 return None, [], None, []
250
251 exception_type, exception_value, tback = sys.exc_info()
252 frames = []
253 while tback is not None:
254 filename = tback.tb_frame.f_code.co_filename
255 function = tback.tb_frame.f_code.co_name
256 lineno = tback.tb_lineno - 1
257
258
259 lineno += tback.tb_frame.f_locals.get("__lineoffset__", 0)
260
261 pre_context_lineno, pre_context, context_line, post_context = \
262 _get_lines_from_file(filename, lineno, 7)
263
264 if '__hidetraceback__' not in tback.tb_frame.f_locals:
265 frames.append(web.storage({
266 'tback': tback,
267 'filename': filename,
268 'function': function,
269 'lineno': lineno,
270 'vars': tback.tb_frame.f_locals,
271 'id': id(tback),
272 'pre_context': pre_context,
273 'context_line': context_line,
274 'post_context': post_context,
275 'pre_context_lineno': pre_context_lineno,
276 }))
277 tback = tback.tb_next
278 frames.reverse()
279 urljoin = urlparse.urljoin
280 def prettify(x):
281 try:
282 out = pprint.pformat(x)
283 except Exception, e:
284 out = '[could not display: <' + e.__class__.__name__ + \
285 ': '+str(e)+'>]'
286 return out
287
288 global djangoerror_r
289 if djangoerror_r is None:
290 djangoerror_r = Template(djangoerror_t, filename=__file__, filter=websafe)
291
292 t = djangoerror_r
293 globals = {'ctx': web.ctx, 'web':web, 'dict':dict, 'str':str, 'prettify': prettify}
294 t.t.func_globals.update(globals)
295 return t(exception_type, exception_value, frames)
296
298 """
299 A replacement for `internalerror` that presents a nice page with lots
300 of debug information for the programmer.
301
302 (Based on the beautiful 500 page from [Django](http://djangoproject.com/),
303 designed by [Wilson Miner](http://wilsonminer.com/).)
304 """
305 return web._InternalError(djangoerror())
306
307 -def emailerrors(to_address, olderror, from_address=None):
308 """
309 Wraps the old `internalerror` handler (pass as `olderror`) to
310 additionally email all errors to `to_address`, to aid in
311 debugging production websites.
312
313 Emails contain a normal text traceback as well as an
314 attachment containing the nice `debugerror` page.
315 """
316 from_address = from_address or to_address
317
318 def emailerrors_internal():
319 error = olderror()
320 tb = sys.exc_info()
321 error_name = tb[0]
322 error_value = tb[1]
323 tb_txt = ''.join(traceback.format_exception(*tb))
324 path = web.ctx.path
325 request = web.ctx.method + ' ' + web.ctx.home + web.ctx.fullpath
326
327 message = "\n%s\n\n%s\n\n" % (request, tb_txt)
328
329 sendmail(
330 "your buggy site <%s>" % from_address,
331 "the bugfixer <%s>" % to_address,
332 "bug: %(error_name)s: %(error_value)s (%(path)s)" % locals(),
333 message,
334 attachments=[
335 dict(filename="bug.html", content=safestr(djangoerror()))
336 ],
337 )
338 return error
339
340 return emailerrors_internal
341
342 if __name__ == "__main__":
343 urls = (
344 '/', 'index'
345 )
346 from application import application
347 app = application(urls, globals())
348 app.internalerror = debugerror
349
353
354 app.run()
355