Skip to content

ТЗ — Sentry/трекинг ошибок (cms/sentry)

Слой: 🔵 инфра-модуль (обязательный в managed-парке) · Зрелость доноров: ★★ · Донор: — Статус: ТЗ к разработке

Назначение и возможности

Интеграция со Sentry для трекинга исключений и производительности. Отдельный DSN на каждый сайт студии, релиз-теги привязаны к версии ядра и модулей — позволяет отследить всплеск ошибок сразу после обновления и использовать это как гейт.

  • DSN per-сайт из .env, без хранения секрета в БД
  • Release-теги = версия ядра + список версий модулей на момент релиза
  • Гейт cms:upgrade: сравнение частоты ошибок до/после обновления
  • Breadcrumbs без ПДн (маскирование email/телефон/токены перед отправкой)
  • Группировка ошибок по модулю-источнику (из stack trace и манифестов)
  • Алерт при превышении порога новых ошибок за окно времени
  • Просмотр последних ошибок прямо в Filament без перехода в Sentry
  • Таймаут отправки, не позволяющий недоступности DSN замедлить пользовательский запрос
  • Обязателен в managed-парке как источник данных для гейта апгрейда наравне с cms/health
  • Не дублирует cms/health: cms/sentry — трейсинг конкретных ошибок/производительности (stack trace, breadcrumbs, релиз-гейт по частоте ошибок), cms/health — агрегат доступности и порогов (аптайм, health-чеки); сбой самого обращения к API Sentry эскалируется как отказ провайдера через cms/health (стандарт §12), отдельного канала эскалации у sentry-модуля для этого нет

Зависимости и выключение

requires: ядро · provides: — (не декларирует канонического контракта — модуль единственный в своей роли, конкурирующей реализации нет)

Модуль помечен обязательным в managed-парке (см. заголовок ТЗ); зафиксировано ревизией 14.07.2026 и §3 стандарта (подраздел «Обязательные модули managed-парка»): disable cms/sentry недоступен клиентским ролям — доступен только studio-роли, с обязательным подтверждением и записью в аудит; кнопка выключения в админке клиента показывает «модуль обязателен по условиям поддержки». Правило «выключение = деградация» при этом не отменяется: если модуль всё же выключен студией или упал, сайт остаётся жив. См. ревизия ядра 14.07.2026.

Поведение при выключении: исключения продолжают падать в штатный лог Laravel, отправка в Sentry не выполняется — деградация внешней трассировки, приложение работает штатно. cms/updates обязан деградировать симметрично: при выключенном sentry гейт cms:sentry:gate внутри cms:upgrade пропускается с явным предупреждением в лог, апгрейд не блокируется отсутствием данных, которые физически некому собрать.

Стоимость внешнего сервиса (критерий матрицы v2.2): у Sentry-плана студии есть квота событий/трасс в месяц — общая на все сайты парка, не индивидуальная per-DSN. sentry.sample_rate и sentry.traces_sample_rate — не только рычаг шума, а прямой рычаг расхода квоты. При исчерпании квоты Sentry на своей стороне молча дропает или rate-limit'ит новые события — модуль не считает это сбоем приложения (исключения всё равно попадают в штатный лог Laravel), но администратор должен иметь возможность узнать о приближении к лимиту: если Sentry API отдаёт остаток квоты — эскалация в cms/fleet-dashboard/уведомление; если нет — ограничение видимости документируется явно как факт (студия не может гарантировать своевременное предупреждение об исчерпании квоты стороннего сервиса).

Модель данных

Своих таблиц нет — использует Sentry как внешнее хранилище; локально кешируется только сводка последних событий (Redis, TTL) для виджета Filament. Кеш-ключ сводки — с TTL не более sentry.spike_window_minutes, без отдельной БД-таблицы и, соответственно, без миграций. Release-теги не хранятся в БД CMS — источник истины версий на момент релиза читается из реестра модулей ядра в момент вызова cms:sentry:release, сам Sentry хранит теги на своей стороне как атрибут события/транзакции.

