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()
.
source to share
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.
source to share