First version, initial module hello, initial load module
[kisspi.git] / web / debugerror.py
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
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:
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>&#x25b6;</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())
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
230 def djangoerror():
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):
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         pre_context_lineno, pre_context, context_line, post_context = \
258             _get_lines_from_file(filename, lineno, 7)
259         frames.append(web.storage({
260             'tback': tback,
261             'filename': filename,
262             'function': function,
263             'lineno': lineno,
264             'vars': tback.tb_frame.f_locals,
265             'id': id(tback),
266             'pre_context': pre_context,
267             'context_line': context_line,
268             'post_context': post_context,
269             'pre_context_lineno': pre_context_lineno,
270         }))
271         tback = tback.tb_next
272     frames.reverse()
273     urljoin = urlparse.urljoin
274     def prettify(x):
275         try: 
276             out = pprint.pformat(x)
277         except Exception, e: 
278             out = '[could not display: <' + e.__class__.__name__ + \
279                   ': '+str(e)+'>]'
280         return out
281         
282     global djangoerror_r
283     if djangoerror_r is None:
284         djangoerror_r = Template(djangoerror_t, filename=__file__, filter=websafe)
285         
286     t = djangoerror_r
287     globals = {'ctx': web.ctx, 'web':web, 'dict':dict, 'str':str, 'prettify': prettify}
288     t.t.func_globals.update(globals)
289     return t(exception_type, exception_value, frames)
290
291 def debugerror():
292     """
293     A replacement for `internalerror` that presents a nice page with lots
294     of debug information for the programmer.
295
296     (Based on the beautiful 500 page from [Django](http://djangoproject.com/), 
297     designed by [Wilson Miner](http://wilsonminer.com/).)
298     """
299     return web._InternalError(djangoerror())
300
301 def emailerrors(email_address, olderror):
302     """
303     Wraps the old `internalerror` handler (pass as `olderror`) to 
304     additionally email all errors to `email_address`, to aid in 
305     debugging production websites.
306     
307     Emails contain a normal text traceback as well as an
308     attachment containing the nice `debugerror` page.
309     """
310     def emailerrors_internal():
311         error = olderror()
312         tb = sys.exc_info()
313         error_name = tb[0]
314         error_value = tb[1]
315         tb_txt = ''.join(traceback.format_exception(*tb))
316         path = web.ctx.path
317         request = web.ctx.method+' '+web.ctx.home+web.ctx.fullpath
318         eaddr = email_address
319         text = ("""\
320 ------here----
321 Content-Type: text/plain
322 Content-Disposition: inline
323
324 %(request)s
325
326 %(tb_txt)s
327
328 ------here----
329 Content-Type: text/html; name="bug.html"
330 Content-Disposition: attachment; filename="bug.html"
331
332 """ % locals()) + str(djangoerror())
333         sendmail(
334           "your buggy site <%s>" % eaddr,
335           "the bugfixer <%s>" % eaddr,
336           "bug: %(error_name)s: %(error_value)s (%(path)s)" % locals(),
337           text, 
338           headers={'Content-Type': 'multipart/mixed; boundary="----here----"'})
339         return error
340     
341     return emailerrors_internal
342
343 if __name__ == "__main__":
344     urls = (
345         '/', 'index'
346     )
347     from application import application
348     app = application(urls, globals())
349     app.internalerror = debugerror
350     
351     class index:
352         def GET(self):
353             thisdoesnotexist
354
355     app.run()