ПДн-паспорт (матрица v2.2): своих таблиц с ПДн в БД CMS нет — единственное потенциальное касание ПДн до скраббинга — breadcrumbs (см. «Безопасность»). Удалённое хранение событий (с уже замаскированными ПДн, если скраббинг не пропустил утечку) — на стороне Sentry, внешнего сервиса студии; ретеншн событий там управляется тарифом Sentry, не настройками CMS. Хуки ядра «выгрузить всё по субъекту» и «забыть по запросу» (152-ФЗ) к самому модулю не применимы — ему нечего выгружать/забывать локально; обязанность модуля — не допустить попадания ПДн в Sentry вовсе (обязательный скраббинг), а не управлять их последующим хранением там. PrivacyRegistry (п.15 ревизии ядра): модуль не регистрирует обработчик — cms:privacy:export/cms:privacy:forget по субъекту Sentry-события не затрагивают, так как они уже обезличены скраббингом до отправки и локально агрегировать по субъекту нечего.

Входные и выходные данные

Входы

ИсточникДанные/поляЧем валидируется
Необработанное исключение ядра/модуля (внутренний перехват SDK)stack trace, breadcrumbs, контекст запросаSDK-перехват, не пользовательский ввод; проходит обязательный PII-скраббинг перед отправкой
Хук деплоя → cms:sentry:releaseверсия ядра, список версий модулейкоманда читает состояние из реестра модулей ядра, не принимает произвольный ввод
cms:sentry:gate (вызов из cms:upgrade)release до/послесравнение частоты ошибок по API Sentry, таймаут на внешний вызов обязателен
Filament: просмотр сводкибез полейpermission sentry.view
.envconfig('sentry.*')DSN, окружениене проходит через settings-store; секрет только из .env

Выходы

ПотребительДанныеФормат
Sentry (внешний сервис)событие исключения/транзакции с release-тегом, breadcrumbs после скраббингаHTTPS, формат Sentry SDK
Filament-виджет «Ошибки за 24 часа»сводка из Redis-кеша + ссылка на SentryBlade через сервис модуля
cms/updates (гейт cms:upgrade)итог сравнения error-rate до/послесервис-вызов (requires, канал 4)
Redis-кеш сводкипоследние события, TTLвнутреннее хранилище виджета

Настройки (группа sentry)

КлючТипДефолтaffectsPageCacheОписание
sentry.enabledbooltrueнетВключить отправку событий
sentry.sample_ratefloat1.0нетДоля событий-исключений для отправки
sentry.traces_sample_ratefloat0.1нетДоля трасс производительности
sentry.pii_scrub_enabledbooltrueнетМаскирование ПДн в breadcrumbs
sentry.upgrade_gate_enabledbooltrueнетУчитывать всплеск ошибок как гейт апгрейда
sentry.error_spike_thresholdint20нетПорог новых ошибок за окно для алерта
sentry.spike_window_minutesint30нетОкно подсчёта всплеска ошибок для алерта и гейта
sentry.dsn_timeout_msint500нетТаймаут отправки события в Sentry — не должен задерживать основной запрос
sentry.scrub_fieldsarray["email","phone","password","token","authorization"]нетСписок полей, обязательных к маскированию в breadcrumbs

sentry.sample_rate и sentry.traces_sample_rate — не только регулятор шума, но и прямой рычаг расхода квоты Sentry-плана студии (критерий «Стоимость внешних API» матрицы v2.2, детали — «Зависимости и выключение»); sentry.error_spike_threshold — лимит матрицы «Лимиты и квоты» на срабатывание алерта, не квота самого Sentry.

Kill-switch (критерий матрицы v2.2): sentry.enabled — штатная реализация этого критерия: аварийное отключение отправки событий без выключения модуля целиком (сводка, права, гейт-логика остаются доступны/консистентны, просто новые события не летят) — дешевле и быстрее полного disable, доступного только studio-роли.

API

Отдельного API нет — админ-CRUD через Filament (просмотр сводки последних ошибок). Гейт cms:sentry:gate и фиксация релиза доступны только как artisan-команды (--json), не как HTTP-эндпоинты — вызываются из пайплайна cms:upgrade и CI/деплой-скрипта соответственно, не из браузера.

Компоненты

Виджеты: Filament-виджет «Ошибки за 24 часа» со ссылкой на Sentry (группировка по модулю-источнику, топ-N по частоте). Filament: страница сводки по релизам (список ReleaseTagged с count ошибок после каждого, тренд «стало лучше/хуже» относительно предыдущего релиза). Команды: cms:sentry:release --json (фиксация release-тега при деплое — версия ядра + версии всех модулей на момент релиза), cms:sentry:gate --json (проверка всплеска ошибок для cms:upgrade, таймаут на вызов API Sentry обязателен), cms:sentry:doctor --json (валидность DSN, доступность SDK).

