logging — это уже целый стандарт записи ошибок в Python. Ваше приложение запускается, сообщения попадают в лог. Но вдруг в продакшене приложение внезапно «падает», а в логах — тишина. Знакомо? Если да — вы столкнулись с одной из малозаметных, но опасных особенностей Python — «непойманные исключения» (
uncaught exceptions).В этом посте мы разберёмся, почему такое вообще случается, как надежно логировать любые исключения.
Рассмотрим следующий код:
import logging
logger = logging.getLogger(__name__)
logging.basicConfig(filename="output.log", level=logging.INFO)
logger.info("Application started")
1 / 0 # деление на ноль
В консоли вы увидите traceback:
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
А в
output.log будет только:
INFO:__main__:Application started
Никакой информации об ошибке. И это сгенерирует вам часы работы.
Почему так происходит?
Библиотека logging в Python не логирует ошибки сама по себе: она просто предоставляет инструменты для записи. Если программа падает из-за исключения, и это исключение не обрабатывается в
try / except, то встроенный модуль никак не участвует в этом процессе. Потому что стандартный Python-интерпретатор выводит непойманные исключения напрямую в stderr, минуя logging.Плохое (но распространённое) решение
Один из способов «поймать всё» — обернуть
main() в try / except:
def main():
logger.info("Application started")
1 / 0
try:
main()
except Exception as e:
logger.exception("Unhandled exception:")
Это сработает:
logger.exception() запишет ошибку и трейсбек. Но есть минусы:— Вы можете пропустить системные исключения (
KeyboardInterrupt и проч., если ловите Exception, а не BaseException;— Такой код трудно масштабировать: оборачивать каждый
main() в каждом скрипте — дублирование;— Это маскирует архитектурные проблемы: непойманные исключения — это чаще всего баг, а не ожидаемое поведение.
Правильное решение:
sys.excepthookPython дает глобально обрабатывать непойманные исключения —
sys.excepthook:
import sys
import logging
logger = logging.getLogger(__name__)
logging.basicConfig(filename="output.log", level=logging.INFO)
def handle_uncaught_exception(exc_type, exc_value, exc_traceback):
logger.critical(
"Uncaught exception. Application will terminate.",
exc_info=(exc_type, exc_value, exc_traceback)
)
sys.excepthook = handle_uncaught_exception
logger.info("Application started")
1 / 0
Теперь, если запустить скрипт:
— В
output.log появится подробный трейсбек ошибки;— Вы будете уверены, что даже критические ошибки попадут в лог, прежде чем приложение завершится.
Когда в Python возникает исключение, и его никто не перехватывает, вызывается sys.excepthook(type, value, traceback). По умолчанию она просто печатает детали в
stderr. Но вы можете управлять процессом:— Логировать ошибки;
— Отправлять оповещения (например, в Telegram или на почту);
— Снимать дампы или делать очистку.
#основы