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

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

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

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

Ответ 1

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 части. Если вы вызываете этот метод в произвольном месте вы можете получить причудливое исключение. Документы оповещения об этом."

Ответ 2

Одна хорошая вещь в logging.exception, которую не показывает ответ SiggyF, заключается в том, что вы можете передать произвольное сообщение, и при ведении журнала все равно будет отображаться полный возврат со всеми деталями исключения:

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

По умолчанию (в последних версиях) ведение журнала просто sys.stderr ошибки в 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

Ответ 3

Использование параметров exc_info может быть лучше, чтобы позволить вам выбрать уровень ошибки (если вы используете exception, оно всегда будет отображать error):

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

Ответ 4

Цитата

Что делать, если ваше приложение регистрируется каким-либо другим способом - не используя модуль 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)
    

Ответ 5

Если вы используете простые журналы - все ваши записи журнала должны соответствовать этому правилу: 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)

Profit!

Ответ 6

Этот ответ строится из вышеупомянутых отличных.

В большинстве приложений вы не будете вызывать 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 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)

Ответ 7

Немного декораторской обработки (очень слабо вдохновлено монадой 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 аргументах).

Ответ 8

Если вы можете справиться с дополнительной зависимостью, используйте twisted.log, вам не нужно явно регистрировать ошибки, а также возвращает всю трассировку и время на файл или поток.

Ответ 9

Чистый способ сделать это - использовать format_exc(), а затем проанализировать вывод, чтобы получить соответствующую часть:

from traceback import format_exc

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

Привет