Демо-контент: не применимо — модулю нечего сидировать demo-данными (события приходят из реальных исключений, не из фикстур), галерея/playground показывают виджет с пустым состоянием «ошибок не было» (см. «UX-требования»).

События и обмен

СобытиеКогдаPayload
ErrorSpikeDetectedпревышен порог новых ошибок после релизаrelease, count, threshold
ReleaseTaggedзафиксирован новый release-тегrelease, core_version, modules

FilterBus и provides-контракты не используются. Слушает: не подписывается на события других модулей — источник данных внешний (Sentry SDK перехватывает исключения ядра).

Таблица взаимодействий

Сущность/модульКаналНаправлениеЧто происходит
Исключения ядра и всех модулейвнутренний перехват SDK (вне пяти каналов, глобальный exception handler)inКаждое необработанное исключение становится событием Sentry с release-тегом
cms/updatesrequires → сервис-вызов (канал 4), cms:sentry:gateoutСравнение error-rate до/после — итог используется как часть гейта апгрейда
Sentry (внешний сервис)внешняя интеграция (HTTPS, вне пяти каналов)outПриём событий/трасс, источник сводки для виджета
Хук деплоявнешний относительно рантайма (CI/деплой-скрипт вызывает команду)inФиксация ReleaseTagged при каждом деплое

Фоновая работа

Своей именованной очереди нет: отправка событий в Sentry выполняется SDK асинхронно (внутренний транспорт пакета), фиксация release-тега — по хуку деплоя, не по расписанию ядра.

Эксплуатация (§15). Метрики: rate отправленных/сброшенных по таймауту событий, доля таймаутов DSN (dsn_timeout_ms от общего числа попыток отправки), задержка выполнения cms:sentry:gate относительно cms:upgrade. Алерты: ErrorSpikeDetected — событие уже формализовано (см. «События и обмен»).

СимптомЧто проверитьКоманда
DSN недоступен, события не долетаютсеть до Sentry, валидность DSN в .envcms:sentry:doctor --json
Всплеск ошибок после релизасводка ошибок в Filament/Sentry по release-тегуоткат через пайплайн cms/updates (cms:upgrade --rollback)
sample_rate/traces_sample_rate слишком высоки, заметная нагрузка на сайтобъём трафика на traces_sample_rate, доля запросов с трассировкойпонизить в настройках sentry, без деплоя
Первый релиз без базы для сравненияистория релизов пуста в cms:sentry:releaseне инцидент — гейт помечает «нет базы для сравнения», не блокирует
Гейт cms:sentry:gate падает по таймауту API Sentryдоступность API Sentry, dsn_timeout_mscms:sentry:doctor --json, при системной недоступности — временно снизить upgrade_gate_enabled

Бэкап/рестор: своих таблиц и файлов нет — модулю нечего добавлять в бэкап сайта и нечего восстанавливать после рестора; вся история событий живёт только на стороне Sentry.

Производительность и кеш

Объёмы: на активном сайте — от единиц до сотен исключений в сутки (при sample_rate=1.0 все идут в Sentry), трассы производительности — доля traces_sample_rate от запросов, на нагруженном сайте это может быть заметный процент трафика — держать 0.1 как дефолт и не поднимать бездумно. Горячий путь — не сама отправка (она асинхронна и не должна блокировать ответ), а вызов dsn_timeout_ms: недоступность DSN не должна замедлять пользовательский запрос — таймаут строгий, сбой отправки проглатывается модулем (лог уровня warning, не exception пользователю). Транспорт SDK — асинхронный, с внутренним пулом/буфером ограниченного размера: HTTP-ответ пользователю не ждёт результата отправки независимо от исхода. Если DSN недоступен дольше, чем вмещает буфер транспорта, событие теряется безвозвратно — приемлемая потеря (диагностика, не бизнес-транзакция), не требующая собственной retry-очереди модуля. Redis-сводка виджета — TTL короче spike_window_minutes, инвалидация естественная по истечении, без событий ядра. Собственных тегов page-cache не объявляет, на публичный рендер сайта не влияет. Виджет сводки в Filament — единственный горячий путь модуля внутри админки: агрегация из Redis, не прямой запрос к API Sentry на каждый рендер дашборда (иначе открытие админки становится зависимым от доступности стороннего сервиса).

