Як надрукувати повний трекбек без зупинки програми?


778

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

try:
    do_stuff()
except:
    pass

Але тепер я хочу зареєструвати помилки:

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

Зверніть увагу, що це друк у файл журналу для подальшого перегляду. Зазвичай це друкує дуже марні дані. Те, що я хочу, - це надрукувати ці ж рядки, надруковані, коли помилка спрацьовує без спроби, за винятком перехоплення винятку, але я не хочу, щоб вона зупиняла мою програму, оскільки вона вкладена в ряд циклів, які я хотів би зробити до завершення.

Відповіді:


582

Деякі інші відповіді вже вказали на модуль відстеження .

Зауважте, що 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:

    Присвоєння значення зворотного відстеження локальній змінній у функції, що обробляє виняток, призведе до кругової посилання . Це запобіжить збиранню сміття будь-яким посиланням на локальну змінну в тій самій функції або шляхом зворотного відстеження. [...] Якщо вам потрібен метод зворотного пошуку, обов'язково видаліть його після використання (найкраще це зробити із спробою ... нарешті заявою)

  • але, від того ж документа:

    Починаючи з 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!")

709

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

1
print(sys.exc_info()[0]відбитки <class 'Exception'>.
weberc2

2
Dont використання отл ... відслідковує містить всю інформацію stackoverflow.com/questions/4564559 / ...
qrtLs

258

Якщо ви налагоджуєте і просто хочете побачити поточний слід стека, ви можете просто зателефонувати:

traceback.print_stack()

Немає потреби вручну піднімати виняток, щоб знову його зловити.


9
Модуль traceback робить саме це - піднімає та виловлює виняток.
pppery

3
Вихідні дані переходять до STDERR за замовчуванням BTW. Не відображалася в моїх журналах, оскільки її переспрямовували кудись ще.
mpen

101

Як надрукувати повний трекбек без зупинки програми?

Якщо ви не хочете зупиняти програму на помилці, вам потрібно обробити цю помилку за допомогою спроби / за винятком:

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()повертається лише останній дзвінок: як вам вдасться повернути кілька рівнів стека (а можливо, і все levele s?)
herve-guerin

@geekobi Я не впевнений, що ти тут питаєш. Я демонструю, що ми отримуємо відстеження до точки входу програми / інтерпретатора. Що вам не зрозуміло?
Аарон Холл

1
Що говорить @geekobi, якщо ви ловите та повторно піднімаєте, traceback.print_exc () поверне стек повторного підйому, а не початковий стек.
фізлоки

@fizloki як ти "рейрінг"? Ви робите голий raiseчи виняток, чи приховуєте оригінальний трекбек? дивіться stackoverflow.com/questions/2052390/…
Aaron Hall

21

По- перше, не використовуйте 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
Він також не працює як є. Це не все. Я не закінчую зараз: ця відповідь просто витрачає час.
А.

Я також додам, що ви просто можете це зробити logging.exception(). Не потрібно створювати примірник журналу, якщо у вас немає спеціальних вимог.
Shital Shah

9

На додаток до відповіді @Aaron Hall, якщо ви ведете журнал, але не хочете користуватися logging.exception()(оскільки він реєструється на рівні ПОМИЛКИ), ви можете використовувати нижчий рівень та пройти 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 та trackback .


4
Це не працює в Python 3, і його потрібно змінити desired_trace = traceback.format_exc(). Передача sys.exc_info()аргументу ніколи не була правильною справою, але в Python 2 мовчки ігнорується, але не в Python 3 (так чи інакше 3.6.4).
мартіно

2
KeyboardInterruptне походить (прямо чи опосередковано) з Exception. (Обидва є похідними від BaseException.) Це означає except Exception:, що ніколи не спіймає KeyboardInterrupt, і, отже except KeyboardInterrupt: raise, абсолютно непотрібне.
AJNeufeld

traceback.format_exc(sys.exc_info())не працює для мене з python 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

... і так далі

Іншими словами, вам потрібно буде обернути твердження, які можуть бути невдалі при спробі / за винятком максимально конкретних, у самій внутрішній циклі.


6

Зауваження до коментарів цієї відповіді : print(traceback.format_exc())це для мене краще, ніж робота traceback.print_exc(). З останнім helloчасом, іноді дивним чином "змішується" з текстом зворотного звороту, наприклад, якщо обидва хочуть писати в stdout або stderr одночасно, отримуючи дивний вихід (принаймні, коли створюється всередині текстового редактора і переглядає вихід у Панель "Збір результатів").

Traceback (останній дзвінок останній):
Файл "C: \ Користувачі \ Користувач \ Настільний \ test.py", рядок 7, у
пеклі do_stuff ()
Файл "C: \ Користувачі \ Користувач \ Настільний \ 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')

5

Я не бачу цього згадування в жодній з інших відповідей. Якщо ви проїжджаєте навколо об'єкта винятку з будь-якої причини ...

У Python 3.5+ ви можете отримати слід від об'єкта винятку за допомогою 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() .

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


Це набагато краще, ніж попередні методи, але все ще смішно складні лише для того, щоб роздрукувати стек-трек. Java бере менше коду FGS.
elhefe

3

Вам потрібен модуль відстеження . Це дозволить вам друкувати скиди стеків, як це робить Python. Зокрема, функція print_last надрукує останній виняток та слід стека.


3

Отримайте повний прослідок у вигляді рядка від об’єкта винятку за допомогою 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.


2

Якщо у вас вже є об’єкт Помилка, і ви хочете надрукувати всю річ, вам потрібно зробити цей злегка незручний дзвінок:

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). Чому ви коли-небудь хочете роздрукувати помилку, а також прослідку, відмінний від того, який належить до цієї помилки, - поза мною.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.