← назад к разделу

PYTHON · FASTAPI Фаза 5. Фундамент - ядро FastAPI

Обязательно

Наблюдаемость во FastAPI: логи, трейсы и метрики

OpenTelemetry · Prometheus · Health

Три сигнала отвечают на разные вопросы, а liveness и readiness запускают разные действия Kubernetes.

1. Логи - структурные события

1 / 5

JSON содержит event, timestamp, level и предметные поля. request_id из middleware связывает все строки одного запроса, вместо разрозненных print.

Middlewarerequest_id7f3a...event=request_started · request_id=7f3aevent=product_created · product_id=42event=request_finished · duration_ms=83

2. Трейс показывает, где потеряно время

2 / 5

OpenTelemetry создаёт родительский HTTP span и вложенные SQLAlchemy и httpx spans. По дереву видно вклад базы, соседа и собственного кода.

HTTP POST /orders · 260 msSQL INSERT200 msHTTP catalog50 msDomain logic10 msFastAPIInstrumentor + SQLAlchemy + httpx

3. Метрики показывают систему в целом

3 / 5

Prometheus забирает числа с /metrics: RPS, длительность, ошибки и бизнес-счётчики. По ним строят дашборды и алерты, а не разбирают один конкретный запрос.

requests/metricsorders_created_totalcounter += 1

4. Liveness и readiness не взаимозаменяемы

4 / 5

Liveness проверяет только процесс и перезапускает его. Readiness проверяет готовность зависимостей и снимает под с трафика без перезапуска.

/health/liveprocess aliveбез SELECT 1DOWN → restart/health/readydatabase availableготов к трафикуDOWN → no traffic

5. Минимум ставится в первый день

5 / 5

Сначала JSON-логи и health, затем метрики и трейсы. Без 100% sampling, user_id в метках и тел запросов в логах; management endpoints не публикуют наружу.

1. Logsstructlogrequest_id2. Healthlive + readyport 80813. MetricsPrometheuslow cardinality4. TracesOTelsample 10%

Когда сервис в продакшне ведёт себя странно — тормозит, отдаёт ошибки, падает — первый вопрос: «что происходит?» Если ответить нечем, остаётся угадывать. Наблюдаемость — это три инструмента (логи, трейсы, метрики) плюс health-checks, которые дают конкретные ответы. FastAPI не навязывает реализацию, но хорошо стыкуется со стандартными инструментами Python.

Логи: от print() к структурным событиям

Когда пишут print("что-то пошло не так"), это бесполезно в продакшне: нет времени, нет контекста, нет способа найти все строки одного запроса. Система сбора логов (Loki, Elasticsearch) ожидает JSON-строки с полями, которые можно фильтровать и агрегировать.

Правильный лог — это событие с атрибутами, а не фраза в консоли:

import logging

logger = logging.getLogger("app")

@router.post("/", status_code=201)
async def create_product(body: CreateProductRequest, handler: CreateProductHandlerDep):
    product = await handler.handle(body.to_command())
    logger.info("product_created", extra={"product_id": product.id})
    return ProductResponse.from_domain(product)

Формат JSON задаётся форматтером один раз на старте — через python-json-logger или structlog. После этого каждый вызов logger.info(...) уходит как JSON-строка с текстом события, временем, уровнем и всеми переданными extra-полями (у structlog текст события лежит в поле event, у python-json-logger — в message).

Ключевое поле — идентификатор запроса (request_id). Его проставляют в middleware и добавляют в каждую строку лога. Тогда все события одного запроса можно найти за секунду одним фильтром.

Трейсинг: куда уходит время запроса

Лог говорит «что случилось», но не «где потеряно время». Запрос может провести 200 мс в базе данных, 50 мс в HTTP-вызове к соседнему сервису и 10 мс в бизнес-логике — лог этого не покажет. Трейс показывает.

Трейс — это дерево отрезков (spans): каждый span фиксирует начало и конец операции. Смотришь на трейс — видишь, где реально уходит время.

Стандарт — OpenTelemetry. Для FastAPI есть готовое авто-инструментирование:

from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor

FastAPIInstrumentor.instrument_app(app)

Одна строка добавляет span на каждый HTTP-запрос. Для SQLAlchemy и HTTP-клиентов есть отдельные инструментирующие пакеты — они добавляют вложенные spans, так что трейс получается сквозным: от входящего запроса до SQL-запроса в базу.

Готовые трейсы уходят в коллектор (Jaeger, Grafana Tempo) — через настройку экспортёра, не через изменение кода приложения.

Метрики: что происходит в целом