Безопасность

Границы входа: DSN и креды Sentry — только .envconfig('sentry.*'), хранение в БД запрещено. Breadcrumbs проходят обязательное маскирование ПДн (scrub_fields) перед отправкой — двойной барьер: маскирование на стороне модуля + отключаемые интеграции SDK, собирающие тело запроса. Специфичные векторы: SQL-бindings в breadcrumbs могут содержать пароли/токены пользователей из форм — скраббинг применяется не только к явным полям контекста, но и к параметрам запросов и заголовкам (Authorization, Cookie); утечка DSN в клиентский JS (при ошибочной публикации frontend-конфига) позволяет постороннему слать мусорные события — DSN серверный, во frontend-бандл не попадает. Доступ к сводке в Filament — только под явными permissions.

Скраббинг ПДн — два уровня, не один: (1) точное совпадение имени поля из scrub_fields (email, phone, password, token, authorization) в именованных контекстах (request-параметры, заголовки, extra-контекст breadcrumb) — маскируется всё значение; (2) эвристика по значению в свободном тексте (сообщение исключения, произвольные строки контекста), где ПДн не привязаны к имени поля — например email-подобная подстрока внутри текста сообщения об ошибке ищется regex-паттерном (через ReDoS-валидатор ядра) и маскируется независимо от того, в каком поле она оказалась. Оба уровня обязательны: скраббинг только по имени поля пропускает ПДн, случайно попавшие в свободный текст (например интерполированный email в сообщении исключения валидатора).

Матрица ролей (permissions sentry.view, sentry.manage):

Рольsentry.viewsentry.manage
Администратордада
Менеджерданет
Редакторнетнет
Studioдада (+ право выключить модуль в managed-парке, см. «Зависимости и выключение»)

UX-требования

Админ: пустое состояние «ошибок за последние 24 часа не было» вместо пустого списка; ссылка «открыть в Sentry» рядом с каждой ошибкой сводки — не требует ручного поиска по release/timestamp; сообщение о недоступности Sentry SDK/DSN — «отправка ошибок временно недоступна, исключения пишутся в штатный лог» на человеческом языке, не трассировка; группировка ошибок по модулю-источнику видна сразу в сводке, не требует перехода в Sentry для базовой диагностики «что чаще всего ломается».

Посетитель: не применимо — отправка в Sentry не должна быть видна пользователю ни при каких условиях (в т.ч. при таймауте DSN — деградация тихая).

