Сервис, который нельзя наблюдать, нельзя и эксплуатировать: когда что-то идёт не так, остаётся гадать. Наблюдаемость — это три опоры (логи, трейсы, метрики) плюс health-checks для оркестратора. FastAPI не навязывает их, но хорошо стыкуется со стандартными инструментами Python.
Структурные логи
Логи должны быть машиночитаемыми — строками JSON с полями, а не свободным текстом. Тогда их можно фильтровать и искать в системе сбора логов. Ключевое поле — идентификатор запроса, проставленный в middleware: по нему все строки одного запроса сшиваются вместе.
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), один раз на старте. Важно не само средство, а правило: лог — это событие с полями, а не печать в консоль.
Трейсинг: OpenTelemetry
Трейс показывает путь запроса через сервис и дальше — в базу, в соседний сервис — и где сколько времени ушло. Стандарт — OpenTelemetry; для FastAPI есть готовое авто-инструментирование.
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
FastAPIInstrumentor.instrument_app(app)
Одна строка добавляет span на каждый запрос; отдельные инструментирующие пакеты делают то же для SQLAlchemy и HTTP-клиента, так что трейс получается сквозным. Дальше трейсы уходят в коллектор (Jaeger, Tempo) — настройкой экспортёра, не кодом приложения.
Метрики: Prometheus
Метрики — это числа во времени: сколько запросов, какие задержки, сколько ошибок. Стандарт — Prometheus, который периодически забирает их с эндпоинта /metrics. Самый простой путь для FastAPI — готовый инструментатор.
from prometheus_fastapi_instrumentator import Instrumentator
Instrumentator().instrument(app).expose(app)
Это поднимает базовые метрики (число запросов, длительность, коды ответов) и отдаёт их на /metrics. Свои метрики (бизнес-счётчики — например, созданных заказов) добавляют через prometheus-client. Метрики отвечают на «что происходит в целом», трейсы — на «что случилось с этим конкретным запросом».
Health-checks
Оркестратору (Kubernetes) нужны два разных сигнала, и путать их опасно:
- liveness — жив ли процесс вообще; провал означает «перезапусти меня». Должен быть простым и не зависеть от внешних систем.
- readiness — готов ли принимать трафик; провал означает «не шли мне запросы пока». Здесь уместно проверить базу и другие зависимости.
@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 зависимым от базы: упала база, Kubernetes решит, что процесс мёртв, и начнёт его перезапускать, хотя перезапуск не лечит чужую базу. Liveness — про себя, readiness — про готовность к работе.
Где это в UCP
Наблюдаемость — не украшение перед продом, а часть определения «готово»: сервис, который нельзя продиагностировать, не доведён до конца. Три опоры отвечают на разные вопросы (что в целом, что с запросом, что в логе события), health-checks дают оркестратору правду о состоянии. Это тот же набор, что Actuator, Micrometer и трейсинг в Spring-биндинге, только инструментами Python. Для продукт-инженера, который владеет сервисом до пользователя, наблюдаемость — это то, чем он закрывает последний участок пути: увидеть, что выкаченное действительно работает, и быстро понять, если нет.