Получить полную трассу
Как я могу получить полную трассировку в следующем случае, включая вызовы функций func2
и func
?
import traceback
def func():
try:
raise Exception('Dummy')
except:
traceback.print_exc()
def func2():
func()
func2()
Когда я запустил это, я получаю:
Traceback (most recent call last):
File "test.py", line 5, in func
raise Exception('Dummy')
Exception: Dummy
traceback.format_stack()
- это не то, что я хочу, так как нужно traceback
объект, который должен быть передан стороннему модулю.
Меня особенно интересует этот случай:
import logging
def func():
try:
raise Exception('Dummy')
except:
logging.exception("Something awful happened!")
def func2():
func()
func2()
В этом случае я получаю:
ERROR:root:Something awful happened!
Traceback (most recent call last):
File "test.py", line 9, in func
raise Exception('Dummy')
Exception: Dummy
Ответы
Ответ 1
Как ответил mechmind, трассировка стека состоит только из фреймов между сайтом, где было создано исключение, и сайтом блока try
. Если вам нужна полная трассировка стека, вам, кажется, не повезло.
За исключением того, что, очевидно, можно извлечь записи стека с верхнего уровня на текущий фрейм. traceback.extract_stack
управляет им просто отлично. Проблема в том, что информация, полученная с помощью traceback.extract_stack
, поступает от прямого контроля кадров стека без создания объекта трассировки в любой точке, а API logging
требует, чтобы объект трассировки влиял на вывод трассировки.
К счастью, logging
не требует реального объекта трассировки, он требует, чтобы объект мог перейти к процедурам форматирования модуля traceback
. traceback
тоже не заботится - он использует только два атрибута трассировки, кадр и номер строки. Таким образом, должно быть возможно создать связанный список объектов, созданных с помощью утка, и передать его в качестве трассировки.
import sys
class FauxTb(object):
def __init__(self, tb_frame, tb_lineno, tb_next):
self.tb_frame = tb_frame
self.tb_lineno = tb_lineno
self.tb_next = tb_next
def current_stack(skip=0):
try: 1/0
except ZeroDivisionError:
f = sys.exc_info()[2].tb_frame
for i in xrange(skip + 2):
f = f.f_back
lst = []
while f is not None:
lst.append((f, f.f_lineno))
f = f.f_back
return lst
def extend_traceback(tb, stack):
"""Extend traceback with stack info."""
head = tb
for tb_frame, tb_lineno in stack:
head = FauxTb(tb_frame, tb_lineno, head)
return head
def full_exc_info():
"""Like sys.exc_info, but includes the full traceback."""
t, v, tb = sys.exc_info()
full_tb = extend_traceback(tb, current_stack(1))
return t, v, full_tb
Благодаря этим функциям ваш код требует только тривиальной модификации:
import logging
def func():
try:
raise Exception('Dummy')
except:
logging.error("Something awful happened!", exc_info=full_exc_info())
def func2():
func()
func2()
... чтобы дать ожидаемый результат:
ERROR:root:Something awful happened!
Traceback (most recent call last):
File "a.py", line 52, in <module>
func2()
File "a.py", line 49, in func2
func()
File "a.py", line 43, in func
raise Exception('Dummy')
Exception: Dummy
Обратите внимание, что объекты faux-traceback полностью пригодны для интроспекции - отображение локальных переменных или как аргумент pdb.post_mortem()
- потому что они содержат ссылки на реальные кадры стека.
Ответ 2
Трассировка стека собирается, когда исключение пузырится вверх. Поэтому вы должны распечатать трассировку поверх нужного стека:
import traceback
def func():
raise Exception('Dummy')
def func2():
func()
try:
func2()
except:
traceback.print_exc()
Ответ 3
Я написал модуль, который пишет более полную трассировку
Модуль здесь документация docs
(также вы можете получить модуль из pypi
sudo pip install pd
)
Для исключения catch и pring выполните следующее:
import pd
try:
<python code>
except BaseException:
pd.print_exception_ex( follow_objects = 1 )
Трассировка стека выглядит следующим образом:
Exception: got it
#1 def kuku2(self = {'a': 42, 'b': [1, 2, 3, 4]}, depth = 1) at t test_pd.py:29
Calls next frame at:
raise Exception('got it') at: test_pd.py:29
#2 def kuku2(self = {'a': 42, 'b': [1, 2, 3, 4]}, depth = 2) at test_pd.py:28
Calls next frame at:
self.kuku2( depth - 1 ) at: test_pd.py:28
#3 def kuku2(self = {'a': 42, 'b': [1, 2, 3, 4]}, depth = 3) at test_pd.py:28
Calls next frame at:
self.kuku2( depth - 1 ) at: test_pd.py:28
#4 def kuku2(self = {'a': 42, 'b': [1, 2, 3, 4]}, depth = 4) at test_pd.py:28
Calls next frame at:
self.kuku2( depth - 1 ) at: test_pd.py:28
#5 def kuku2(self = {'a': 42, 'b': [1, 2, 3, 4]}, depth = 5) at test_pd.py:28
Calls next frame at:
self.kuku2( depth - 1 ) at: test_pd.py:28
#6 def kuku2(self = {'a': 42, 'b': [1, 2, 3, 4]}, depth = 6) at test_pd.py:28
Calls next frame at:
self.kuku2( depth - 1 ) at: test_pd.py:28
#7 def main() at test_pd.py:44
Local variables:
n = {'a': 42, 'b': [1, 2, 3, 4]}
Calls next frame at:
pd.print_exception_ex( follow_objects = 1 ) at: test_pd.py:44
follow_objects = 0 не будет печатать содержимое объекта (со сложными структурами данных follow_objects может занять много времени).
Ответ 4
Это основано на ответе @user4815162342, но немного более минималистично:
import sys
import collections
FauxTb = collections.namedtuple("FauxTb", ["tb_frame", "tb_lineno", "tb_next"])
def full_exc_info():
"""Like sys.exc_info, but includes the full traceback."""
t, v, tb = sys.exc_info()
f = sys._getframe(2)
while f is not None:
tb = FauxTb(f, f.f_lineno, tb)
f = f.f_back
return t, v, tb
Это позволяет избежать создания фиктивного исключения за счет необходимости использования sys._getframe()
. Предполагается, что используется условие except
, где было поймано исключение, так как оно поднимается до стековых фреймов (full_exc_info
и функции, которая вызывает full_exc_info
) - это будет функция, которая вызывает код повышения, и как такое уже включено в оригинальную трассировку).
Это дает тот же вывод, что и код в ответе пользователя 4815162342.
Если вы не возражаете против небольших различий в форматировании, вы также можете использовать
import logging
def func():
try:
raise Exception('Dummy')
except:
logging.exception("Something awful happened!", stack_info=True)
def func2():
func()
func2()
что приводит к
ERROR:root:Something awful happened!
Traceback (most recent call last):
File "test.py", line 5, in func
raise Exception('Dummy')
Exception: Dummy
Stack (most recent call last):
File "test.py", line 12, in <module>
func2()
File "test.py", line 10, in func2
func()
File "test.py", line 7, in func
logging.exception("Something awful happened!", stack_info=True)
В этом случае вы проследите от попытки до исключения и секунды от корневого вызова до местоположения вызова регистрации.
Ответ 5
Там есть еще информация, которая может быть извлечена из трассировки, и я иногда предпочитаю более аккуратную, более "логическую" информацию вместо многострочного blob с файлами, номерами строк и фрагментами кода, полученными с помощью traceback. Предпочтительно, одна строка должна сказать все основные моменты.
Для этого я использую следующую функцию:
def raising_code_info():
code_info = ''
try:
frames = inspect.trace()
if(len(frames)):
full_method_name = frames[0][4][0].rstrip('\n\r').strip()
line_number = frames[1][2]
module_name = frames[0][0].f_globals['__name__']
if(module_name == '__main__'):
module_name = os.path.basename(sys.argv[0]).replace('.py','')
class_name = ''
obj_name_dot_method = full_method_name.split('.', 1)
if len(obj_name_dot_method) > 1:
obj_name, full_method_name = obj_name_dot_method
try:
class_name = frames[0][0].f_locals[obj_name].__class__.__name__
except:
pass
method_name = module_name + '.'
if len(class_name) > 0:
method_name += class_name + '.'
method_name += full_method_name
code_info = '%s, line %d' % (method_name, line_number)
finally:
del frames
sys.exc_clear()
return code_info
Он дает. и номер строки, например:
(пример имени модуля: test.py):
(line 73:)
def function1():
print 1/0
class AClass(object):
def method2(self):
a = []
a[3] = 1
def try_it_out():
# try it with a function
try:
function1()
except Exception, what:
print '%s: \"%s\"' % (raising_code_info(), what)
# try it with a method
try:
my_obj_name = AClass()
my_obj_name.method2()
except Exception, what:
print '%s: \"%s\"' % (raising_code_info(), what)
if __name__ == '__main__':
try_it_out()
test.function1(), line 75: "integer division or modulo by zero"
test.AClass.method2(), line 80: "list assignment index out of range"
Что может быть немного более аккуратным в некоторых случаях использования.