Як записати помилку Python з інформацією про налагодження?


468

Я друкую повідомлення про виключення Python у файл журналу за допомогою logging.error:

import logging
try:
    1/0
except ZeroDivisionError as e:
    logging.error(e)  # ERROR:root:division by zero

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

Відповіді:


733

logger.exception видасть слід стека поряд із повідомленням про помилку.

Наприклад:

import logging
try:
    1/0
except ZeroDivisionError as e:
    logging.exception("message")

Вихід:

ERROR:root:message
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero

@Paulo Перевірте зауваження, "майте на увазі, що в Python 3 ви повинні викликати logging.exceptionметод просто всередині exceptчастини. Якщо ви називаєте цей метод у довільному місці, ви можете отримати химерне виключення. Документи попереджають про це".


131
exceptionМетод просто викликає error(message, exc_info=1). Як тільки ви перейдете exc_infoдо будь-якого з методів реєстрації даних із контексту винятку, ви отримаєте прослідкування.
Гельмут Гроне

16
Ви також можете встановити sys.excepthook(див. Тут ), щоб уникнути необхідності загортати весь код у спробі / за винятком.
Липень

23
Ви могли просто написати, except Exception:тому що не використовуєте eжодним чином;)
Марко Феррарі

21
Ви можете дуже добре перевірити, eнамагаючись інтерактивно відлагоджувати код. :) Ось чому я завжди включаю його.
Вікі Лейдлер

4
Виправте мене, якщо я помиляюся, у цьому випадку виняток не реально поводиться, і тому є сенс додати raiseв кінці exceptобласті. Інакше біг триватиме так, ніби все було добре.
Dror

184

Одне приємне, logging.exceptionщо відповідь SiggyF не показує, це те, що ви можете передати довільне повідомлення, і при реєстрації все ще буде показано повне прослідкування з усіма деталями виключень:

import logging
try:
    1/0
except ZeroDivisionError:
    logging.exception("Deliberate divide by zero traceback")

З поведінкою за замовчуванням (в останніх версіях) реєстрації лише помилок друку на sys.stderr, це виглядає приблизно так:

>>> import logging
>>> try:
...     1/0
... except ZeroDivisionError:
...     logging.exception("Deliberate divide by zero traceback")
... 
ERROR:root:Deliberate divide by zero traceback
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero

Чи можна реєструвати виняток, не надаючи повідомлення?
Стевойсяк

@StevenVascellaro - Я б запропонував вам передати, ''якщо ви дійсно не хочете вводити повідомлення ... функцію не можна викликати хоча б одним аргументом, тому вам доведеться щось надати.
ArtOfWarfare

147

Використання exc_infoпараметрів може бути кращим, щоб ви могли вибрати рівень помилки (якщо ви використовуєте exception, він завжди буде на errorрівні):

try:
    # do something here
except Exception as e:
    logging.critical(e, exc_info=True)  # log exception info at CRITICAL log level

@CivFan: Я насправді не дивився на інші зміни чи вступ до публікації; це вступ також додав сторонній редактор. У видалених коментарях я ніде не бачу, що це колись було наміром, але я можу також відмінити свою редагування та видалити коментарі. Голосування тут було занадто довгим, ніж для відредагованої версії .
Martijn Pieters

Чи logging.fatalє метод у бібліотеці журналів? Я лише бачу critical.
Ян

1
@Ian Це псевдонім critical, як warnі до warning.
0xc0de

35

Цитуючи

Що робити, якщо ваш додаток веде журнал іншим способом - не використовуючи loggingмодуль?

Тепер, tracebackможна використовувати тут.

import traceback

def log_traceback(ex, ex_traceback=None):
    if ex_traceback is None:
        ex_traceback = ex.__traceback__
    tb_lines = [ line.rstrip('\n') for line in
                 traceback.format_exception(ex.__class__, ex, ex_traceback)]
    exception_logger.log(tb_lines)
  • Використовуйте його в Python 2 :

    try:
        # your function call is here
    except Exception as ex:
        _, _, ex_traceback = sys.exc_info()
        log_traceback(ex, ex_traceback)
  • Використовуйте його в Python 3 :

    try:
        x = get_number()
    except Exception as ex:
        log_traceback(ex)

Чому ви розмістили "_, _, ex_traceback = sys.exc_info ()" поза функцією log_traceback і потім передали його як аргумент? Чому б не використовувати його безпосередньо всередині функції?
Василь Муса

@BasilMusa, щоб відповісти на ваше запитання, коротше кажучи, сумісний з Python 3, оскільки ex_tracebackце з- ex.__traceback__під Python 3, але ex_tracebackз- sys.exc_info()під Python 2.
zangw

12

Якщо ви використовуєте прості журнали - все ваші записи журналу повинні відповідати цьому правилу: one record = one line. Дотримуючись цього правила, ви можете використовувати grepта інші інструменти для обробки файлів журналу.

Але інформація про зворотний зворотний зв'язок є багаторядковою. Тож моя відповідь - це розширена версія рішення, запропонована zangw вище в цій темі. Проблема полягає в тому, що рядки прослідковування можуть бути \nвсередині, тому нам потрібно зробити додаткову роботу, щоб позбутися закінчень цього рядка:

import logging


logger = logging.getLogger('your_logger_here')

def log_app_error(e: BaseException, level=logging.ERROR) -> None:
    e_traceback = traceback.format_exception(e.__class__, e, e.__traceback__)
    traceback_lines = []
    for line in [line.rstrip('\n') for line in e_traceback]:
        traceback_lines.extend(line.splitlines())
    logger.log(level, traceback_lines.__str__())

