feat(backend): sentry-sdk init для FastAPI + Celery (#204 backend) #207
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
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#207
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/204-glitchtip-backend"
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?
Closes #204 (backend часть)
Summary
sentry-sdk[fastapi,celery,sqlalchemy,httpx]>=2.18.0к backendmain.py) и отдельно вcelery_app.pyдля Celery-процессаGLITCHTIP_DSN— SDK не инициализируется, контейнер стартует без ошибокИнтеграции
StarletteIntegration+FastApiIntegration— трейсинг HTTP-запросовCeleryIntegration(monitor_beat_tasks=True)— ошибки и beat-мониторингSqlalchemyIntegration— slow/error queriesHttpxIntegration— исходящие httpx запросыLoggingIntegration(level=INFO, event_level=ERROR)— ERROR-логи → события GlitchTipprofiles_sample_rate=0.0— GlitchTip профили не поддерживаетEnv-переменные (новые)
GLITCHTIP_DSNhttps://<key>@errors.gendsgn.ru/<project_id>GLITCHTIP_TRACES_SAMPLE_RATE0.05Acceptance checklist
uv syncпроходит без ошибокruff check app/— чистоGLITCHTIP_DSN(no-op)Deep Code Review — verdict
Summary
main.py,celery_app.py,config.py,pyproject.toml· P2:.env.example· P3:test_sentry_init.py· ⚪uv.lock)2f79f7c· Mergeable: true · Conflicts: none🟠 High (должен быть пофиксен до merge)
1. Release tracking сломан в production —
GIT_SHAнигде не выставляетсяНовый код в
main.py:37иcelery_app.py:25:Но
GIT_SHAне задан нигде:.forgejo/workflows/deploy.ymlпишетSENTRY_RELEASE=$IMAGE_TAGвbackend/.env.runtime(lines 157, 164, 180-184) — НЕGIT_SHAdocker-compose.prod.ymlenv_file подгружает.env.runtime, в которомSENTRY_RELEASE=...— это значение становитсяos.environ["SENTRY_RELEASE"], а неGIT_SHAbackend/.env.runtime.example:6всё ещё содержитSENTRY_RELEASE=(не обновлён).claude/rules/deploy.mdявно фиксирует контрактSENTRY_RELEASE=$IMAGE_TAGв.env.runtimeРезультат после merge: каждый event в GlitchTip будет помечен
release=unknown— теряется возможность фильтра по версии, regression detection, deploy markers. Это тихая поломка: SDK инициализируется, события идут, но release-tag всегда "unknown".Fix (выбрать один):
main.pyиcelery_app.py:SENTRY_RELEASE_VAL→GIT_SHA_VAL, вsedписатьGIT_SHA=...в.env.runtime. Также обновитьbackend/.env.runtime.example(GIT_SHA=) и.claude/rules/deploy.md. Туда жеREADME.md:109иdocker-compose.prod.yml:51(комментарии).Без любого из них release tracking remains broken — это не fail-deploy, но обесценивает половину ценности GlitchTip (deploy markers / version filtering).
2. Init-блок не отрабатывает в
beatпроцессеdocker-compose.prod.yml:122:celery beat -A app.workers.celery_appимпортируетcelery_app.py— module-levelif settings.glitchtip_dsn: sentry_sdk.init(...)отработает, ✅ beat покрыт.Но
workerкомандуется аналогично (docker-compose.prod.yml:109) → SDK init происходит в родительском процессе worker'а до fork'а. Сprefork-pool по умолчанию child-процессы наследуют SDK state через fork — это работает для error capture, ноCeleryIntegration(monitor_beat_tasks=True)heartbeat-events отправляются из beat-процесса, не из worker fork'а — то есть pattern корректен.⚠️ Однако: если в
app.workers.lifecycleесть@worker_process_init.connecthook (vault упоминает Bug_Worker_Ready_EarlyReturn) — нужно убедиться, что после fork SDK переинициализируется или transport reopen'ится. Стандартныйsentry-sdkэто делает автоматически черезmultiprocessing.util.register_after_fork(с 2.x). На версии>=2.18.0— OK.🤔 Question: проверить руками после deploy, что test-event из worker task (
celery_app.send_task(...)) реально доходит до GlitchTip — fork-safety багов вsentry-sdk[celery]2.x исторически было несколько.🟡 Medium (желательно fix, но не блокирующее)
3. PII в логах через
LoggingIntegration(level=INFO, event_level=ERROR)send_default_pii=False— корректно, IP / cookies / headers фильтруются. НО:logger.error/exceptionв 18 файлах — каждый теперь шлёт eventbackend/app/workers/tasks/nspd_geo.py:140-148,nspd_geo.py:225-238логируютсяcad_numчерез f-string в WARNING/INFO. Per CLAUDE.md «cad_num as PII» в задаче — это spec-level concern. Reality check: cad_num по 152-ФЗ скорее всего НЕ персональные данные (это публичный кадастровый номер, не привязан к физлицу до запроса в ЕГРН), но если у нас лиды связаны с конкретным cad_num — это уже бизнес-PII.domrf_kn.py:620-627— уже специально подавляет логирование в Sentry/Docker для одного класса ошибок (comment подтверждает учёт). Хороший знак — codebase осведомлён о volume.Fix-suggestion (опционально): в
LoggingIntegrationподнятьevent_level=logging.WARNINGили оставить ERROR — но добавитьbefore_sendфильтр, scrubbing'ующий поляinn,phone,email,password,tokenиз eventextra. Канонически:4.
traces_sample_rate=0.05×HttpxIntegration→ утечка outbound URLsHttpxIntegrationпишет URL outbound запросов в spans. У нас scrapers ходят на:pkk.rosreestr.ru/api/features/1?...— содержитcad_numв querystringapi.objctv.ru/...— содержитapiKey=<KEY>в querystring (config.py:48-50)domrf-kn— токены в URL не передаются, но search params могут содержать ИНН/cad_numПри
traces_sample_rate=0.055% всех outbound httpx-запросов отправят их URL целиком в GlitchTip.apiKey=в URL → token leakage в трейсы. Это уже security-relevant.Fix: добавить
before_send_transaction(илиbefore_breadcrumb) которая redact'ит query params для известных доменов. Минимально:Альтернатива (proще): передать
HttpxIntegration(...)с whitelisttracing_status_codesили вообщеtraces_sample_rate=0для httpx через отдельный фильтр. Самое чистое — обернуть httpx-вызовы Objective вurlsplitи держатьapiKeyв header'е вместо querystring (но это уже scope-creep для отдельного PR).5.
SENTRY_DSN=в.env.runtime.example/backend/.env.exampleorphanedПосле переименования
sentry_dsn→glitchtip_dsn, любой существующий.envили.env.runtimeна VPS с заданнымSENTRY_DSN=https://...молча игнорируется (pydanticextra="ignore"). Пользователь думает SDK работает, по факту no-op.Fix: в
config.pyдобавить migration warning:Либо — короче — в
backend/.env.exampleоставить обе переменные с пометкой "SENTRY_DSN deprecated, prefer GLITCHTIP_DSN" на 1-2 deploy-цикла.🟢 Low / nits
celery_app.py:13—logger = logging.getLogger(__name__)определён ПОСЛЕ import-блока, ноlogger.info(...)в init-блоке (line 38) вызывается ДО созданияcelery_app. Это работает (logger создан line 13, init line 16-39), но порядок странный — комментарий "SDK инициализируется в обоих процессах" между import-блоком иcelery_app = Celery(...). Ничего критичного.test_sentry_init.py:30-37— тестируют логику внутриwith patch(locally-definedglitchtip_dsn = None), а не реальный модуль. То есть unit-тест не покрывает фактический guard вmain.py:30— если кто-то удалитif settings.glitchtip_dsn:в main.py, тесты не упадут. Рекомендую:importlib.reload(app.main)с monkey-patchedsettings.glitchtip_dsn→ проверитьmock_init.called. Не блокирует merge.sentry-sdk[...]>=2.18.0без upper bound. sentry-sdk 3.x ожидается и может содержать breaking changes (e.g., legacy SDK API removal). Реальная установлена2.58.0(uv.lock:1907) — OK сейчас, но приuv lock --upgradeбез awareness можно подтянуть 3.x. Обычно для prod рекомендую>=2.18.0,<3.0. Минор.Cross-file impact analysis
.forgejo/workflows/deploy.yml:157-184не обновлён — пишетSENTRY_RELEASEкоторый теперь nobody читает (см. 🟠#1)backend/.env.runtime.example:6не обновлён — orphanedSENTRY_RELEASE=docker-compose.prod.yml:51комментарий устарел: "SENTRY_RELEASE=$IMAGE_TAG"README.md:109упоминает sed SENTRY_RELEASE.claude/rules/deploy.md"Sentry release tracking" секция устарела (но scope-rule, обновляется параллельно)app.workers.lifecycleimport — не сломан (PR не трогает worker hooks)logger.error/exceptionтеперь шлют events → estimated volume ~10-50 events/час on healthy prod, всплеск до тысяч при scraper outage. Для self-hosted GlitchTip приемлемо, ноtraces_sample_rate=0.05оставит достаточный budget. Per-task rate-limit на GlitchTip side стоит выставить.decisions/decision-glitchtip-frontend-sentry.md)build_beat_schedule()зарегистрирует 4 periodic tasks (scrape_kn, refresh_analytics, objective_sync, geo) — все будут видны в GlitchTip Crons после первых тиков. ✅Vault cross-check
decisions/decision-glitchtip-frontend-sentry.md— frontend pair, consistent design (no-op без DSN, Sentry-compatible)fixes/fixes-MOC.mdсодержит entry под этот PR с упоминанием "SENTRY_RELEASE удалено — теперь читается из os.getenv('GIT_SHA', 'unknown')" — то есть design intent был именно перейти с.env.runtimeна CI-env. Но CI-env механизм не реализован в deploy.yml — это и есть 🟠#1. Либо vault впереди кода, либо PR неполный.limitations/илиcode/patterns/по error-tracking — это первая GlitchTip migration, нормально.Performance findings
LoggingIntegration(level=INFO, event_level=ERROR)— breadcrumbs от INFO + events от ERROR. На worker'е с 8 concurrency возможен memory pressure при event burst'е (sentry-sdk буферизует до 30 events по умолчанию × 8 workers = 240 in-flight). Default acceptable, но мониторить worker RSS первую неделю.SqlalchemyIntegration— каждый query как span при traced transaction. Сtraces_sample_rate=0.05→ 5% запросов получат SQL spans. Negligible overhead.HttpxIntegration— wrapshttpx.AsyncClient.send→ +1 context manager per call. Тоже negligible.Security 🔒
send_default_pii=False— IP/cookies/headers не уходятPositive observations
StarletteIntegration(если init в lifespan, middleware не оборачивается)profiles_sample_rate=0.0— соответствует GlitchTip limitationsmonitor_beat_tasks=True— beat-уровень мониторинга подключенsend_default_pii=Falseпо умолчанию — секурно--no-verify/--amend/--forceмаркеровuv.lockupdated consistently — нет risk of import failure из-за missing extrasPre-flight
feat/204-glitchtip-backend— не mainRecommended next steps
os.getenv("GIT_SHA") or os.getenv("SENTRY_RELEASE") or "unknown"(минимум) ИЛИ обновить.forgejo/workflows/deploy.ymlчтобы писатьGIT_SHA=в.env.runtime(+ обновить.env.runtime.example,docker-compose.prod.yml:51,README.md:109)before_send_transactionredactingapiKey=/api_key=query params (🟡#4)backend/.env.example— оставитьSENTRY_DSN=(deprecated) рядом сGLITCHTIP_DSN=на 1-2 deploy-цикла или добавить runtime DeprecationWarningcelery_app.send_task('test')что event из worker fork'а доходит в GlitchTip.claude/rules/deploy.mdсекцию "Sentry release tracking"fixes/fixes-MOC.mdentry с финальным механизмом передачи release tag (CI env vs .env.runtime)Complexity / blast radius
Auto-merge policy
❌ BLOCKED for auto-merge per scope policy:
backend/app/main.py+backend/app/workers/celery_app.py+backend/app/core/config.py— backend corebackend/pyproject.toml(dependency growth — docker image rebuild required)Action: human merge required после fix 🟠#1 (release-tracking) + 🟡#4 (apiKey scrubbing). 🟡#3 и 🟡#5 можно follow-up PR.
Review fixes pushed in commit
8a04af5.Закрытые пункты:
Release tracking (HIGH) — исправлен fallback:
release=os.getenv("GIT_SHA") or os.getenv("SENTRY_RELEASE") or "unknown"в обоих файлах. Покрывает существующий deploy.yml (пишет SENTRY_RELEASE=$IMAGE_TAG).
apiKey scrub (HIGH) — новый модуль
app/observability/sentry_scrub.pyс
scrub_sensitive_query(before_send_transaction hook). Redact-итapiKey/api_key/token/access_token/secret из span data и request URL.
Импортируется обоими entrypoint'ами (main.py + celery_app.py).
Orphan SENTRY_DSN (HIGH) —
model_validator(mode="after")в config.pyчитает os.getenv("SENTRY_DSN") если glitchtip_dsn пуст, продвигает
значение с DeprecationWarning. .env.example дополнен deprecated-записью.
Отложено в follow-up PR:
(отдельный blast radius, согласовано).
Тесты: 11/11 passed. Ruff: ok. Mypy на наших файлах: 0 ошибок (129 pre-existing в других модулях).
Deep Code Review — re-review verdict (SHA
8a04af5)Summary
8a04af5· Mergeable: true · No conflictsapp/observability/sentry_scrub.py,__init__.py,model_validatorв config,before_send_transaction=scrub_sensitive_queryhook, release fallback chain, extended test coverageFixup verification (per previous comment #388)
1. 🟠 GIT_SHA release tracking → FIXED
main.py:37иcelery_app.py:25теперь:.forgejo/workflows/deploy.yml(пишетSENTRY_RELEASE=$IMAGE_TAGв.env.runtime) — release будет корректно проставлен сразу после merge без изменений deploy.yml. Future cleanup (переименованиеSENTRY_RELEASE → GIT_SHAв deploy.yml +.env.runtime.example+docker-compose.prod.yml:51+README.md:109+.claude/rules/deploy.md:31) — отдельный PR, согласовано.test_release_uses_git_sha_when_set / falls_back_to_sentry_release / falls_back_to_unknownкорректно покрывают цепочку.2. 🟠 Beat-monitor / fork SDK inheritance — Resolved (prefork inheritance + module-level init в celery_app.py покрывает оба процесса). Runtime verify после deploy остаётся в check-list PR.
3. 🟡 PII в логах via LoggingIntegration — Не покрыто в этом PR (логично —
send_default_pii=Falseуже фильтрует canonical PII fields; кастомный extra-scrubbing вынесен в follow-up если volume / sensitivity станут проблемой). Acceptable.4. 🟡 apiKey leak в HttpxIntegration traces → FIXED
app/observability/sentry_scrub.pyсscrub_sensitive_query(event, hint)подключён черезbefore_send_transaction=scrub_sensitive_queryв обоих entrypoint'ах.((?:api[_-]?[Kk]ey|token|access[_-]?token|secret)=)([^&\s]+)покрывает все известные query-секреты (ObjectiveapiKey=, generictoken=,access_token=,api_key=,secret=). Case-insensitive.span.data["url"/"http.url"/"http.target"],span["description"],event["request"]["url"]— это покрытие всех мест, куда HttpxIntegration / FastApiIntegration кладут URL.span.get("data")возвращает живой dict) — корректно: mutation in-place + return event.5. 🟡 Legacy SENTRY_DSN migration → FIXED
model_validator(mode="after")._promote_legacy_sentry_dsnвconfig.py:23-41читаетos.getenv("SENTRY_DSN")напрямую (минуя pydanticextra="ignore"), выдаётDeprecationWarningи автопродвигает вglitchtip_dsn..env.exampleобновлён с явной DEPRECATED-меткой и пояснением механизма. Корректный path для smooth миграции на VPS.Дополнительные findings (новый код)
🟢 Low / nits — none blocking
sentry_scrub.py:35—event.get("spans") or []корректно обрабатывает None (transaction events vs error events).customtoken=(будет matched какtoken=) — это OK, defensive scrubbing: false-positive redacts non-sensitive value, false-negative leaks secret. Trade-off правильный._redactвозвращает non-string значения as-is — graceful для dict с smешанными типами.Event | Noneкорректен — позволяет filter-out events (хотя текущая реализация всегда возвращает event).test_sentry_init.py—from sentry_sdk.types import Eventопосредованно проверен через тест import-блока.⚪ Не fixed (acceptable / follow-up)
sentry-sdk<3.0— минорный nit из прошлого reviewLoggingIntegrationextra-field scrubbing для inn/phone/email — отложен (low priority pending volume/sensitivity observation post-deploy)Security 🔒
send_default_pii=False(оба процесса)before_send_transaction=scrub_sensitive_queryredacts API keys из span URLs — ObjectiveapiKey=больше не утечёт в трейсы__init__.py(empty) — нет import-side-effectsConventions (backend.md / git-pr.md)
psycopg2/ norequests/ noprint()/ no f-string SQL--no-verify/--amend/--forcemarkersCross-file impact
pyproject.toml+uv.lockconsistent (extras добавлены в обе стороны)main.pyinit до FastAPI app — middleware покрытcelery_app.pyinit доCelery(...)— корректный порядокscrub_sensitive_queryимпортируется в обоих entrypoint'ах — единая логикаVerdict
✅ APPROVE — все блокирующие findings закрыты, security-критичная утечка apiKey предотвращена, legacy migration path прописан, тесты покрывают новую логику. Готов к merge.
Per user override 2026-05-16 ("после approve можешь мерджить любой scope") — мержу сам.