Python traceback with module names

Stack traces in Python show file paths. Is there a way to make them show fully qualified function names?

Example:

class Foo(object):
    def bar(self):
        raise Exception, "Some error."

def inner():
    return Foo().bar()

def outer():
    return inner()

      

I want my output to look like this:

In __main__.Foo.bar ("scratchpad.py", line 3)
   __main__.inner ("scratchpad.py", line 6)
   __main__.outer ("scratchpad.py", line 9)
Exception: Some error.

      

If this changes anything, I am using Python 2.7.


Here's what I have so far:

import sys

class Foo(object):
    def bar(self):
        raise Exception, "Dummy value."

def inner():
    "Inner function..."
    return Foo().bar()

def outer():
    return inner()


try:
    outer()
except Exception, error:
    traceback = sys.exc_info()[2]
    while traceback is not None:
        frame = traceback.tb_frame
        print 'Name', frame.f_globals['__name__']+'.'+frame.f_code.co_name
        docs = frame.f_code.co_consts[0]
        if docs and docs != -1: # docs can be None or -1.
            print 'Docs "%s"' % docs
        print 'Args', frame.f_code.co_argcount
        print 'File "%s", line %d' % (frame.f_code.co_filename, frame.f_lineno)
        print
        traceback = traceback.tb_next

      

When I run it, it prints

$ python pretty-stack.py
Name __main__.<module>
Args 0
File "pretty-stack.py", line 28

Name __main__.outer
Args 0
File "pretty-stack.py", line 14

Name __main__.inner
Docs "Inner function..."
Args 0
File "pretty-stack.py", line 11

Name __main__.bar
Args 1
File "pretty-stack.py", line 7

      

It's almost there, but I'm having problems with important use cases. For example, I cannot get the class name Foo

for Foo.bar()

.

+3


source to share


1 answer


There is no direct way to access symbols from a trace as only "code objects" and Python documents on code objects are available

Unlike function objects, code objects are immutable and do not contain references (directly or indirectly) to mutable objects.

It seems that in order to retrieve the modules, functions and classes involved in the tracing, we need to search for them.


I have an experimental version that seems to work. This implementation relies on walking the module referenced by a code object to find a function or method that refers to a given code object.



from collections import namedtuple
import inspect
import sys

from nested.external import Foo

def inner(a, b='qux'):
    "Inner function..."
    return Foo().bar()

def outer(a, *args, **kwds):
    return inner(a)

def resolve_signature(function):
    """Get a formatted string that looks like the function signature."""
    prgs, vrgs, kwds, defs = inspect.getargspec(function)
    arguments = []
    if defs:
        for name in prgs[:len(defs)-1]:
            arguments.append(name)
        for i,name in enumerate(prgs[len(defs)-1]):
            arguments.append('%s=%r'%(name,defs[i]))
    else:
        arguments.extend(prgs)
    if vrgs:
        arguments.append('*'+vrgs)
    if kwds:
        arguments.append('**'+kwds)
    return '('+', '.join(arguments)+')'


def resolve_scope(module_name, code):
    """Resolve the scope name for a code object provided its module name."""
    # Resolve module.
    module = sys.modules.get(module_name, None)
    if not module:
        return '<hidden-module>' + '.' + code.co_name + '(?)'

    # Check module functions.
    symbols = inspect.getmembers(module, inspect.isfunction)
    for symbol_name,symbol_info in symbols:
        if symbol_info.func_code is code:
            scope = module_name + '.'
            return scope + code.co_name + resolve_signature(symbol_info)

    # Check module classes.
    symbols = inspect.getmembers(module, inspect.isclass)
    for symbol_name,symbol_info in symbols:
        # Check class' methods.
        members = inspect.getmembers(symbol_info, inspect.ismethod)
        for method_name,method_info in members:
            if method_info.__func__.func_code is code:
                scope = module_name + '.' + symbol_name + '.'
                return scope + code.co_name + resolve_signature(method_info)

    # Default to the thing name.  This usually happens
    # when resolving the stack frame for module-level code.
    return code.co_name

Frame = namedtuple('Frame', ['call', 'file', 'line', 'help'])

def pretty_stack(traceback=None):
    """Returns a simple stack frame."""
    frames = []
    if traceback is None:
        traceback = sys.exc_info()[2]
    while traceback is not None:
        frame = traceback.tb_frame
        call = resolve_scope(frame.f_globals['__name__'], frame.f_code)
        path = frame.f_code.co_filename.replace('\\', '/')
        line = frame.f_lineno
        docs = frame.f_code.co_consts[0]
        if docs == -1:
            docs = None
        frames.append(Frame(call, path, line, docs))
        traceback = traceback.tb_next
    return frames

try:
    outer(1)
except Exception, error:
    frames = pretty_stack()
    for frame in frames:
        print frame.call
        print '  -> "%s", line %d.' % (frame.file, frame.line)
        if frame.help:
            print frame.help
        print

      

When I run this, I get something like:

$ python pretty-stack.py
<module>
  -> "pretty-stack.py", line 84.

__main__.outer(a, *args, **kwds)
  -> "pretty-stack.py", line 14.

__main__.inner(a='qux')
  -> "pretty-stack.py", line 11.
Inner function...

nested.external.Foo.bar(self)
  -> "C:/Users/acaron/Desktop/nested/external.py", line 3.

      

Note that this even prints function signatures and doc strings, which can help while debugging.

It may not work as expected in the presence of descriptors and whatnot, I haven't tried very complex use cases. If you find a case where it doesn't work, please let me know and I'll try to fix it.

+2


source







All Articles