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

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

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

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

Ответы [ 9 ]

610 голосов
/ 04 марта 2011

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 Check notes, "имейте в виду, что в Python 3 вы должны вызывать метод logging.exception только внутри части except. Если вы вызываете этот метод в произвольном месте, вы можете получить странное исключение. Документы предупреждают об этом. "

169 голосов
/ 01 июля 2013

Одна приятная вещь о 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
106 голосов
/ 10 апреля 2015

Использование параметров 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
30 голосов
/ 19 октября 2015

Цитирование

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

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

Этот ответ основан на вышеупомянутых превосходных ответах.

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

Немного декораторской обработки (очень слабо вдохновлено монадой и подъемом 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 голосов
/ 20 февраля 2013

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

from traceback import format_exc

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

Привет

0 голосов
/ 04 марта 2011

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

...