Логирование в Python: модуль logging — настройка, уровни и практические примеры

Логирование в Python: модуль logging — настройка, уровни и практические примеры

Логирование в 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 с Нуля до Гуру».

    Источник

    НЕТ КОММЕНТАРИЕВ

    Оставить комментарий