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