Логирование в Python: модуль logging — настройка, уровни и практические примеры
Логирование в Python — это основа наблюдаемости и отладки. Правильно настроенный logging экономит часы на поиске проблем и делает сервисы предсказуемыми. Ниже — практическое руководство: от базовой конфигурации до ротации файлов и JSON‑логов.
Зачем нужно логирование
- Диагностика ошибок в продакшене и локально
- Аудит операций и бизнес‑событий
- Метрики и анализ производительности
Уровни логирования
Основные уровни от более подробного к более важному: DEBUG, INFO, WARNING, ERROR, CRITICAL. Фильтрация идёт «сверху вниз»: если уровень INFO — DEBUG не выводится.
Быстрый старт с basicConfig
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
)
logging.info("Старт приложения")
logging.debug("Этого не видно при INFO")
basicConfig удобно для скриптов, но для приложений лучше явная настройка логгеров и хендлеров.
Логгеры по модулям и иерархия
# mymodule.py
import logging
logger = logging.getLogger(__name__)
def divide(a, b):
logger.debug("divide a=%s b=%s", a, b) # ленивое форматирование
return a / b
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
print(divide(4, 2))
Используйте логгеры с именем модуля (__name__) — так работает наследование уровней и хендлеров через иерархию (root → package → module).
Обработчики и форматтеры
import logging
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
logger = logging.getLogger("app")
logger.setLevel(logging.DEBUG)
console = logging.StreamHandler()
file_rot = RotatingFileHandler(
"app.log", maxBytes=1_000_000, backupCount=3, encoding="utf-8"
)
file_time = TimedRotatingFileHandler(
"app-time.log", when="midnight", backupCount=7, utc=True
)
fmt = logging.Formatter(
"%(asctime)s | %(levelname)-8s | %(name)s | %(message)s"
)
for h in (console, file_rot, file_time):
h.setFormatter(fmt)
logger.addHandler(h)
logger.info("Запуск приложения")
- StreamHandler — вывод в консоль
- FileHandler — запись в файл
- RotatingFileHandler — ротация по размеру
- TimedRotatingFileHandler — ротация по времени
dictConfig: настраиваем logging декларативно
import logging
import logging.config
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"default": {
"format": "%(asctime)s %(levelname)s %(name)s: %(message)s",
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "INFO",
"formatter": "default",
"stream": "ext://sys.stdout",
},
"file": {
"class": "logging.handlers.RotatingFileHandler",
"level": "DEBUG",
"formatter": "default",
"filename": "app.log",
"maxBytes": 1048576,
"backupCount": 3,
"encoding": "utf-8",
},
},
"root": {
"level": "DEBUG",
"handlers": ["console", "file"],
},
}
logging.config.dictConfig(LOGGING)
logger = logging.getLogger("app.service")
logger.info("Логирование настроено через dictConfig")
Плюс dictConfig — переносимость: словарь можно держать в YAML/JSON и подгружать по окружению.
Переключение уровня через аргументы CLI
import argparse
import logging
parser = argparse.ArgumentParser()
parser.add_argument(
"--log-level",
default="INFO",
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
)
args = parser.parse_args()
logging.basicConfig(
level=getattr(logging, args.log_level),
format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
)
logging.info("Уровень из CLI: %s", args.log_level)
JSON‑логи без сторонних библиотек
import json
import logging
class JSONFormatter(logging.Formatter):
def format(self, record):
payload = {
"time": self.formatTime(record, "%Y-%m-%dT%H:%M:%S"),
"level": record.levelname,
"name": record.name,
"message": record.getMessage(),
}
if record.exc_info:
payload["exc"] = self.formatException(record.exc_info)
return json.dumps(payload, ensure_ascii=False)
logger = logging.getLogger("api")
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
handler.setFormatter(JSONFormatter())
logger.addHandler(handler)
logger.info("Сервис готов")
Ловля исключений и трассировка
import logging
logger = logging.getLogger("app")
logging.basicConfig(level=logging.INFO)
def main():
try:
1 / 0
except ZeroDivisionError:
logger.exception("Ошибка в main") # добавит traceback
if __name__ == "__main__":
main()
Типовые ошибки и лучшие практики
- Не используйте print вместо логов — print неуправляем и не структурирован.
- Не вызывайте basicConfig несколько раз: повторная инициализация игнорируется, а хендлеры могут дублироваться.
- Избегайте склеивания строк: используйте ленивое форматирование — logger.debug("x=%s", x) вместо f-строки, чтобы не тратить CPU при выключенном уровне.
- Для библиотек добавляйте NullHandler, чтобы не шуметь в чужих приложениях:
# в библиотеке
import logging
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
- Следите за дублированием записей: проверьте logger.propagate и уникальность хендлеров.
- Для больших проектов — dictConfig и единый формат, включая корреляционные ID (request_id, user_id).
Мини‑шаблон приложения
# app.py
import logging
from logging.config import dictConfig
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"rich": {"format": "%(asctime)s | %(levelname)s | %(name)s | %(message)s"}
},
"handlers": {
"console": {"class": "logging.StreamHandler", "formatter": "rich"}
},
"root": {"level": "INFO", "handlers": ["console"]},
}
dictConfig(LOGGING)
logger = logging.getLogger("app")
def run():
logger.info("Старт")
try:
result = 42
logger.debug("Результат=%s", result)
except Exception:
logger.exception("Непредвиденная ошибка")
finally:
logger.info("Завершение")
if __name__ == "__main__":
run()
Что дальше
Освоив logging, вы получите контроль над поведением приложения в продакшене. Хотите прокачаться системно: от основ синтаксиса до реальных проектов и DevOps‑практик? Рекомендую пройти бесплатный старт и посмотреть программу курса «Программирование на Python с Нуля до Гуру».
-
Создано 21.01.2026 17:01:36
-
Михаил Русаков

Комментарии (0):
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.