Как распечатать полную трассировку без остановки программы?

781

Я пишу программу, которая анализирует 10 веб-сайтов, находит файлы данных, сохраняет файлы, а затем анализирует их, чтобы получить данные, которые можно легко использовать в библиотеке NumPy. Есть тонны ошибок этого файл встречает через плохие ссылки, плохо сформированный XML, недостающие записи и другие вещи , которые я еще категоризировать. Я изначально сделал эту программу для обработки ошибок, как это:

try:
    do_stuff()
except:
    pass

Но теперь я хочу регистрировать ошибки:

try:
    do_stuff()
except Exception, err:
    print Exception, err

Обратите внимание, что это печать в файл журнала для последующего просмотра. Это обычно печатает очень бесполезные данные. Я хочу напечатать точно такие же строки, которые печатаются, когда ошибка срабатывает без попытки, за исключением перехвата исключения, но я не хочу, чтобы это останавливало мою программу, поскольку она вложена в серию циклов for, которые я хотел бы смотреть до завершения.

chriscauley
источник

Ответы:

585

Некоторые другие ответы уже указали на модуль трассировки .

Обратите внимание, что print_excв некоторых случаях вы не получите того, чего ожидаете. В Python 2.x:

import traceback

try:
    raise TypeError("Oups!")
except Exception, err:
    try:
        raise TypeError("Again !?!")
    except:
        pass

    traceback.print_exc()

... отобразит трассировку последнего исключения:

Traceback (most recent call last):
  File "e.py", line 7, in <module>
    raise TypeError("Again !?!")
TypeError: Again !?!

Если вам действительно нужен доступ к исходной трассировке, одним из решений является кэширование информации об исключении, возвращенной из exc_infoлокальной переменной, и ее отображение с помощью print_exception:

import traceback
import sys

try:
    raise TypeError("Oups!")
except Exception, err:
    try:
        exc_info = sys.exc_info()

        # do you usefull stuff here
        # (potentially raising an exception)
        try:
            raise TypeError("Again !?!")
        except:
            pass
        # end of useful stuff


    finally:
        # Display the *original* exception
        traceback.print_exception(*exc_info)
        del exc_info

Производство:

Traceback (most recent call last):
  File "t.py", line 6, in <module>
    raise TypeError("Oups!")
TypeError: Oups!

Хотя есть несколько подводных камней с этим:

  • Из документа sys_info:

    Присвоение возвращаемого значения traceback локальной переменной в функции, которая обрабатывает исключение, вызовет циклическую ссылку . Это предотвратит сбор мусора, на который ссылается локальная переменная в той же функции или трассировка. [...] Если вам нужна трассировка, обязательно удалите ее после использования (лучше всего делать с помощью оператора try ... finally)

  • но из того же документа:

    Начиная с Python 2.2, такие циклы автоматически восстанавливаются, когда сборка мусора включена, и они становятся недоступными, но остается более эффективным избегать создания циклов.


С другой стороны, предоставляя вам доступ к трассировке, связанной с исключением, Python 3 дает менее удивительный результат:

import traceback

try:
    raise TypeError("Oups!")
except Exception as err:
    try:
        raise TypeError("Again !?!")
    except:
        pass

    traceback.print_tb(err.__traceback__)

... отобразит:

  File "e3.py", line 4, in <module>
    raise TypeError("Oups!")
Сильвен Леру
источник
711

traceback.format_exc()или sys.exc_info()даст больше информации, если вы этого хотите.

import traceback
import sys

try:
    do_stuff()
except Exception:
    print(traceback.format_exc())
    # or
    print(sys.exc_info()[2])