Після цього (коли ви будете аналізувати свої журнали), ви можете скопіювати / вставити необхідні рядки прослідкування з вашого файлу журналу і зробити це:

ex_traceback = ['line 1', 'line 2', ...]
for line in ex_traceback:
    print(line)

Прибуток!


9

Ця відповідь випливає з вищезазначених відмінних.

У більшості програм ви не будете телефонувати logging.exception (e) безпосередньо. Швидше за все, ви визначили спеціальний реєстратор, специфічний для вашої програми чи модуля, такий:

# Set the name of the app or module
my_logger = logging.getLogger('NEM Sequencer')
# Set the log level
my_logger.setLevel(logging.INFO)

# Let's say we want to be fancy and log to a graylog2 log server
graylog_handler = graypy.GELFHandler('some_server_ip', 12201)
graylog_handler.setLevel(logging.INFO)
my_logger.addHandler(graylog_handler)

У цьому випадку просто використовуйте реєстратор для виклику винятку (e) таким чином:

try:
    1/0
except ZeroDivisionError, e:
    my_logger.exception(e)

Це дійсно корисний фінішний штрих, якщо ви хочете виділити реєстратор лише за винятками.
logicOnAbstractions

7

Ви можете записати трасування стека без винятку.

https://docs.python.org/3/library/logging.html#logging.Logger.debug

Другий необов'язковий аргумент ключового слова - stack_info, який за замовчуванням відповідає False. Якщо вірно, інформація про стек додається до повідомлення журналу, включаючи фактичний виклик журналу. Зауважте, що це не та сама інформація про стек, як та, яка відображається через вказівку exc_info: Перша - це кадри стека від нижньої частини стека до виклику журналу в поточному потоці, тоді як остання - це інформація про розмотані кадри, слідкуючи за винятком під час пошуку обробників винятків.

Приклад:

>>> import logging
>>> logging.basicConfig(level=logging.DEBUG)
>>> logging.getLogger().info('This prints the stack', stack_info=True)
INFO:root:This prints the stack
Stack (most recent call last):
  File "<stdin>", line 1, in <module>
>>>

5

Трохи декоративної обробки (дуже вільно натхненна монадою "Можливо" та "ліфтинг". Ви можете безпечно видаляти анотації типу Python 3.6 та використовувати старіший стиль форматування повідомлень.

fallible.py

from functools import wraps
from typing import Callable, TypeVar, Optional
import logging


A = TypeVar('A')


def fallible(*exceptions, logger=None) \
        -> Callable[[Callable[..., A]], Callable[..., Optional[A]]]:
    """
    :param exceptions: a list of exceptions to catch
    :param logger: pass a custom logger; None means the default logger, 
                   False disables logging altogether.
    """
    def fwrap(f: Callable[..., A]) -> Callable[..., Optional[A]]:

        @wraps(f)
        def wrapped(*args, **kwargs):
            try:
                return f(*args, **kwargs)
            except exceptions:
                message = f'called {f} with *args={args} and **kwargs={kwargs}'
                if logger:
                    logger.exception(message)
                if logger is None:
                    logging.exception(message)
                return None

        return wrapped

    return fwrap

Демонстрація:

In [1] from fallible import fallible

In [2]: @fallible(ArithmeticError)
    ...: def div(a, b):
    ...:     return a / b
    ...: 
    ...: 

In [3]: div(1, 2)
Out[3]: 0.5

In [4]: res = div(1, 0)
ERROR:root:called <function div at 0x10d3c6ae8> with *args=(1, 0) and **kwargs={}
Traceback (most recent call last):
  File "/Users/user/fallible.py", line 17, in wrapped
    return f(*args, **kwargs)
  File "<ipython-input-17-e056bd886b5c>", line 3, in div
    return a / b

In [5]: repr(res)
'None'

Ви також можете змінити це рішення, щоб повернути щось трохи більш значуще, ніж Noneіз exceptчастини (або навіть зробити рішення загальним, вказавши це повернене значення в fallibleаргументах s).


0

У своєму модулі реєстрації (якщо спеціальний модуль) просто увімкніть stack_info.

api_logger.exceptionLog("*Input your Custom error message*",stack_info=True)

-1

Якщо ви можете впоратися з додатковою залежністю, тоді використовуйте twisted.log, вам не потрібно явно входити в журнал помилок, а також він повертає весь файл сліду та часу у файл чи потік.


8
Можливо twisted, це хороша рекомендація, але ця відповідь насправді не дуже сприяє. Він не говорить про те, як користуватися twisted.log, ні які переваги він має над loggingмодулем зі стандартної бібліотеки, а також не пояснює, що означає "не потрібно явно вмикати помилки" .
Марк Амері

-8

Чистий спосіб зробити це за допомогою, format_exc()а потім проаналізувати вихід, щоб отримати відповідну частину:

from traceback import format_exc

try:
    1/0
except Exception:
    print 'the relevant part is: '+format_exc().split('\n')[-2]

З повагою


4
Так? Чому це "відповідна частина" ? Все .split('\n')[-2]це - викинути номер рядка та відслідковувати результат від format_exc()- корисної інформації, яку ви зазвичай хочете! Більше того, це навіть не дуже добре справляється з цим ; якщо повідомлення про виключення містить новий рядок, тоді цей підхід буде надрукувати лише остаточний рядок повідомлення про виняток - це означає, що ви втрачаєте клас винятку та більшість повідомлень про виняток поверх втрати прослідковування. -1.
Марк Амері
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.