Трейс отвечает на вопрос «что случилось с этим конкретным запросом». Метрики отвечают на другой вопрос: «как сервис ведёт себя в целом прямо сейчас?»

Метрика — это число во времени: сколько запросов в секунду, какой процент отвечает дольше 500 мс, сколько ошибок за последние пять минут. По метрикам настраивают алерты.

Стандарт — Prometheus: он периодически забирает метрики с эндпоинта /metrics. Для FastAPI есть готовый инструментатор:

from prometheus_fastapi_instrumentator import Instrumentator

Instrumentator().instrument(app).expose(app)

Это поднимает базовые метрики (число запросов, длительность по перцентилям, коды ответов) и отдаёт их на /metrics. Свои бизнес-метрики (число созданных заказов, размер очереди) добавляют через prometheus-client:

from prometheus_client import Counter

orders_created = Counter("orders_created_total", "Количество созданных заказов")

async def create_order(...):
    ...
    orders_created.inc()

Health-checks: сигналы для Kubernetes

Kubernetes нужно знать две разные вещи о сервисе, и путать их опасно:

liveness /health/live процесс отвечает? DOWN → перезапуск контейнера readiness /health/ready база доступна? DOWN → под без трафика, без перезапуска

Две пробы отвечают на разные вопросы и ведут к разным действиям; база в liveness превращает её лаг в перезапуск всех подов.

  • liveness — жив ли процесс вообще. Если провален — Kubernetes перезапускает контейнер. Должен быть максимально простым и не обращаться к внешним системам.
  • readiness — готов ли процесс принимать трафик. Если провален — Kubernetes перестаёт слать запросы на этот экземпляр, но не перезапускает его. Здесь уместно проверить базу данных.
@router.get("/health/live")
async def live():
    return {"status": "ok"}


@router.get("/health/ready")
async def ready(session: SessionDep):
    await session.execute(text("SELECT 1"))
    return {"status": "ready"}

Типичная ошибка — сделать liveness зависимым от базы данных. Если база недоступна, liveness начнёт падать, Kubernetes будет перезапускать контейнер — а перезапуск не починит чужую базу.

Дополнительно: при первом чтении можно пропустить

Глубже: с чего начать в первый деньрасширенное

Четыре раздела выше раскрываются в отдельные статьи, и легко утонуть. Минимум для нового сервиса укладывается в один файл настройки и один middleware, и его ставят в первый день, пока в сервисе три эндпоинта.

Логи: structlog с JSONRenderer в проде и ConsoleRenderer локально, переключение по APP_ENV, и middleware, который кладёт request_id в contextvars и возвращает его в заголовке ответа. Трейсы: FastAPIInstrumentor.instrument_app(app) плюс инструментации SQLAlchemy и httpx, адрес коллектора из OTEL_EXPORTER_OTLP_ENDPOINT, выборка 10 процентов из настроек. Метрики: Instrumentator на отдельном management-приложении на порту 8081 вместе с /health/live, /health/ready и /info. Пробы в Deployment смотрят на 8081, Ingress публикует только 8080.

Чего в первый день не делают: не включают выборку трейсов в 100 процентов «пока трафика мало», не вешают user_id меткой на метрики, не логируют тело запроса, не ставят SELECT 1 в liveness. Эти четыре привычки потом убирать дороже, чем не заводить.

Порядок настройки один: сначала логирование, потому что оно нужно, чтобы отладить остальное; затем health-эндпоинты, без которых под не выйдет в трафик; затем метрики и трейсы. Подробности по каждому инструменту в статьях ниже.

Коротко

  • Логи — структурные JSON-события с полями, не print(). Ключевое поле — request_id, чтобы сшивать все строки одного запроса.
  • Трейсинг через OpenTelemetry: FastAPIInstrumentor.instrument_app(app) добавляет spans на каждый запрос. Трейс показывает, где теряется время внутри запроса.
  • Метрики через Prometheus: prometheus-fastapi-instrumentator поднимает /metrics с базовыми показателями; бизнес-метрики — через prometheus-client.
  • liveness — простая проверка, что процесс жив; не зависит от базы. readiness — проверяет готовность к трафику, включая зависимости.
  • Три инструмента отвечают на разные вопросы: метрики — «что в целом», трейсы — «что с этим запросом», логи — «что произошло в этот момент».
  • Первый день: structlog с request_id, автоинструментация OTel с адресом коллектора из окружения, Instrumentator и пробы на отдельном порту; без 100 процентов трейсов, user_id в метках и SELECT 1 в liveness.

Что почитать дальше