volting
источник
1
print(sys.exc_info()[0]отпечатки <class 'Exception'>.
weberc2
2
не используйте exc ... traceback содержит всю информацию stackoverflow.com/questions/4564559/…
qrtLs
258

Если вы отлаживаете и хотите видеть текущую трассировку стека, вы можете просто вызвать:

traceback.print_stack()

Нет необходимости вручную вызывать исключение, чтобы просто перехватить его.

dimo414
источник
9
Модуль traceback делает именно это - вызывает и ловит исключение.
pppery
3
По умолчанию вывод идет в STDERR. Не появлялся в моих журналах, потому что он был перенаправлен куда-то еще.
mpen
101

Как распечатать полную трассировку без остановки программы?

Если вы не хотите останавливать свою программу из-за ошибки, вам нужно обработать эту ошибку с помощью try / кроме:

try:
    do_something_that_might_error()
except Exception as error:
    handle_the_error(error)

Для извлечения полной трассировки мы будем использовать tracebackмодуль из стандартной библиотеки:

import traceback

И создать довольно сложную трассировку стека, чтобы продемонстрировать, что мы получаем полную трассировку стека:

def raise_error():
    raise RuntimeError('something bad happened!')

def do_something_that_might_error():
    raise_error()

печать

Чтобы распечатать полную трассировку, используйте traceback.print_excметод:

try:
    do_something_that_might_error()
except Exception as error:
    traceback.print_exc()

Какие отпечатки:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

Лучше, чем печать, регистрация:

Однако лучше всего настроить регистратор для вашего модуля. Он будет знать имя модуля и сможет изменять уровни (среди других атрибутов, таких как обработчики)

import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

В этом случае вам понадобится logger.exceptionфункция вместо:

try:
    do_something_that_might_error()
except Exception as error:
    logger.exception(error)

Какие журналы:

ERROR:__main__:something bad happened!
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

Или, возможно, вам просто нужна строка, в этом случае вам понадобится traceback.format_excфункция:

try:
    do_something_that_might_error()
except Exception as error:
    logger.debug(traceback.format_exc())

Какие журналы:

DEBUG:__main__:Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

Вывод

И для всех трех опций мы видим, что получаем тот же результат, что и при появлении ошибки:

>>> do_something_that_might_error()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!
Аарон Холл
источник
2
как было сказано выше и для меня тоже, traceback.print_exc()возвращает только последний вызов: как вам удастся вернуть несколько уровней стека (и, возможно, все уровни?)
herve-
@geekobi Я не уверен, что вы спрашиваете здесь. Я демонстрирую, что мы получаем трассировку до точки входа программы / интерпретатора. Что тебе не понятно?
Аарон Холл
1
@Geekobi говорит, что если вы поймаете и поднимете рейз, traceback.print_exc () просто вернет стек ре-рейза, а не оригинальный стек.
физлоки
@fizloki как ты "ререйз"? Вы делаете raiseцепочку голых или исключений, или вы скрываете первоначальную трассировку? см. stackoverflow.com/questions/2052390/…
Аарон Холл
20

Во- первых, не используйте prints для протоколирования, там нестабильна, проверенная и хорошо продуманный модуль STDLIB , чтобы сделать это: logging. Вы определенно должны использовать это вместо этого.

Во-вторых, не поддавайтесь соблазну создавать беспорядок с не связанными инструментами, когда есть нативный и простой подход. Вот:

log = logging.getLogger(__name__)

try:
    call_code_that_fails()
except MyError:
    log.exception('Any extra info you want to see in your logs')

Вот и все. Вы сделали сейчас.

Объяснение для всех, кто интересуется, как все работает под капотом

Что log.exceptionна самом деле делает, так это просто вызов log.error(то есть регистрирует событие с уровнем ERROR) и затем вывод трассировки.

Почему лучше?

Ну, вот некоторые соображения:

  • это просто правильно ;
  • это просто;
  • это просто.

Почему никто не должен использовать tracebackили вызывать регистратор с exc_info=Trueили грязные руки sys.exc_info?

Ну просто потому что! Все они существуют для разных целей. Например, traceback.print_excвыходные данные немного отличаются от обратных трассировок, производимых самим интерпретатором. Если вы используете его, вы запутаете любого, кто читает ваши журналы, они будут бить себя против них.

Переход exc_info=Trueна входящие звонки просто неуместен. Но это полезно при отлове восстанавливаемых ошибок, и вы хотите регистрировать их (используя, например, INFOуровень) также с помощью обратных трассировок, потому что log.exceptionсоздает журналы только одного уровня - ERROR.

И вам определенно следует избегать возиться с sys.exc_infoкак можно большим количеством. Это просто не публичный интерфейс, а внутренний - вы можете использовать его, если точно знаете, что делаете. Он не предназначен только для печати исключений.

вздор
источник
4
Это также не работает как есть. Это не так. Я еще не закончил: этот ответ просто теряет время.
A.
Я также добавил бы, что вы можете просто сделать logging.exception(). Нет необходимости создавать экземпляр журнала, если у вас нет особых требований.
Шиталь Шах
9

В дополнение к ответу @Aaron Hall, если вы регистрируетесь, но не хотите использовать logging.exception()(так как он регистрирует на уровне ERROR), вы можете использовать более низкий уровень и пройти exc_info=True. например

try:
    do_something_that_might_error()
except Exception:
    logger.info('General exception noted.', exc_info=True)
Марк Макдональд
источник
7

Чтобы получить точную трассировку стека в виде строки, которая была бы вызвана, если бы не было попытки / кроме того, чтобы перешагнуть через нее, просто поместите ее в блок исключения, который перехватывает ошибочное исключение.

desired_trace = traceback.format_exc(sys.exc_info())

Вот как это использовать (при условии, что flaky_funcоно определено и logвызывает вашу любимую систему регистрации):

import traceback
import sys

try:
    flaky_func()
except KeyboardInterrupt:
    raise
except Exception:
    desired_trace = traceback.format_exc(sys.exc_info())
    log(desired_trace)

Это хорошая идея, чтобы ловить и повторно поднимать KeyboardInterrupts, так что вы все равно можете убить программу, используя Ctrl-C. Ведение журнала выходит за рамки вопроса, но хорошим вариантом является ведение журнала . Документация для модулей sys и traceback .

Эдвард Ньюэлл
источник
4
Это не работает в Python 3 и должно быть изменено на desired_trace = traceback.format_exc(). Передача sys.exc_info()в качестве аргумента никогда не была правильной вещью, но игнорируется в Python 2, но не в Python 3 (в любом случае, 3.6.4).
Мартино
2
KeyboardInterruptне происходит (прямо или косвенно) от Exception. (Оба получены из BaseException.) Это означает, except Exception:что никогда не поймает a KeyboardInterrupt, и, следовательно, except KeyboardInterrupt: raiseсовершенно не нужно.
AJNeufeld
traceback.format_exc(sys.exc_info())у меня не работает с питоном 3.6.10
Nam G VU
6

Вам нужно будет поместить попытку / исключение в самую внутреннюю петлю, где может возникнуть ошибка, т.е.

for i in something:
    for j in somethingelse:
        for k in whatever:
            try:
                something_complex(i, j, k)
            except Exception, e:
                print e
        try:
            something_less_complex(i, j)
        except Exception, e:
            print e

... и так далее

Другими словами, вам нужно будет обернуть операторы, которые могут потерпеть неудачу в try /, за исключением как можно более конкретных, в максимально возможной внутренней петле.

Иво ван дер Вейк
источник
6

Замечание по поводу комментариев этого ответа : print(traceback.format_exc())делает для меня лучшую работу, чем traceback.print_exc(). Что helloкасается последнего, то иногда странным образом «смешивается» с текстом трассировки, например, если оба хотят одновременно писать в stdout или stderr, производя странный вывод (по крайней мере, при сборке из текстового редактора и просмотре вывода в Панель «Результаты сборки»).

Трассировка (последний вызов был последним):
Файл «C: \ Users \ Пользователь \ Рабочий стол \ test.py», строка 7, в
аду do_stuff ()
Файл «C: \ Users \ Пользователь \ Рабочий стол \ test.py», строка 4 , в do_stuff
1/0
ZeroDivisionError: целочисленное деление или по модулю на ноль
o
[Завершено за 0,1 с]

Поэтому я использую:

import traceback, sys

def do_stuff():
    1/0

try:
    do_stuff()
except Exception:
    print(traceback.format_exc())
    print('hello')
Basj
источник
5

Я не вижу это упомянутое ни в одном из других ответов. Если вы передаете объект Exception по любой причине ...

В Python 3.5+ вы можете получить трассировку от объекта Exception, используя traceback.TracebackException.from_exception () . Например:

import traceback


def stack_lvl_3():
    raise Exception('a1', 'b2', 'c3')


def stack_lvl_2():
    try:
        stack_lvl_3()
    except Exception as e:
        # raise
        return e


def stack_lvl_1():
    e = stack_lvl_2()
    return e

e = stack_lvl_1()

tb1 = traceback.TracebackException.from_exception(e)
print(''.join(tb1.format()))

Однако приведенный выше код приводит к:

Traceback (most recent call last):
  File "exc.py", line 10, in stack_lvl_2
    stack_lvl_3()
  File "exc.py", line 5, in stack_lvl_3
    raise Exception('a1', 'b2', 'c3')
Exception: ('a1', 'b2', 'c3')

Это всего лишь два уровня стека, в отличие от того, что было бы напечатано на экране, если бы исключение было поднято stack_lvl_2()и не было перехвачено (раскомментируйте# raise строку).

Насколько я понимаю, это связано с тем, что stack_lvl_3()в данном случае исключение записывает только текущий уровень стека при его повышении . Когда он проходит обратно через стек, к нему добавляется больше уровней __traceback__. Но мы перехватили его stack_lvl_2(), то есть все, что нужно было записать, это уровни 3 и 2. Чтобы получить полную трассировку, напечатанную на stdout, нам нужно поймать ее на самом высоком (самом низком?) Уровне:

import traceback


def stack_lvl_3():
    raise Exception('a1', 'b2', 'c3')


def stack_lvl_2():
    stack_lvl_3()


def stack_lvl_1():
    stack_lvl_2()


try:
    stack_lvl_1()
except Exception as exc:
    tb = traceback.TracebackException.from_exception(exc)

print('Handled at stack lvl 0')
print(''.join(tb.stack.format()))

Что приводит к:

Handled at stack lvl 0
  File "exc.py", line 17, in <module>
    stack_lvl_1()
  File "exc.py", line 13, in stack_lvl_1
    stack_lvl_2()
  File "exc.py", line 9, in stack_lvl_2
    stack_lvl_3()
  File "exc.py", line 5, in stack_lvl_3
    raise Exception('a1', 'b2', 'c3')

Обратите внимание, что печать стека отличается, первая и последняя строки отсутствуют. Потому что это другоеformat() .

Перехват исключения как можно дальше от того места, где оно было вызвано, упрощает код, а также дает больше информации.

bgdnlp
источник
Это намного лучше, чем в предыдущем методе (ах), но все еще смехотворно запутано, просто распечатывая трассировку стека. Java занимает меньше кода FGS.
elhefe
4

Получить полную трассировку в виде строки из объекта исключения с traceback.format_exception

Если у вас есть только объект исключения, вы можете получить обратную трассировку в виде строки из любой точки кода в Python 3 с помощью:

import traceback

''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))

