Logging в Python: уровни, обработчики и повторяющиеся сообщения

Logging в Python: уровни, обработчики и повторяющиеся сообщения
Содержание статьи

Для логирования в Python создайте логгер модуля через getLogger(__name__), а вывод настройте один раз в точке входа приложения. Если сообщение исчезает, проверьте уровни логгера и обработчика. Если повторяется, проверьте обработчики и передачу записи родительскому логгеру через propagate.

На Mentoring.digital разберём небольшой пример: скрипт импорта сообщает о начале работы, отклонённых строках и ошибке чтения файла. Его журнал должен помогать диагностике, не раскрывая содержимое документов.

Минимальная настройка logging

Сохраните пример в отдельный файл и запустите новым процессом Python. В интерактивной среде или фреймворке обработчики могут быть настроены раньше, поэтому повторный вызов basicConfig не обязательно изменит вывод.

import logging

logger = logging.getLogger(__name__)

def run_import():
    logger.debug("Preparing import")
    logger.info("Import started: rows=%s", 3)
    logger.warning("Skipped rows: %s", 1)

if __name__ == "__main__":
    logging.basicConfig(
        level=logging.INFO,
        format="%(levelname)s %(name)s %(message)s",
    )
    run_import()

Здесь видны INFO и WARNING, а DEBUG отсекается. Аргументы передаются отдельно от строки: форматирование сообщения выполняет logging. Значения 3 и 1 служат учебными данными, а не результатом реального импорта.

Как выбирать уровень сообщения

  • DEBUG: детали, нужные при диагностике.
  • INFO: нормальный ход операции, например её завершение.
  • WARNING: отклонение, после которого приложение может продолжать работу.
  • ERROR: операция не выполнена из-за ошибки.
  • CRITICAL: серьёзный сбой, угрожающий работе приложения.

Уровень отражает последствие события. Не стоит помечать все записи как ERROR ради заметности: тогда поиск настоящей проблемы станет сложнее. Состав полей тоже важен: идентификатор операции обычно полезнее полного пользовательского запроса.

Почему запись пропала или появилась дважды

Проверяйте цепочку по порядку: разрешён ли уровень для логгера, есть ли обработчик, пропускают ли запись его уровень и фильтры. У дочернего логгера значение NOTSET означает наследование эффективного уровня от предков.

Частая причина дублей: обработчик добавлен и дочернему логгеру, и корневому, а propagate=True оставлен включённым. Одна запись доходит до обоих обработчиков. Обычно удобнее держать вывод на корневом логгере. Если дочернему действительно нужен независимый вывод, его передачу наверх отключают осознанно.

Не добавляйте обработчик внутри функции, вызываемой для каждой строки: при повторных вызовах их число может увеличиваться. Библиотека также не должна принудительно перенастраивать журнал всего приложения.

Как записать исключение и проверить результат

try:
    with open("input.csv", encoding="utf-8") as source:
        first_line = source.readline()
except OSError:
    logger.exception("Cannot read import file")

logger.exception внутри обработчика исключения добавляет traceback. Решение продолжать работу или передать исключение выше принимает приложение; запись в журнал сама по себе не устраняет ошибку.

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

Системно освоить обработку ошибок помогут курсы Python с практикой. Схему логирования конкретного сервиса можно разобрать с наставником по Python.

Похожие статьи по данной теме