Recipe8.6.Getting More Information from Tracebacks


Recipe 8.6. Getting More Information from Tracebacks

Credit: Bryn Keller

Problem

You want to display all of the available information when an uncaught exception is raised.

Solution

A traceback object is basically a linked list of nodes, in which each node refers to a frame object. Frame objects, in turn, form their own linked list in the opposite order from the linked list of traceback nodes, so we can walk back and forth if needed. This recipe exploits this structure and the rich amount of information held by frame objects, including, in particular, the dictionary of local variables for the function corresponding to each frame:

import sys, traceback def print_exc_plus( ):     """ Print the usual traceback information, followed by a listing of         all the local variables in each frame.     """     tb = sys.exc_info( )[2]     while tb.tb_next:         tb = tb.tb_next     stack = [  ]     f = tb.tb_frame     while f:         stack.append(f)         f = f.f_back     stack.reverse( )     traceback.print_exc( )     print "Locals by frame, innermost last"     for frame in stack:         print         print "Frame %s in %s at line %s" % (frame.f_code.co_name,                                              frame.f_code.co_filename,                                              frame.f_lineno)         for key, value in frame.f_locals.items( ):             print "\t%20s = " % key,             # we must _absolutely_ avoid propagating exceptions, and str(value)             # COULD cause any exception, so we MUST catch any...:             try:                 print value             except:                 print "<ERROR WHILE PRINTING VALUE>"

Discussion

The standard Python TRaceback module provides useful functions to give information about where and why an error occurred. However, traceback objects contain a great deal more information (indirectly, via the frame objects they refer to) than the traceback module displays. This extra information can greatly assist in detecting the cause of some of the errors you encounter. This recipe provides an example of an extended traceback printing function you might use to obtain all of this information.

Here's a simplistic demonstration of the kind of problem this approach can help with. Basically, we have a simple function that manipulates all the strings in a list. The function doesn't do any error checking, so, when we pass a list that contains something other than strings, we get an error. Figuring out which bad data caused the error is easier with our new print_exc_plus function to help us:

data = ["1", "2", 3, "4"]     # Typo: we 'forget' the quotes on data[2] def pad4(seq):     """     Pad each string in seq with zeros up to four places. Note that there     is no reason to actually write this function; Python already     does this sort of thing much better.  It's just an example!     """     return_value = [  ]     for thing in seq:         return_value.append("0" * (4 - len(thing)) + thing)     return return_value

Here's the (limited) information we get from a normal traceback.print_exc:

>>> try: ...     pad4(data) ... except: ...     traceback.print_exc( ) ... Traceback (most recent call last):   File "<stdin>", line 2, in ?   File "<stdin>", line 9, in pad4 TypeError: len( ) of unsized object

Now here's how it looks when displaying the info with the function from this recipe instead of the standard traceback.print_exc:

>>> try: ...     pad4(data) ... except: ...     print_exc_plus( ) ... Traceback (most recent call last):   File "<stdin>", line 2, in ?   File "<stdin>", line 9, in pad4 TypeError: len( ) of unsized object Locals by frame, innermost last Frame ? in <stdin> at line 4                          sys =  <module 'sys' (built-in)>                         pad4 =  <function pad4 at 0x007C6210>                 _ _builtins_ _ =  <module '_ _builtin_ _' (built-in)>                     _ _name_ _ =  _ _main_ _                         data =  ['1', '2', 3, '4']                      _ _doc_ _ =  None               print_exc_plus =  <function print_exc_plus at 0x00802038> Frame pad4 in <stdin> at line 9                        thing =  3                 return_value =  ['0001', '0002']                          seq =  ['1', '2', 3, '4']

Note how easy it is to see the bad data that caused the problem. The thing variable has a value of 3, so we know why we got the TypeError. A quick look at the value for data shows that we simply forgot the quotes on that item. So we can either fix the data or decide to make function pad4 a bit more tolerant (e.g., by changing the loop to for thing in map(str, seq)). These kind of design choices are important, but the point of this recipe is to save you time in understanding what's going on, so you can make your design choices with all the available information.

The recipe relies on the fact that each traceback object refers to the next traceback object in the stack through the tb_next field, forming a linked list. Each traceback object also refers to a corresponding frame object through the tb_frame field, and each frame refers to the previous frame through the f_back field (a linked list going the other way around from that of the traceback objects).

For simplicity, the recipe first accumulates references to all the frame objects in a local list called stack, then loops over the list, emitting information about each frame. For each frame, it first emits some basic information (e.g., function name, filename, line number, etc.) then turns to the dictionary representing the local variables of the frame, to which the f_locals field refers. Just like for the dictionaries built and returned by the locals and globals built-in functions, each key is a variable name, and the corresponding value is the variable's value. Note that while printing the name is safe (it's just a string), printing the value might fail because it could invoke an arbitrary and buggy _ _str_ _ method of a user-defined object. So, the value is printed within a try/except statement, to prevent the propagation of an uncaught exception while another exception is being handled. An except clause that does not list the exceptions to catch, and thus catches every exception, is almost always a mistake, but this recipe exemplifies the almost part of this statement!

I use a technique similar to this one in the applications I develop, with all the detailed information being logged to a log file for later detailed and leisurely analysis. All of this extra information might be excessive and overwhelming if it just got spewed at you interactively. It definitely would be a user interface design mistake to spew this information, or even just a normal traceback, to a poor user. Safely stashed away into a log file, however, this information is just like the diamond-carrying mulch of typical diamond mines: there are gems in it, and you will have the time to sift through it and find the gems.

See Also

Recipe 8.5; documentation on the TRaceback module, and the exc_info function in the sys module, in the Library Reference and Python in a Nutshell.



Python Cookbook
Python Cookbook
ISBN: 0596007973
EAN: 2147483647
Year: 2004
Pages: 420

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net