Reputation: 45224
Stack traces in Python show file paths. Is there any way to get them to show fully qualified function names instead?
Example:
class Foo(object):
def bar(self):
raise Exception, "Some error."
def inner():
return Foo().bar()
def outer():
return inner()
I'd like 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 it changes anything, I'm 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 have trouble with important use cases. For example, I can't get the class name Foo
for Foo.bar()
.
Upvotes: 5
Views: 2717
Reputation: 45224
There's no direct way to access symbols from a traceback since only "code objects" are accessible and, as the Python docs on code objects say:
Unlike function objects, code objects are immutable and contain no references (directly or indirectly) to mutable objects.
It seems that to retrieve the modules, functions and classes involved in the traceback, we need to search for them.
I've got an experimental version that seems to work. This implementation is based on walking the module referenced by the code object to find the function or method that references the code object in question.
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's 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's 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's 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's 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 may help during debugging.
It may not work as expected in presence of descriptors and whatnot, I haven't tried very elaborate use cases. If you can find a case in which it doesn't work, let me know and I'll try to patch it.
Upvotes: 5