Полный пример:

#!/usr/bin/env python3

import traceback

def f():
    g()

def g():
    raise Exception('asdf')

try:
    g()
except Exception as e:
    exc = e

tb_str = ''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))
print(tb_str)

Вывод:

Traceback (most recent call last):
  File "./main.py", line 12, in <module>
    g()
  File "./main.py", line 9, in g
    raise Exception('asdf')
Exception: asdf

Документация: https://docs.python.org/3.7/library/traceback.html#traceback.format_exception

См. Также: извлечение информации трассировки из объекта исключения.

Протестировано в Python 3.7.3.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
источник
3

Вы хотите модуль трассировки . Это позволит вам печатать дампы стека, как это обычно делает Python. В частности, функция print_last напечатает последнее исключение и трассировку стека.

nmichaels
источник
2

Если у вас уже есть объект Error, и вы хотите напечатать все это, вам нужно сделать этот слегка неловкий вызов:

import traceback
traceback.print_exception(type(err), err, err.__traceback__)

Это верно, print_exceptionзанимает три позиционных аргумента: тип исключения, фактический объект исключения и собственное внутреннее свойство трассировки исключения.

В python 3.5 или более поздних версиях type(err)является необязательным ... но это позиционный аргумент, поэтому вам все равно придется явно передать None вместо него.

traceback.print_exception(None, err, err.__traceback__)

Я понятия не имею, почему все это не просто traceback.print_exception(err). Почему вы когда-нибудь захотите распечатать ошибку вместе с трассировкой, отличной от той, которая принадлежит этой ошибке, мне не понятно.

nupanick
источник