Как зарегистрировать ошибку в Python с отладочной информацией?
Я записываю сообщения об ошибках Python в лог-файл с помощью функции logging.error:
import logging
try:
    1/0
except ZeroDivisionError as e:
    logging.error(e)  # ERROR:root:division by zero
Можно ли вывести более подробную информацию об исключении и коде, который его вызвал, чем просто строку исключения? Такие данные, как номера строк или трассировки стека, были бы очень полезны.
5 ответ(ов)
Использование параметра exc_info может быть более предпочтительным, так как он позволяет выбрать уровень ошибки (если вы используете exception, он всегда будет на уровне error):
try:
    # выполнить какое-то действие здесь
except Exception as e:
    logging.critical(e, exc_info=True)  # логируем информацию об исключении на уровне CRITICAL
Да, вы можете записать стек вызовов без исключения, используя параметр stack_info в методах журналирования. По умолчанию этот параметр установлен в False, но если установить его в True, информация о стеке добавляется к сообщению журнала.
Важно отметить, что эта информация о стеке отличается от той, которую вы получаете при использовании параметра exc_info: первая показывает фреймы стека от самого низкого уровня до вызова журналирования в текущем потоке, тогда как вторая содержит информацию о фреймах стека, которые были развёрнуты в результате исключения, когда вы ищете обработчики исключений.
Вот пример использования:
import logging
logging.basicConfig(level=logging.DEBUG)
logging.getLogger().info('Это выводит стек', stack_info=True)
Если вы выполните этот код, вы увидите сообщение журнала вместе со стеком вызовов, что поможет вам понять, откуда именно был выполнен вызов журналирования.
С начала Python 3.5 появилась возможность явно указывать исключение в функции логирования:
try:
    1 / 0
except Exception as ex:
    logging.error("Произошла ошибка", exc_info=ex)
Функция logging.exception по-прежнему работает, но её необходимо вызывать только в блоке except, и она не позволяет задавать уровень логирования.
Этот ответ основывается на вышеуказанных отличных ответах.
В большинстве приложений вы не будете напрямую вызывать logging.exception(e). Скорее всего, вы определили свой собственный логгер, специфичный для вашего приложения или модуля, вот так:
# Задаем имя приложения или модуля
my_logger = logging.getLogger('NEM Sequencer')
# Устанавливаем уровень логирования
my_logger.setLevel(logging.INFO)
# Предположим, что мы хотим быть немного изящными и отправлять логи на сервер graylog2
graylog_handler = graypy.GELFHandler('some_server_ip', 12201)
graylog_handler.setLevel(logging.INFO)
my_logger.addHandler(graylog_handler)
В этом случае просто используйте логгер для вызова exception(e) следующим образом:
try:
    1/0
except ZeroDivisionError as e:
    my_logger.exception(e)
Обратите внимание на использование as для захвата исключения. Это более современный и предпочтительный стиль, начиная с Python 3.
Вы можете воспользоваться декоратором fallible для обработки исключений в вашем коде, что позволит вам безопасно обрабатывать ошибки и делать это с помощью логирования. Этот декоратор позволяет указать список исключений, которые он будет перехватывать, и опционально использовать пользовательский логгер.
Вот как это работает:
fallible.py
from functools import wraps
import logging
def fallible(*exceptions, logger=None):
    """
    :param exceptions: список исключений для перехвата
    :param logger: передайте пользовательский логгер; None означает использование стандартного логгера,
                   False отключает логгирование вовсе.
    """
    def fwrap(f):
        @wraps(f)
        def wrapped(*args, **kwargs):
            try:
                return f(*args, **kwargs)
            except exceptions:
                message = 'called {} with *args={} and **kwargs={}'.format(f, args, kwargs)
                if logger:
                    logger.exception(message)
                if logger is None:
                    logging.exception(message)
                return None
        return wrapped
    return fwrap
Пример использования:
from fallible import fallible
@fallible(ArithmeticError)
def div(a, b):
    return a / b
print(div(1, 2))  # Output: 0.5
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
print(repr(res))  # Output: 'None'
В результате, если функция вызовет исключение, вы получите сообщение об ошибке в журнале, а возвращаемое значение будет None. Вы также можете настроить декоратор, чтобы он возвращал более значимое значение вместо None в блоке except, или сделать его универсальным, добавив параметр, который задает это значение.
Используйте этот подход, чтобы обеспечить более надежную обработку ошибок в своем коде!
Сохранение сообщений об исключениях в Python
Настройка логирования в Python: вывод всех сообщений в stdout и файл журнала
Как окрасить вывод логирования Python?
Как заставить логгер удалять существующий файл журнала перед записью в него?
Как использовать UTF-8 в логировании Python?