feat(observability): /metrics в обоих бэкендах — счётчики, задержка, дашборд приложений #3102
No reviewers
Labels
No labels
Fable 5 ревью
GG-форсайт
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
feedback/max
generative
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
ИРД
вторичка
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#3102
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/observability-app-metrics"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Третья часть из пяти по #3078 и единственная, трогающая прод-код. Стек-приёмник уже в main (#3099); эта часть даёт ему то, что собирать с приложений.
Зачем
До неё числовых рядов у «Птицы» и «Меры» не было вовсе — только логи и исключения в GlitchTip. Класс отказов «отвечает, но медленно» и «отдаёт 401 потоком» в такой картине невидим: исключения нет, строка в логе выглядит обычной, а продукт при этом не работает.
Появляются HTTP-счётчики, гистограмма времени ответа, число запросов в работе, версия сборки и
process_*от реестраprometheus_client— память процесса, дескрипторы, сборщик мусора.Решения, которые стоит проверить при ревью
Метка
route— ШАБЛОН маршрута, а не путь запроса./api/v1/parcels/{cad_num}, не/api/v1/parcels/66:41:0301001:123. Разница не косметическая: у Prometheus временной ряд стоит памяти постоянно, а не в момент запроса, поэтому кадастровый номер в метке кладёт приёмник за сутки — самый известный способ уронить мониторинг тем самым мониторингом. Отказ отложенный и не выглядит как ошибка кода, поэтому свойство закреплено тестом, а не комментарием: тест бьёт тремя разными идентификаторами и требует один ряд, а потом проверяет, что ни один идентификатор не встречается в выгрузке.Незаматченные пути сведены в одну метку
__unmatched__. Иначе тот же взрыв рядов устроит через чёрный ход любой бот, перебирающий адреса, — тест бьёт по/wp-admin/setup-config.php,/.envи кириллическому пути и требует один ряд.Слой регистрируется последним и потому оказывается самым внешним (
add_middlewareвставляет в начало списка). Порядок несущий. Изнутри RBAC-гварда не видно ни отказов авторизации — их отдаёт сам гвард, — ни времени, которое он тратит на резолв сессии в БДauth; а именно этот путь уже давал инцидент с блокирующим I/O в middleware (#1202). У «Меры» так же не были бы видны 429 от ограничителя частоты.Упавший исключением запрос считается как 500 в
finally. Исключение проходит сквозь слой наружу, кServerErrorMiddleware; безfinallyтакие запросы просто отсутствовали бы в счётчике — то есть ровно тогда, когда метрики нужнее всего.Чистый ASGI, а не
BaseHTTPMiddleware. Последний заворачивает ответ в собственный поток и на потоковых ответах ведёт себя иначе. Такие ответы есть в обоих продуктах — выгрузки PDF/DXF/XLSX у «Птицы», PDF отчёта у «Меры», — и менять их обработку ради подсчёта запросов не стоит.Два периметра, а не один.
/metricsпубличен внутри и закрыт снаружи. Скрейп идёт из docker-сети, где заголовкаX-Authenticated-Userнет ни у кого, поэтому путь внесён в_PUBLIC_PATHSобоих бэкендов — без этого агент получал бы 401 и метрик не было бы вовсе. Наружу он при этом не открывается:gendsgn.ruотдаёт бэкенду «Птицы» только/healthи/api/*, бэкенду «Меры» — только/trade-in/api/*, аmeraocenka.ruработает по белому списку. Сверх этого в обоих site-блоках стоит явныйrespond 404на/metrics: одной строкиhandle /metrics { reverse_proxy backend:8000 }, добавленной когда-нибудь по невнимательности, хватит, чтобы выставить наружу внутреннее устройство продукта, — явный отказ делает закрытость решением, а не следствием текущего порядка директив.Ограничитель частоты и аудит «Меры» не трогались. Первый смотрит только на пути под
/api/, второй — на/api/и известного пользователя, так что скрейп раз в 30 секунд не попадает ни под один. Иначе метрики пропадали бы пачками именно под нагрузкой, аuser_eventsполучала бы 2880 строк в сутки ни о чём. Проверено тестом (реальныйRateLimitMiddleware, лимит понижен до 2, шесть запросов подряд — все 200), а не чтением кода.Границы гистограммы разные у двух продуктов, и это намеренно. У «Птицы» верхняя корзина 60 с:
POST /api/v1/parcels/{cad_num}/analyzeуходит в десятки секунд, внутри поход в OSRM и подсчёт геометрии. У «Меры» шаг плотнее снизу — расчёт укладывается в 90 мс (замер на живом запросе), — но верхние корзины оставлены из-за внешних зависимостей с непредсказуемым временем: геокодер, банк-эквайер.Проверено
Тесты гоняются в изолированном окружении обоих проектов:
Один тест «Птицы» (
_PUBLIC_PATHSизapp.main) в изолированном прогоне не поднялся — не хваталоsentry_sdk; в CI окружение полное. Одноимённый тест «Меры» с полными зависимостями прошёл.Отдельно, на живом Beget'е,
caddy validateв обоих режимах после правкиapps.caddy→Valid configuration;caddy adaptподтверждает, что оба блока/metrics → 404реально попали в конфиг:Проверка стоит до перезагрузки и в workflow тоже: тот же Caddy обслуживает
git.,errors.иobsidian., включая Forgejo, из которого идёт деплой.ruffчист по всем изменённым файлам обоих проектов.prometheus-clientдобавлен в обаpyproject.toml, оба lock-файла обновлены черезuv lock(0.26.0).Что осталось незакрытым сознательно
Один процесс — один реестр. Оба контейнера запускают
uvicornбез--workers, поэтому счётчики целостны. Появятся воркеры — счётчики станут per-process, каждый скрейп попадёт в случайный, и график начнёт пилить вверх-вниз без связи с нагрузкой. Лечится штатным многопроцессным режимомprometheus_client; делать это заранее незачем, но связь--workers→ сломанные графики отмечена в коде, чтобы её не искали заново.Celery-воркеры своего
/metricsне отдают — у них нет HTTP-сервера, а поднимать его в каждом воркере ради счётчиков это отдельная конструкция со своим временем жизни. Прогоны фоновых задач будут видны иначе, черезscrape_runs(часть 4), и это лучше: там уже лежит история, а не только происходящее прямо сейчас.process_*появляются только на Linux — коллектор читает/proc. Боевой рантайм это контейнер, там метрики есть; на Windows их нет, и это не отказ.Test plan
route— шаблон: три идентификатора → один ряд, ни один не утёк в выгрузкуin_progressсходится в ноль, в том числе после упавшего запроса/metricsне ловит 429 от реального лимитера «Меры»caddy validateв обоих режимах + оба/metrics → 404в адаптированном конфигеruffпо обоим проектамup{job="app"}равен 1 дляsitefinderиmera(сейчас 0 — цель видна как недоступная, это ожидаемо)curl -sI https://gendsgn.ru/metricsиhttps://meraocenka.ru/metrics→ 404/,/trade-in,/estimateи/healthотвечают как раньшеRefs #3078