Как мне зарегистрировать ошибку Python с отладочной информацией?


470

Я печатаю сообщения об исключениях Python в файл журнала с logging.error:

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

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

Ответы:


735

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(см. Здесь ), чтобы избежать необходимости переносить весь код в try / кроме.
июля

23
Вы можете просто написать, except Exception:потому что вы не используете eв любом случае;)
Марко Феррари

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

4
Поправьте меня, если я ошибаюсь, в данном случае нет реальной обработки исключения, и поэтому имеет смысл добавить raiseв конец области exceptвидимости. В противном случае работа продолжится, как будто все в порядке.
Дрор

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: На самом деле я не смотрел другие правки или вступление к публикации; это вступление было также добавлено сторонним редактором. В удаленных комментариях я нигде не вижу, что это когда-либо было намерением, но я могу также отменить свое редактирование и удалить комментарии, это было слишком долго, чтобы голосование здесь было для чего-то кроме отредактированной версии ,
Мартин Питерс

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

Немного декораторской обработки (очень слабо вдохновлено монадой Maybe и лифтингом). Вы можете безопасно удалять аннотации типа 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аргументах).


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.