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

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

try:
    do_stuff()
except:
    pass

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

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

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

Ответ 1

В другом ответе уже указан модуль traceback.

Обратите внимание, что при 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:

    Присвоение возвращаемого значения трассировки локальной переменной в функции, обрабатывающей исключение, вызовет круговую ссылку. Это предотвратит что-либо, на которое ссылается локальная переменная в той же функции, или трассировка от сбора мусора. [...] Если вам нужна трассировка, обязательно удалите ее после использования (лучше всего сделать с помощью инструкции 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!")

Ответ 2

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])

Ответ 3

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

traceback.print_stack()

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

Ответ 4

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

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

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!

Ответ 5

Во-первых, не используйте print для ведения журнала, для этого есть нестабильный, проверенный и продуманный модуль 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 как можно больше. Это просто не публичный интерфейс, а внутренний - вы можете использовать его, если точно знаете, что делаете. Он не предназначен только для печати исключений.

Ответ 6

В дополнение к ответу @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

Чтобы получить точную трассировку стека, как строку, которая была бы поднята, если бы try/except не был над ней, просто поместите это в блок исключений, который улавливает исключение.

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)

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

Ответ 8

Вам нужно будет поставить try/except внутри самой внутренней паузы, где может произойти ошибка, то есть

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/except, насколько это возможно, в максимально возможном внутреннем цикле.

Ответ 9

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

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

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

import traceback, sys

def do_stuff():
    1/0

try:
    do_stuff()
except Exception:
    print(traceback.format_exc())
    print('hello')

Ответ 10

Я не вижу это упомянутое ни в одном из других ответов. Если вы передаете объект 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().

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

Ответ 11

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

Ответ 12

Получить полную трассировку в виде строки из объекта исключения с помощью 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.