Крайние случаи и типовые баги

  • утечка ПДн/секретов в трейсы → breadcrumbs обязаны проходить скраббинг (scrub_fields) до сериализации в SDK, не после — тест на утечку конкретно проверяет, что замаскированное значение никогда не попадает в исходящий HTTP-payload, включая вложенные массивы и SQL-bindings;
  • недоступность DSN не должна замедлять запросыdsn_timeout_ms — жёсткий таймаут; при недоступности Sentry основной запрос пользователя не ждёт ответа от внешнего сервиса, ошибка отправки логируется, не пробрасывается наверх;
  • двойная отправка одного и того же исключения (retry SDK при флапе сети) → идемпотентность на стороне Sentry по fingerprint события — модуль не обязан дедуплицировать сам, но не должен создавать искусственные дубли повторной ручной отправкой;
  • выключение модуля посреди обработки исключения → недопустимая гонка: если sentry.enabled меняется в момент отправки, текущая отправка либо завершается, либо тихо отбрасывается — исключение в любом случае попадает в штатный лог Laravel (двойная гарантия, не полагаться только на Sentry);
  • отсутствие requires/suggests-зависимостей — модуль ни от кого не зависит, но сам является зависимостью гейта cms:upgrade через cms/updates: если sentry выключен, cms/updates обязан пропускать error-rate-гейт с предупреждением, а не падать при вызове недоступного сервиса;
  • сбой/таймаут внешнего API Sentry при cms:sentry:gate → таймаут обязателен и здесь отдельно от dsn_timeout_ms отправки событий; при недоступности API гейт возвращает «неизвестно» с явным предупреждением в cms:upgrade, не блокирует и не пропускает молча апгрейд как «чисто»;
  • пустая история релизов (первый деплой) → ErrorSpikeDetected не может сравнивать «до/после» без предыдущего релиза — гейт для первого релиза инсталляции пропускается с пометкой «нет базы для сравнения», не считается ложным провалом;
  • огромный всплеск ошибок (например, взрыв 500 на всех страницах сразу после релиза) → error_spike_threshold/spike_window_minutes должны триггерить алерт быстро, но не заспамить — один агрегированный алерт на всплеск, не по одному на каждую ошибку внутри окна;
  • измерения locale/city/site — Sentry-события не несут этих измерений явно; на cms/multisite стоит явно зафиксировать: DSN общий на инсталляцию или per-сайт — при общем DSN ошибки разных сайтов парка смешиваются в одном проекте Sentry без тега site_id, что затрудняет диагностику конкретного сайта;
  • противоречивая комбинация настроек: sentry.upgrade_gate_enabled=true при sentry.enabled=false → аналогично health, гейт не может опираться на данные, которые не собираются; cms:upgrade обязан явно предупреждать, а не тихо считать гейт пройденным;
  • обязательность модуля в managed-парке и disable — зафиксировано ревизией 14.07.2026: disable cms/sentry в managed-парке недоступен клиентским ролям — доступен только studio-роли с обязательным подтверждением и записью в аудит; правило «выключение = деградация» при этом сохраняется. См. ревизия ядра 14.07.2026 и §3 стандарта (подраздел «Обязательные модули managed-парка»);
  • исчерпание квоты Sentry-плана → Sentry на своей стороне молча дропает/rate-limit'ит новые события — не считается сбоем приложения (исключения всё равно попадают в штатный лог Laravel), но не должно оставаться незамеченным: при доступности остатка квоты через API Sentry — эскалация в cms/fleet-dashboard, иначе — задокументированное ограничение видимости (см. «Зависимости и выключение»);
  • потеря события при длительной недоступности DSN → внутренний буфер транспорта SDK ограничен по размеру; если DSN недоступен дольше, чем вмещает буфер, событие теряется безвозвратно — приемлемо (диагностика, не бизнес-транзакция), HTTP-ответ пользователю при этом не блокируется независимо от исхода отправки (детали — «Производительность и кеш»);

Донорский код

Донор: — (новая разработка, обёртка над sentry/sentry-laravel)

Миграция legacy-данных (§16): не применимо — модуль не имеет доменных данных для переноса с донорских сайтов (события живут в Sentry, не в БД CMS), cms:sentry:import-legacy не требуется.

Тесты и приёмка

  • [ ] Контрактный тест: DSN берётся из .env через config(), не хранится в БД
  • [ ] Health-чек модуля отражает доступность Sentry SDK и валидность DSN
  • [ ] Гейт cms:upgrade блокирует релиз при всплеске ошибок выше порога
  • [ ] При выключении модуля исключения не теряются — попадают в штатный лог
  • [ ] Breadcrumbs проходят маскирование ПДн перед отправкой (тест на утечку), включая SQL-bindings и заголовки авторизации
  • [ ] Недоступность/таймаут Sentry (отправка и cms:sentry:gate) не увеличивает время ответа пользователю и не роняет cms:upgrade
  • [ ] Первый релиз инсталляции не считается ложным провалом гейта из-за отсутствия базы для сравнения
  • [ ] Права sentry.view/sentry.manage разграничивают доступ к сводке
  • [ ] DSN не попадает в клиентский JS-бандл (контрактная проверка frontend-сборки)
  • [ ] Виджет сводки в Filament не делает синхронный запрос к API Sentry при каждом рендере дашборда
  • [ ] Disable модуля в managed-парке недоступен клиентским ролям — доступен только studio-роли с подтверждением и записью в аудит (контрактный тест на permission-гейт)
  • [ ] Исчерпание квоты Sentry-плана не считается сбоем приложения: исключения остаются в штатном логе Laravel, ограничение видимости расхода квоты задокументировано
  • [ ] Потеря события при недоступности DSN дольше внутреннего буфера транспорта не блокирует и не замедляет HTTP-ответ пользователю
  • [ ] Контрактный набор cms-testing пройден, пакет протестирован в testbench-изоляции
  • [ ] Feature-тест на каждый роут модуля; тестовая БД только sentry_test, migrate:fresh запрещён

Внутренняя база знаний студии