Логирование вместо print в Python: как настроить с нуля
Замена print на модуль logging: как за 20 минут получить читаемые логи с уровнями, временными метками и записью в файл. Разбираем базовую настройку и типичные ошибки.

К концу этого разбора у вас будет рабочая настройка логирования на модуле logging из стандартной библиотеки: сообщения с уровнями важности, временем и именем модуля, вывод одновременно в консоль и файл. Никаких сторонних пакетов, всё работает на CPython 3.8 и новее. Настройка займёт минут двадцать, если делаете впервые.
Проблема с print не в том, что он плохой. Он не умеет главного: молчать, когда отладка не нужна, и говорить громче, когда что-то сломалось. Каждый print вы потом ищете и удаляете руками. Логирование включается и выключается одной строкой конфигурации.
Что нужно до начала
- Python 3.8 или новее (проверьте командой
python --version). - Любой проект, где сейчас есть отладочные
print, — их и будем замещать. - Права на запись в каталог, если планируете писать логи в файл.
Модуль logging входит в стандартную библиотеку. Устанавливать ничего не нужно.
Пять уровней вместо одного print
В logging у каждого сообщения есть уровень важности. Их пять, от самого подробного к самому тревожному:
| Уровень | Числовое значение | Когда использовать |
|---|---|---|
| DEBUG | 10 | Детали для отладки: значения переменных, ход цикла |
| INFO | 20 | Штатные события: сервис запустился, файл обработан |
| WARNING | 30 | Что-то подозрительное, но программа работает дальше |
| ERROR | 40 | Операция не выполнена, но процесс жив |
| CRITICAL | 50 | Программа не может продолжать работу |
Смысл уровней: вы задаёте порог, и всё, что ниже, не попадает в вывод. В разработке ставите DEBUG и видите всё. В продакшене ставите INFO или WARNING — и подробности отладки исчезают сами, без правки кода.
Шаг 1. Минимальная настройка
Самый простой способ — basicConfig. Одна функция настраивает корневой логгер: формат, уровень и куда писать.
import logging
# Настраиваем корневой логгер один раз при старте программы
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
logging.debug("Значение счётчика: 42")
logging.info("Обработка файла завершена")
logging.warning("Файл конфигурации не найден, беру значения по умолчанию")
logging.error("Не удалось подключиться к базе")Запустите. В консоли появятся четыре строки вида:
2024-05-01 12:30:15 [DEBUG] root: Значение счётчика: 42
2024-05-01 12:30:15 [INFO] root: Обработка файла завершена
2024-05-01 12:30:15 [WARNING] root: Файл конфигурации не найден, беру значения по умолчанию
2024-05-01 12:30:15 [ERROR] root: Не удалось подключиться к базеЕсли вы видите эти строки со временем и уровнями — базовая настройка работает. Имя root появилось потому, что мы вызвали logging.info напрямую, без своего логгера. Это чинится на следующем шаге.
Шаг 2. Свой логгер в каждом модуле
Вместо вызовов logging.info напрямую заведите логгер с именем модуля. Так в логах будет видно, откуда пришло сообщение, а в большом проекте это половина пользы от логирования.
import logging
# __name__ подставит имя текущего модуля, например "myapp.database"
logger = logging.getLogger(__name__)
def load_user(user_id):
logger.debug("Загружаю пользователя id=%s", user_id)
if user_id <= 0:
logger.warning("Некорректный id=%s, возвращаю None", user_id)
return None
logger.info("Пользователь id=%s загружен", user_id)
return {"id": user_id}Обратите внимание: аргументы передаются через запятую, а не через f-строку. Так logging подставит значения только если сообщение реально попадёт в вывод. Если уровень DEBUG отключён, форматирование строки даже не запустится — мелочь, но на горячем пути это экономит время.
Шаг 3. Запись в файл и в консоль одновременно
В продакшене логи нужны в файле. Но basicConfig с параметром filename перестанет писать в консоль. Чтобы шло и туда, и туда, добавьте два обработчика вручную.
import logging
logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)
# Общий формат для обоих обработчиков
formatter = logging.Formatter(
"%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
# В консоль — только INFO и выше
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(formatter)
# В файл — всё, включая DEBUG
file_handler = logging.FileHandler("app.log", encoding="utf-8")
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
logger.addHandler(console)
logger.addHandler(file_handler)
logger.debug("Это уйдёт только в файл")
logger.info("А это в файл и в консоль")После запуска рядом с программой появится файл app.log. В консоли вы увидите одну строку (INFO), в файле — две (DEBUG и INFO). Если файл не появился — проверьте права на запись в каталог.
Уровень логгера и уровень обработчика — разные вещи. Логгер решает, пропускать ли сообщение вообще; обработчик — писать ли его в свой приёмник. Сообщение должно пройти оба фильтра.
Шаг 4. Логируйте исключения правильно
Внутри блока except не пишите текст ошибки вручную. Метод logger.exception сам добавит полный traceback.
import logging
logger = logging.getLogger("myapp")
def divide(a, b):
try:
return a / b
except ZeroDivisionError:
# exception() автоматически прикладывает traceback
logger.exception("Деление на ноль при a=%s b=%s", a, b)
return NoneВ логе окажется и ваше сообщение, и стек вызовов до строки с ошибкой. Вызывать logger.exception имеет смысл только внутри except — вне его traceback пустой.
Если не получилось
- Сообщения не появляются вовсе. Скорее всего,
basicConfigвызван после первого лог-сообщения или несколько раз. Он настраивает корневой логгер только при первом вызове. Вызывайте его один раз в точке входа программы, до любых логов. - Каждое сообщение печатается дважды. Вы добавили обработчик к своему логгеру и при этом сработал
basicConfigна корневом. Сообщение всплывает вверх по иерархии. Отключите всплытие:logger.propagate = False. - DEBUG-сообщения не видны, хотя уровень выставлен. Проверьте оба уровня — и у логгера, и у обработчика. Если у обработчика стоит INFO, DEBUG до него не дойдёт.
- В файле кракозябры вместо кириллицы. Укажите
encoding="utf-8"вFileHandler, как в примере выше. По умолчанию кодировка зависит от системы. - Ошибка
ValueError: Unknown format code. Вы смешали%-стиль формата с f-строкой. ВFormatterиспользуется только%-синтаксис вида%(message)s.
Что делать дальше
Базовая настройка закрывает 90% нужд. Когда проект вырастет, посмотрите в сторону logging.handlers.RotatingFileHandler — он режет лог на файлы по размеру, чтобы один app.log не разросся на гигабайты. Ротация по времени и структурированные логи в JSON — отдельная тема, здесь мы её не разворачиваем.
Следующий полезный шаг — вынести конфигурацию логирования в отдельный файл через logging.config.dictConfig, чтобы не настраивать обработчики кодом. Официальная документация модуля даёт готовый пример словаря конфигурации.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: Logging HOWTO — официальная документация Python, logging — Logging facility for Python
Частые вопросы
У меня Python 3.7, всё это заработает?
Не сработало: сообщения печатаются по два раза. Что делать?
Стоит ли сразу ставить loguru вместо стандартного logging?
Как быстро отключить все DEBUG-сообщения в продакшене?
Логирование замедлит программу по сравнению с print?
Можно ли писать логи в файл и консоль в разных форматах?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.