Поток событий проходит через сборщик и сохраняется упорядоченными уровнями
Программирование

Журналирование в Python: logging без лишнего шума

Настраиваем уровни сообщений, пишем журнал в консоль и файл, сохраняем traceback при ошибке и не допускаем утечки секретов.

Содержание

Консольный скрипт удобно наблюдать через print, пока он выполняет одну короткую операцию. В планировщике нужен журнал: когда началась задача, какой файл обрабатывался, сколько строк принято и почему выполнение завершилось ошибкой. Модуль logging добавляет к сообщению время, уровень и источник.

Уровень показывает важность записи. DEBUG нужен для подробной диагностики, INFO — для штатных этапов, WARNING — для отклонения, после которого работа продолжается, ERROR — для сорванной операции, CRITICAL — для отказа всей программы или службы.

Пример развивает консольный инструмент из урока об argparse и использует правила обработки исключений.

Создайте именованный logger

Создайте файл logging_demo.py. Первый вариант выводит сообщения только в консоль и не создаёт файлов:

import logging


logger = logging.getLogger(__name__)


def main():
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s %(levelname)s %(name)s: %(message)s",
    )
    logger.info("Задание запущено")
    logger.warning("Учебное предупреждение: входной список пуст")
    logger.info("Задание завершено")


if __name__ == "__main__":
    main()

Запустите python logging_demo.py. В терминале появятся три строки с временем, уровнем и именем модуля. Именованный logger позволяет позже изменить правила для одного модуля, не затрагивая всю программу.

basicConfig вызывают один раз в точке входа. Если библиотечный модуль сам меняет общую конфигурацию, он мешает приложению выбрать файл, формат и уровень. В библиотеке достаточно getLogger(__name__) и самих сообщений.

Передавайте значения отдельно от шаблона

Добавьте функцию, которая сообщает путь и число обработанных строк через заполнители %s и %d:

def report_result(path, row_count):
    logger.info("Обработан файл %s; строк: %d", path, row_count)

Поместите вызов report_result("data/report.csv", 2) внутрь main после первой записи. Ожидаемый результат — строка с путём и числом. Logging подставляет аргументы только тогда, когда выбранный уровень действительно выводится. Это удобнее преждевременно собранной длинной f-строки и является обычным стилем модуля.

Не передавайте в журнал пароль, токен, cookie, приватный ключ или полную строку подключения. Даже уровень DEBUG может случайно попасть в файл, резервную копию или систему сбора логов. Записывайте безопасный идентификатор операции и маскируйте чувствительные части до вызова logger.

Запишите журнал в отдельный файл

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

from pathlib import Path


def configure_logging():
    log_path = Path(__file__).resolve().parent / "logs" / "job.log"
    log_path.parent.mkdir(exist_ok=True)
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s %(levelname)s %(name)s: %(message)s",
        filename=log_path,
        encoding="utf-8",
    )
    return log_path

Внутри main замените прежний вызов logging.basicConfig(...) строкой log_path = configure_logging(). Код создаёт каталог logs, если его нет, и добавляет записи в job.log. После запуска консоль останется пустой, а сообщения появятся в файле. Удаление job.log отменит только накопленный журнал, но не действия самой программы.

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

Сохраните причину ошибки вместе с traceback

Трассировка, или traceback, показывает цепочку вызовов до места исключения. Добавьте функцию, которая читает файл и переводит ожидаемую ошибку в код возврата:

def count_lines(path):
    try:
        with path.open("r", encoding="utf-8") as file:
            return sum(1 for _ in file)
    except OSError:
        logger.exception("Не удалось прочитать файл %s", path)
        return None

Вызовите её из main с заведомо отсутствующим учебным файлом. Этот фрагмент должен стоять после настройки журнала и до сообщения о штатном завершении:

    missing_path = Path("data/missing.csv")
    row_count = count_lines(missing_path)
    if row_count is None:
        logger.error("Задание завершено с ошибкой")
        return 1

    logger.info("Строк прочитано: %d", row_count)
    return 0

И замените нижний вызов на raise SystemExit(main()). После запуска в job.log появятся сообщение, тип ошибки и traceback; процесс завершится с кодом 1. logger.exception следует вызывать внутри except, иначе у него нет текущего исключения для трассировки.

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

Выбирайте уровень по действию оператора

Если необязательный файл не найден и программа использует документированное значение по умолчанию, это может быть WARNING. Если обязательный отчёт не создан, операция сорвана и нужен ERROR. CRITICAL не означает «ошибка посильнее»: он уместен, когда приложение в целом не может продолжать работу.

Для подробной диагностики добавьте параметр --verbose и выбирайте DEBUG в точке входа. Не меняйте уровень внутри каждой функции. Тогда одна настройка управляет детализацией всей программы.

Официальный Logging HOWTO подробно объясняет поток записей, обработчики и форматирование. Следующий урок проверит функции без ручного запуска с помощью unittest.

Рекламное местоВаша компания здесьРазместить рекламу

Самопроверка

Проверьте, что материал усвоен

Ответьте на все вопросы. Результат сохранится только в этом браузере и будет учтён в статистике прочитанных материалов.

01Какой уровень подходит для штатного завершения задания?
02Что делает logger.exception внутри except?
03Почему конфигурацию журнала задают один раз?

Разбираем коротко

Частые вопросы

Чем logging лучше обычного print?

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

Когда использовать logger.exception?

Внутри блока except, когда нужно сохранить сообщение вместе с трассировкой исходного исключения.

Можно ли записывать в журнал токены и пароли?

Нет. Секреты, cookies, ключи и полные строки подключения нужно исключать или маскировать до передачи журналу.