Что использовать для логирования и сбора метрик в приложении
Что проверяют
Есть ли у вас опыт эксплуатации, а не только разработки: как вы узнаёте, что сервис на Python сломался или деградировал, раньше пользователей. Следом обычно спрашивают, чем логи отличаются от метрик и как связать строку лога с трассой запроса.
Как отвечать
Разделите сигналы и назовите инструмент для каждого:
- логи: стандартный
logging, логгер на модуль черезgetLogger(__name__), настройка один раз при старте черезlogging.config.dictConfig. В проде логи структурированные, в JSON:structlogилиpython-json-logger. Сервис пишет в stdout, агент (Vector, Fluent Bit, Grafana Alloy) собирает и отправляет в Loki, Elasticsearch или OpenSearch; - метрики:
prometheus_clientс типамиCounter,Gauge,Histogram,Summaryи эндпоинт/metrics, который опрашивает Prometheus. Для фреймворков есть обёрткиprometheus-fastapi-instrumentatorиdjango-prometheus. Дашборды в Grafana, алерты через Alertmanager; - трассы: OpenTelemetry, автоинструментация
opentelemetry-instrumentпокрывает FastAPI, Django,requests, SQLAlchemy.trace_idстоит класть в каждую строку лога; - ошибки: Sentry через
sentry-sdk, с группировкой исключений и контекстом запроса.
Что мерить, подскажет метод RED: частота запросов, доля ошибок, длительность. Длительность пишут в Histogram, потому что гистограммы с разных подов складываются в общий перцентиль, а квантили Summary между экземплярами не агрегируются.
Отдельно упомяните Gunicorn с несколькими воркерами: каждый процесс держит свои счётчики, и для верных цифр нужен режим multiprocess с переменной PROMETHEUS_MULTIPROC_DIR.
Где спотыкаются
Пишут logger.info(f"user {user_id}"). Строка форматируется даже при выключенном уровне, а агрегатор не сгруппирует такие записи. Правильно logger.info("user %s", user_id) или поля в structlog.
Кладут в лейблы метрик user_id, URL с параметрами или текст ошибки. Каждое значение лейбла создаёт отдельный временной ряд, и Prometheus раздувается от кардинальности.
Логируют исключение через logger.error(e) и теряют трейсбек. Внутри except нужен logger.exception(...) или exc_info=True.