$search
00001 """More comprehensive traceback formatting for Python scripts. 00002 00003 To enable this module, do: 00004 00005 import cgitb; cgitb.enable() 00006 00007 at the top of your script. The optional arguments to enable() are: 00008 00009 display - if true, tracebacks are displayed in the web browser 00010 logdir - if set, tracebacks are written to files in this directory 00011 context - number of lines of source code to show for each stack frame 00012 format - 'text' or 'html' controls the output format 00013 viewClass - sub class of View. Create this if you want to customize the 00014 layout of the traceback. 00015 debug - may be used by viewClass to decide on level of detail 00016 00017 By default, tracebacks are displayed but not saved, the context is 5 lines 00018 and the output format is 'html' (for backwards compatibility with the 00019 original use of this module). 00020 00021 Alternatively, if you have caught an exception and want cgitb to display it 00022 for you, call cgitb.handler(). The optional argument to handler() is a 00023 3-item tuple (etype, evalue, etb) just like the value of sys.exc_info(). 00024 The default handler displays output as HTML. 00025 00026 00027 2005-04-22 Nir Soffer <nirs@freeshell.org> 00028 00029 Rewrite: 00030 - Refactor html and text functions to View class, HTMLFormatter and 00031 TextFormatter. No more duplicate formating code. 00032 - Layout is done with minimal html and css, in a way it can't be 00033 affected by surrounding code. 00034 - Built to be easy to subclass and modify without duplicating code. 00035 - Change layout, important details come first. 00036 - Factor frame analyzing and formatting into separate class. 00037 - Add debug argument, can be used to change error display e.g. user 00038 error view, developer error view. 00039 - Add viewClass argument, make it easy to customize the traceback view. 00040 - Easy to customize system details and application details. 00041 00042 The main goal of this rewrite was to have a traceback that can render 00043 few tracebacks combined. It's needed when you wrap an expection and want 00044 to print both the traceback up to the wrapper exception, and the 00045 original traceback. There is no code to support this here, but it's easy 00046 to add by using your own View sub class. 00047 """ 00048 00049 __author__ = 'Ka-Ping Yee' 00050 __version__ = '$Revision: 1.10 $' 00051 00052 import sys, os, pydoc, inspect, linecache, tokenize, keyword 00053 00054 00055 def reset(): 00056 """ Reset the CGI and the browser 00057 00058 Return a string that resets the CGI and browser to a known state. 00059 TODO: probably some of this is not needed any more. 00060 """ 00061 return """<!--: spam 00062 Content-Type: text/html 00063 00064 <body><font style="color: white; font-size: 1px"> --> 00065 <body><font style="color: white; font-size: 1px"> --> --> 00066 </font> </font> </font> </script> </object> </blockquote> </pre> 00067 </table> </table> </table> </table> </table> </font> </font> </font> 00068 """ 00069 00070 __UNDEF__ = [] # a special sentinel object 00071 00072 00073 class HiddenObject: 00074 def __repr__(self): 00075 return "<HIDDEN>" 00076 HiddenObject = HiddenObject() 00077 00078 class HTMLFormatter: 00079 """ Minimal html formatter """ 00080 00081 def attributes(self, attributes=None): 00082 if attributes: 00083 result = [' %s="%s"' % (k, v) for k, v in attributes.items()] 00084 return ''.join(result) 00085 return '' 00086 00087 def tag(self, name, text, attributes=None): 00088 return '<%s%s>%s</%s>\n' % (name, self.attributes(attributes), text, name) 00089 00090 def section(self, text, attributes=None): 00091 return self.tag('div', text, attributes) 00092 00093 def title(self, text, attributes=None): 00094 return self.tag('h1', text, attributes) 00095 00096 def subTitle(self, text, attributes=None): 00097 return self.tag('h2', text, attributes) 00098 00099 def subSubTitle(self, text, attributes=None): 00100 return self.tag('h3', text, attributes) 00101 00102 def paragraph(self, text, attributes=None): 00103 return self.tag('p', text, attributes) 00104 00105 def list(self, items, attributes=None): 00106 return self.formatList('ul', items, attributes) 00107 00108 def orderedList(self, items, attributes=None): 00109 return self.formatList('ol', items, attributes) 00110 00111 def formatList(self, name, items, attributes=None): 00112 """ Send list of raw texts or formatted items. """ 00113 if isinstance(items, (list, tuple)): 00114 items = '\n' + ''.join([self.listItem(i) for i in items]) 00115 return self.tag(name, items, attributes) 00116 00117 def listItem(self, text, attributes=None): 00118 return self.tag('li', text, attributes) 00119 00120 def link(self, href, text, attributes=None): 00121 if attributes is None: 00122 attributes = {} 00123 attributes['href'] = href 00124 return self.tag('a', text, attributes) 00125 00126 def strong(self, text, attributes=None): 00127 return self.tag('strong', text, attributes) 00128 00129 def em(self, text, attributes=None): 00130 return self.tag('em', text, attributes) 00131 00132 def repr(self, object): 00133 return pydoc.html.repr(object) 00134 00135 00136 class TextFormatter: 00137 """ Plain text formatter """ 00138 00139 def section(self, text, attributes=None): 00140 return text 00141 00142 def title(self, text, attributes=None): 00143 lineBellow = '=' * len(text) 00144 return '%s\n%s\n\n' % (text, lineBellow) 00145 00146 def subTitle(self, text, attributes=None): 00147 lineBellow = '-' * len(text) 00148 return '%s\n%s\n\n' % (text, lineBellow) 00149 00150 def subSubTitle(self, text, attributes=None): 00151 lineBellow = '~' * len(text) 00152 return '%s\n%s\n\n' % (text, lineBellow) 00153 00154 def paragraph(self, text, attributes=None): 00155 return text + '\n\n' 00156 00157 def list(self, items, attributes=None): 00158 if isinstance(items, (list, tuple)): 00159 items = [' * %s\n' % i for i in items] 00160 return ''.join(items) + '\n' 00161 return items 00162 00163 def orderedList(self, items, attributes=None): 00164 if isinstance(items, (list, tuple)): 00165 result = [] 00166 for i in range(items): 00167 result.append(' %d. %s\n' % (i, items[i])) 00168 return ''.join(result) + '\n' 00169 return items 00170 00171 def listItem(self, text, attributes=None): 00172 return ' * %s\n' % text 00173 00174 def link(self, href, text, attributes=None): 00175 return '[[%s]]' % text 00176 00177 def strong(self, text, attributes=None): 00178 return text 00179 00180 def em(self, text, attributes=None): 00181 return text 00182 00183 def repr(self, object): 00184 return repr(object) 00185 00186 00187 class Frame: 00188 """ Analyze and format single frame in a traceback """ 00189 00190 def __init__(self, frame, file, lnum, func, lines, index): 00191 self.frame = frame 00192 self.file = file 00193 self.lnum = lnum 00194 self.func = func 00195 self.lines = lines 00196 self.index = index 00197 00198 def format(self, formatter): 00199 """ Return formatted content """ 00200 self.formatter = formatter 00201 vars, highlight = self.scan() 00202 items = [self.formatCall(), 00203 self.formatContext(highlight), 00204 self.formatVariables(vars)] 00205 return ''.join(items) 00206 00207 # ----------------------------------------------------------------- 00208 # Private - formatting 00209 00210 def formatCall(self): 00211 call = '%s in %s%s' % (self.formatFile(), 00212 self.formatter.strong(self.func), 00213 self.formatArguments(),) 00214 return self.formatter.paragraph(call, {'class': 'call'}) 00215 00216 def formatFile(self): 00217 """ Return formatted file link """ 00218 if not self.file: 00219 return '?' 00220 file = pydoc.html.escape(os.path.abspath(self.file)) 00221 return self.formatter.link('file://' + file, file) 00222 00223 def formatArguments(self): 00224 """ Return formated arguments list """ 00225 if self.func == '?': 00226 return '' 00227 00228 def formatValue(value): 00229 return '=' + self.formatter.repr(value) 00230 00231 args, varargs, varkw, locals = inspect.getargvalues(self.frame) 00232 return inspect.formatargvalues(args, varargs, varkw, locals, 00233 formatvalue=formatValue) 00234 00235 def formatContext(self, highlight): 00236 """ Return formatted context, next call highlighted """ 00237 if self.index is None: 00238 return '' 00239 context = [] 00240 i = self.lnum - self.index 00241 for line in self.lines: 00242 line = '%5d %s' % (i, pydoc.html.escape(line)) 00243 attributes = {} 00244 if i in highlight: 00245 attributes = {'class': 'highlight'} 00246 context.append(self.formatter.listItem(line, attributes)) 00247 i += 1 00248 context = '\n' + ''.join(context) + '\n' 00249 return self.formatter.orderedList(context, {'class': 'context'}) 00250 00251 def formatVariables(self, vars): 00252 """ Return formatted variables """ 00253 done = {} 00254 dump = [] 00255 for name, where, value in vars: 00256 if name in done: 00257 continue 00258 done[name] = 1 00259 if value is __UNDEF__: 00260 dump.append('%s %s' % (name, self.formatter.em('undefined'))) 00261 else: 00262 dump.append(self.formatNameValue(name, where, value)) 00263 return self.formatter.list(dump, {'class': 'variables'}) 00264 00265 def formatNameValue(self, name, where, value): 00266 """ Format variable name and value according to scope """ 00267 if where in ['global', 'builtin']: 00268 name = '%s %s' % (self.formatter.em(where), 00269 self.formatter.strong(name)) 00270 elif where == 'local': 00271 name = self.formatter.strong(name) 00272 else: 00273 name = where + self.formatter.strong(name.split('.')[-1]) 00274 return '%s = %s' % (name, self.formatter.repr(value)) 00275 00276 # --------------------------------------------------------------- 00277 # Private - analyzing code 00278 00279 def scan(self): 00280 """ Scan frame for vars while setting highlight line """ 00281 highlight = {} 00282 00283 def reader(lnum=[self.lnum]): 00284 highlight[lnum[0]] = 1 00285 try: 00286 return linecache.getline(self.file, lnum[0]) 00287 finally: 00288 lnum[0] += 1 00289 00290 vars = self.scanVariables(reader) 00291 return vars, highlight 00292 00293 def scanVariables(self, reader): 00294 """ Lookup variables in one logical Python line """ 00295 vars, lasttoken, parent, prefix, value = [], None, None, '', __UNDEF__ 00296 for ttype, token, start, end, line in tokenize.generate_tokens(reader): 00297 if ttype == tokenize.NEWLINE: 00298 break 00299 if ttype == tokenize.NAME and token not in keyword.kwlist: 00300 if lasttoken == '.': 00301 if parent is not __UNDEF__: 00302 if self.unsafe_name(token): 00303 value = HiddenObject 00304 else: 00305 value = getattr(parent, token, __UNDEF__) 00306 vars.append((prefix + token, prefix, value)) 00307 else: 00308 where, value = self.lookup(token) 00309 vars.append((token, where, value)) 00310 elif token == '.': 00311 prefix += lasttoken + '.' 00312 parent = value 00313 else: 00314 parent, prefix = None, '' 00315 lasttoken = token 00316 return vars 00317 00318 def lookup(self, name): 00319 """ Return the scope and the value of name """ 00320 scope = None 00321 value = __UNDEF__ 00322 locals = inspect.getargvalues(self.frame)[3] 00323 if name in locals: 00324 scope, value = 'local', locals[name] 00325 elif name in self.frame.f_globals: 00326 scope, value = 'global', self.frame.f_globals[name] 00327 elif '__builtins__' in self.frame.f_globals: 00328 scope = 'builtin' 00329 builtins = self.frame.f_globals['__builtins__'] 00330 if isinstance(builtins, dict): 00331 value = builtins.get(name, __UNDEF__) 00332 else: 00333 value = getattr(builtins, name, __UNDEF__) 00334 if self.unsafe_name(name): 00335 value = HiddenObject 00336 return scope, value 00337 00338 def unsafe_name(self, name): 00339 return name in self.frame.f_globals.get("unsafe_names", ()) 00340 00341 class View: 00342 """ Traceback view """ 00343 00344 frameClass = Frame # analyze and format a frame 00345 00346 def __init__(self, info=None, debug=0): 00347 """ Save starting info or current exception info """ 00348 self.info = info or sys.exc_info() 00349 self.debug = debug 00350 00351 def format(self, formatter, context=5): 00352 self.formatter = formatter 00353 self.context = context 00354 return formatter.section(self.formatContent(), {'class': 'cgitb'}) 00355 00356 def formatContent(self): 00357 """ General layout - override to change layout """ 00358 content = ( 00359 self.formatStylesheet(), 00360 self.formatTitle(), 00361 self.formatMessage(), 00362 self.formatTraceback(), 00363 self.formatSystemDetails(), 00364 self.formatTextTraceback(), 00365 ) 00366 return ''.join(content) 00367 00368 # ----------------------------------------------------------------- 00369 # Stylesheet 00370 00371 def formatStylesheet(self): 00372 """ Format inline html stylesheet """ 00373 return '<style type="text/css">%s</style>' % self.stylesheet() 00374 00375 def stylesheet(self): 00376 """ Return stylesheet rules. Override to change rules. 00377 00378 The rules are sparated to make it easy to extend. 00379 00380 The stylesheet must work even if sorounding code define the same 00381 css names, and it must not change the sorounding code look and 00382 behavior. This is done by having all content in a .traceback 00383 section. 00384 """ 00385 return """ 00386 .cgitb {background: #E6EAF0; border: 1px solid #4D6180; direction: ltr;} 00387 .cgitb p {margin: 0.5em 0; padding: 5px 10px; text-align: left;} 00388 .cgitb ol {margin: 0} 00389 .cgitb li {margin: 0.25em 0;} 00390 .cgitb h1, .cgitb h2, .cgitb h3 {padding: 5px 10px; margin: 0; background: #4D6180; color: white;} 00391 .cgitb h1 {font-size: 1.3em;} 00392 .cgitb h2 {font-size: 1em; margin-top: 1em;} 00393 .cgitb h3 {font-size: 1em;} 00394 .cgitb .frames {margin: 0; padding: 0; color: #606060} 00395 .cgitb .frames li {display: block;} 00396 .cgitb .call {padding: 5px 10px; background: #A3B4CC; color: black} 00397 .cgitb .context {padding: 0; font-family: monospace; } 00398 .cgitb .context li {display: block; white-space: pre;} 00399 .cgitb .context li.highlight {background: #C0D3F0; color: black} 00400 .cgitb .variables {padding: 5px 10px; font-family: monospace;} 00401 .cgitb .variables li {display: inline;} 00402 .cgitb .variables li:after {content: ", ";} 00403 .cgitb .variables li:last-child:after {content: "";} 00404 .cgitb .exception {border: 1px solid #4D6180; margin: 10px} 00405 .cgitb .exception h3 {background: #4D6180; color: white;} 00406 .cgitb .exception p {color: black;} 00407 .cgitb .exception ul {padding: 0 10px; font-family: monospace;} 00408 .cgitb .exception li {display: block;} 00409 """ 00410 00411 # ----------------------------------------------------------------- 00412 # Head 00413 00414 def formatTitle(self): 00415 return self.formatter.title(self.exceptionTitle(self.info)) 00416 00417 def formatMessage(self): 00418 return self.formatter.paragraph(self.exceptionMessage(self.info)) 00419 00420 # ----------------------------------------------------------------- 00421 # Traceback 00422 00423 def formatTraceback(self): 00424 """ Return formatted traceback """ 00425 return self.formatOneTraceback(self.info) 00426 00427 def formatOneTraceback(self, info): 00428 """ Format one traceback 00429 00430 Separate to enable formatting multiple tracebacks. 00431 """ 00432 output = [self.formatter.subTitle('Traceback'), 00433 self.formatter.paragraph(self.tracebackText(info)), 00434 self.formatter.orderedList(self.tracebackFrames(info), 00435 {'class': 'frames'}), 00436 self.formatter.section(self.formatException(info), 00437 {'class': 'exception'}), ] 00438 return self.formatter.section(''.join(output), {'class': 'traceback'}) 00439 00440 def tracebackFrames(self, info): 00441 frames = [] 00442 traceback = info[2] 00443 for record in inspect.getinnerframes(traceback, self.context): 00444 frame = self.frameClass(*record) 00445 frames.append(frame.format(self.formatter)) 00446 del traceback 00447 return frames 00448 00449 def tracebackText(self, info): 00450 return '''A problem occurred in a Python script. Here is the 00451 sequence of function calls leading up to the error, in the 00452 order they occurred.''' 00453 00454 # -------------------------------------------------------------------- 00455 # Exception 00456 00457 def formatException(self, info): 00458 items = [self.formatExceptionTitle(info), 00459 self.formatExceptionMessage(info), 00460 self.formatExceptionAttributes(info), ] 00461 return ''.join(items) 00462 00463 def formatExceptionTitle(self, info): 00464 return self.formatter.subSubTitle(self.exceptionTitle(info)) 00465 00466 def formatExceptionMessage(self, info): 00467 return self.formatter.paragraph(self.exceptionMessage(info)) 00468 00469 def formatExceptionAttributes(self, info): 00470 attribtues = [] 00471 for name, value in self.exceptionAttributes(info): 00472 value = self.formatter.repr(value) 00473 attribtues.append('%s = %s' % (name, value)) 00474 return self.formatter.list(attribtues) 00475 00476 def exceptionAttributes(self, info): 00477 """ Return list of tuples [(name, value), ...] """ 00478 instance = info[1] 00479 attribtues = [] 00480 for name in dir(instance): 00481 if name.startswith('_'): 00482 continue 00483 value = getattr(instance, name) 00484 attribtues.append((name, value)) 00485 return attribtues 00486 00487 def exceptionTitle(self, info): 00488 type = info[0] 00489 return getattr(type, '__name__', str(type)) 00490 00491 def exceptionMessage(self, info): 00492 instance = info[1] 00493 return pydoc.html.escape(str(instance)) 00494 00495 00496 # ----------------------------------------------------------------- 00497 # System details 00498 00499 def formatSystemDetails(self): 00500 details = ['Date: %s' % self.date(), 00501 'Platform: %s' % self.platform(), 00502 'Python: %s' % self.python(), ] 00503 details += self.applicationDetails() 00504 return (self.formatter.subTitle('System Details') + 00505 self.formatter.list(details, {'class': 'system'})) 00506 00507 def date(self): 00508 import time 00509 rfc2822Date = time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()) 00510 return rfc2822Date 00511 00512 def platform(self): 00513 try: 00514 return pydoc.html.escape(' '.join(os.uname())) 00515 except: 00516 return pydoc.html.escape('%s (%s)' % (sys.platform, os.name)) 00517 00518 def python(self): 00519 return 'Python %s (%s)' % (sys.version.split()[0], sys.executable) 00520 00521 def applicationDetails(self): 00522 """ Override for your application """ 00523 return [] 00524 00525 # ----------------------------------------------------------------- 00526 # Text traceback 00527 00528 def formatTextTraceback(self): 00529 template = self.textTracebackTemplate() 00530 return template % self.formatOneTextTraceback(self.info) 00531 00532 def formatOneTextTraceback(self, info): 00533 """ Separate to enable formatting multiple tracebacks. """ 00534 import traceback 00535 return pydoc.html.escape(''.join(traceback.format_exception(*info))) 00536 00537 def textTracebackTemplate(self): 00538 return ''' 00539 00540 <!-- The above is a description of an error in a Python program, 00541 formatted for a Web browser. In case you are not reading this 00542 in a Web browser, here is the original traceback: 00543 00544 %s 00545 --> 00546 ''' 00547 00548 00549 class Hook: 00550 """A hook to replace sys.excepthook that shows tracebacks in HTML.""" 00551 00552 def __init__(self, display=1, logdir=None, context=5, file=None, 00553 format="html", viewClass=View, debug=0): 00554 self.display = display # send tracebacks to browser if true 00555 self.logdir = logdir # log tracebacks to files if not None 00556 self.context = context # number of source code lines per frame 00557 self.file = file or sys.stdout # place to send the output 00558 self.format = format 00559 self.viewClass = viewClass 00560 self.debug = debug 00561 00562 def __call__(self, etype, evalue, etb): 00563 self.handle((etype, evalue, etb)) 00564 00565 def handle(self, info=None): 00566 info = info or sys.exc_info() 00567 if self.format.lower() == "html": 00568 formatter = HTMLFormatter() 00569 self.file.write(reset()) 00570 plain = False 00571 else: 00572 formatter = TextFormatter() 00573 plain = True 00574 try: 00575 view = self.viewClass(info, self.debug) 00576 doc = view.format(formatter, self.context) 00577 except: 00578 raise 00579 import traceback 00580 doc = ''.join(traceback.format_exception(*info)) 00581 plain = True 00582 00583 if self.display: 00584 if plain: 00585 doc = doc.replace('&', '&').replace('<', '<') 00586 self.file.write('<pre>' + doc + '</pre>\n') 00587 else: 00588 self.file.write(doc + '\n') 00589 else: 00590 self.file.write('<p>A problem occurred in a Python script.\n') 00591 00592 if self.logdir is not None: 00593 import tempfile 00594 suffix = ['.txt', '.html'][self.format == "html"] 00595 (fd, path) = tempfile.mkstemp(suffix=suffix, dir=self.logdir) 00596 try: 00597 file = os.fdopen(fd, 'w') 00598 file.write(doc) 00599 file.close() 00600 msg = '<p> %s contains the description of this error.' % path 00601 except: 00602 msg = '<p> Tried to save traceback to %s, but failed.' % path 00603 self.file.write(msg + '\n') 00604 try: 00605 self.file.flush() 00606 except: pass 00607 00608 00609 handler = Hook().handle 00610 00611 def enable(display=1, logdir=None, context=5, format="html", viewClass=View, debug=0): 00612 """Install an exception handler that formats tracebacks as HTML. 00613 00614 The optional argument 'display' can be set to 0 to suppress sending the 00615 traceback to the browser, and 'logdir' can be set to a directory to cause 00616 tracebacks to be written to files there.""" 00617 sys.excepthook = Hook(display=display, logdir=logdir, context=context, 00618 format=format, viewClass=viewClass, debug=debug) 00619