Выгрузки
Заявка на файл со списком ошибок или сырых событий проекта — CSV, JSON или NDJSON,
собирается в фоне и ждёт скачивания на странице /projects/{id}/exports.
Что выгружается
Два вида заявки:
- Группы ошибок — та же таблица, что список Проблем: заголовок, culprit (место в коде), уровень, статус, число попаданий, первое/последнее появление, окружения, ответственный, ссылка на группу.
- События — сырые события выбранной группы либо всего проекта: время,
идентификатор события и группы, уровень, сообщение, тип и значение исключения,
окружение, релиз, имя сервера, SDK,
trace_id, идентификатор/IP/email пользователя, теги. В CSV — только эти колонки (полный объект превратил бы таблицу в нечитаемое полотно); в JSON/NDJSON — событие целиком, включая stacktrace, contexts, breadcrumbs иrequest.
Заявка ставится с фильтром по периоду и окружению — тем же, что выбран на странице ошибок/событий в момент постановки; период фиксируется в саму заявку, дальнейшее изменение выбора времени на странице на уже поставленную заявку не влияет.
Порядок строк — часть контракта файла, не случайность: группы отсортированы по последнему появлению, от новых к старым, а группы с ОДИНАКОВЫМ временем последнего появления — по убыванию id (самая недавно созданная из них первой). На него можно полагаться при обработке файла программой — правило зафиксировано и не меняется молча.
Форматы
| Формат | Что это | Когда использовать |
|---|---|---|
| CSV | Табличный файл, запятая-разделитель, кодировка UTF-8 с BOM | Открыть в Excel/таблицах, быстро просмотреть |
| JSON | Один массив объектов | Импорт в другую систему, программная обработка |
| NDJSON | По одному JSON-объекту на строку | Потоковая обработка большого файла без загрузки целиком в память |
Если Excel не разбил CSV-файл на колонки. Файл — валидный UTF-8 CSV с запятой в качестве разделителя; на локали, где Excel ждёт точку с запятой (например, русская Windows), двойной клик по файлу может свалить всё в один столбец. Откройте файл не двойным щелчком, а через Данные → Из текста/CSV (или Файл → Импорт) и явно укажите запятую как разделитель — так колонки разложатся правильно независимо от локали Excel.
Формат выбирается явно на каждой из четырёх точек постановки заявки: на самой странице «Выгрузки», и на компактных раскрывающихся формах — «Экспорт групп»/«Экспорт событий» на странице Проблемы и «Экспорт событий группы» на странице отдельной ошибки. У всех четырёх — один и тот же выпадающий список CSV/JSON/NDJSON и (если доступна) галка «Выгружать PII без маскирования», описанная ниже.
Кто может выгружать
Создавать заявки, скачивать и удалять свои — любой оператор проекта (тот же
уровень доступа, что у настроек алертов и подавления). Скачивание перепроверяет
доступ к проекту в момент скачивания, а не только на постановке: доступ мог
быть отозван позже — чужая или недоступная заявка отвечает 404, а не 403.
В списке на странице «Выгрузки» админ или владелец организации видит ВСЕ заявки проекта — свои и чужие; оператор без этой роли видит только свои собственные. Список — не больше 50 последних заявок и без пагинации: более старые заявки (или чужие — у обычного оператора) в нём просто не появляются.
Удалить чужую заявку может только админ или владелец организации; удаление доступно только для завершённых заявок (готова, ошибка, истёк срок) — у ещё выполняющейся заявки кнопки удаления нет, вместо неё пояснение, что заявка ещё в работе. Само удаление — двухшаговое: кнопка ведёт на страницу подтверждения, необратимое действие происходит только после явного подтверждения на ней.
PII: маскирование по умолчанию
user_email и user_ip по умолчанию заменяются на [masked]; user_id —
на псевдоним (случайная строка, стабильная только внутри одного файла, см.
ниже «Псевдонимы user_id не сопоставимы между выгрузками»), а не остаётся
как есть и не заменяется той же статичной маской, что email/IP: псевдоним,
в отличие от [masked], позволяет посчитать, сколько РАЗНЫХ пользователей
затронуто, не раскрывая ни одного исходного значения. В JSON/NDJSON та же
маска [masked]/скраб применяется к ключам request, contexts,
stacktrace и breadcrumbs, похожим на персональные данные (локальные
переменные фрейма, URL с query-параметрами).
Галка «Выгружать PII без маскирования» на форме постановки доступна только
админу или владельцу организации — оператору проекта без этой роли она не
показывается, а переданное значение молча игнорируется (заявка всё равно
ставится с маской, а не отказом). При включённой галке user_id тоже
уезжает как есть, без псевдонимизации. Факт применения галки виден в списке
заявок каждому, кому видна сама заявка.
Если на приёме включено серверное маскирование (GOTCHA_SCRUB_EMAIL/
GOTCHA_SCRUB_IP, см. Приватность), user_email/user_ip
уже занулены в хранилище на момент приёма события. Галка «Выгружать PII без
маскирования» не воскрешает то, что не было сохранено — если адреса и так
пустые, снятие маски
на выгрузке ничего не изменит.
В выгрузке групп (kind=issues) той же галкой и той же маской [masked]
управляется колонка assignee_email — email пользователя, назначенного на
группу: это прямой идентификатор пользователя, а не деталь самой группы.
Пустая колонка (группа без назначенного) маской не заменяется — как и
user_email/user_ip выше.
Псевдонимы user_id не сопоставимы между выгрузками
Псевдоним user_id строится по случайному одноразовому ключу, который
генерируется заново на КАЖДУЮ заявку и нигде не сохраняется. Следствие:
внутри ОДНОГО файла одинаковый user_id всегда даёт одинаковый псевдоним
(можно посчитать уникальных пользователей события/группы), но у ДВУХ разных
файлов — даже поставленных с одинаковым фильтром одну за другой — один и тот
же живой пользователь получит РАЗНЫЕ псевдонимы. Склеить две выгрузки по
user_id нельзя, и это осознанное поведение, а не дефект: обратное дало бы
корреляцию активности пользователя между файлами, которую никто не заказывал.
Пометка об этом — не только здесь. Файлы выгрузки (CSV/JSON/NDJSON) остаются
ЧИСТЫМИ: CSV — только строка колонок и строки данных без единого
комментария, JSON — только массив записей, NDJSON — только однородные
строки; ни один из трёх форматов не несёт служебного элемента, который
пришлось бы отличать от данных. Машиночитаемые сведения о заявке —
scope_issue_id, filter_code и, для events без снятой маски PII,
pseudonym_note — лежат РЯДОМ с файлом, тремя путями:
- Соседний ответ того же скачивания:
GET .../exports/{jobID}/download?meta=1отдаёт эти поля в JSON под теми же гейтами доступа, что и сам файл, — тому, кому нужен только идентификатор, не приходится парсить переведённую фразу вида «issue #123» из человекочитаемой сводки; - На странице «Выгрузки»: у ячейки колонки «Фильтры» есть атрибуты
data-scope-issue-id/data-filter-code/data-pseudonym-masked— те же значения рядом с локализованным текстом, читаемые скриптом без разбора текста; - В письме о готовности: нелокализуемая строка
gotcha-export-meta: job_id=… scope_issue_id=… filter_code=…и (когда применимо) сама пометка о псевдонимах добавляются к телу письма отдельно от переведённой фразы выше.
Лимиты
- 3 активных заявки (в очереди или уже собирается) на пользователя, 10 на проект одновременно — постановка новой при превышении отклоняется. Предел на пользователя общий по ВСЕМ его проектам, а не по каждому в отдельности.
- Потолок строк (
GOTCHA_EXPORT_MAX_ROWS, по умолчанию 200 000) и потолок размера файла (GOTCHA_EXPORT_MAX_BYTES, по умолчанию 256 МиБ): при достижении любого из них сборка останавливается, а заявка помечается «обрезана» — на странице это видно явной пометкой рядом со статусом, файл при этом всё равно доступен для скачивания. - Общий бюджет каталога выгрузок (
GOTCHA_EXPORT_DISK_BUDGET_BYTES, по умолчанию 5 ГиБ на инстанс, без пер-проектной квоты) и реальное свободное место на файловой системе, где лежитGOTCHA_EXPORT_DIR— оба резервируют место под MaxBytes ТЕКУЩЕЙ заявки, а не только под уже собранные файлы. Отказ по любой из двух причин — ВРЕМЕННЫЙ (заявка возвращается в очередь, до 3 попыток): нехватка места самоустраняется первым же проходом джанитора, который освобождает диск от истёкших файлов. От пользователя действий не требуется; если заявка всё же добита до статуса «ошибка» — за три попытки место так и не освободилось, нужно вручную освободить диск или поднятьGOTCHA_EXPORT_DISK_BUDGET_BYTES(см. Конфигурацию) и поставить заявку заново, сама она не перезапускается. - Выгрузка событий без выбранной конкретной группы (
kind=events, весь проект с фильтром) — если фильтр резолвится в более чем 20 000 групп ошибок, отказ окончательный (в отличие от нехватки места, повторных попыток не будет): нужно сузить период, окружение или поиск и поставить заявку заново. Потолок — константа продукта, переменной окружения не вынесен. Выгрузку групп ошибок и выгрузку событий ОДНОЙ уже известной группы (со страницы этой ошибки) этот отказ не касается.
Хранение и удаление
Готовый файл живёт GOTCHA_EXPORT_RETENTION_HOURS часов от момента завершения (по
умолчанию 168 — семь суток): по истечении джанитор удаляет файл с диска, а
заявку помечает статусом «истекла». Строка заявки в истории переживает файл —
она хранится не меньше 30 суток от завершения, а если GOTCHA_EXPORT_RETENTION_HOURS
задан больше 30 суток, то ровно этот срок (срок хранения строки растёт вместе
с TTL файла, но не бывает короче него), и только затем джанитор убирает её из
истории без следа. Автору приходит письмо ровно один раз на заявку — когда
она перешла в «готова» или «ошибка» (если настроена почта, см.
Конфигурацию); отдельного письма-напоминания об
истечении файла нет. Причину отказа не обязательно искать в письме — у
заявки со статусом «ошибка» она показана прямо в её строке в списке на
странице «Выгрузки». Статус на странице «Выгрузки» сам не обновляется, пока
заявка ещё в очереди или собирается — узнать о готовности можно письмом или
перезагрузкой страницы; скачать файл стоит до истечения его собственного
срока, повторно он уже не появится.
Файлы выгрузок и сам каталог GOTCHA_EXPORT_DIR — единственное место
продукта, где персональные данные (email/IP пользователя, contexts, request)
ложатся на диск: права на них сужены до владельца процесса (файл 0600,
каталог 0700). Это касается только НОВОГО каталога — MkdirAll не меняет
права уже существующего, поэтому на инстансе, поднятом до этой правки,
каталог остаётся с прежними правами до ручного chmod 0700.
Одноинстансное ограничение
Файлы выгрузок лежат на диске того процесса, который их собрал
(GOTCHA_EXPORT_DIR). Раздел рассчитан на одноинстансный деплой: при
нескольких репликах приложения за балансировщиком скачивание может попасть на
реплику, где файла нет физически. Горизонтальное масштабирование этой фичи
потребует общего хранилища (сетевой том, S3-совместимое хранилище и т.п.) —
сейчас это не реализовано.
Перезапуск процесса
Заявка, которую воркер как раз собирал в момент штатной остановки процесса
(рестарт, деплой, docker compose up -d), не остаётся зависшей в статусе
«выполняется» и не считается отказавшей: она возвращается в очередь без
траты одной из трёх попыток и полностью пересобирается заново на следующем
старте — файл, недописанный до остановки, отбрасывается. Письмо об ошибке
в этом случае не приходит: с точки зрения заявки ничего не сломалось,
сборка просто продолжится минутой позже. Заявок, ещё стоявших в очереди на
момент остановки, эта пауза не касается вовсе — они и так ждут своей
очереди.
Воркер и джанитор выгрузок поднимаются только в процессах с режимом web
или all — там же, где обрабатывается скачивание. При раздельном
развёртывании --mode=ingest/--mode=web (см. Обновление)
это устраняет самый опасный сценарий (заявку собирает реплика без единого
маршрута выгрузок), но не снимает одноинстансное ограничение целиком: если
web-процесс запущен в НЕСКОЛЬКИХ репликах за балансировщиком, джанитор
каждой из них по-прежнему видит только свой локальный диск. Если истёкшую
заявку собрала соседняя web-реплика, джанитор текущей реплики не находит
файл (ENOENT) и всё равно помечает заявку «истекла» — реальный файл на
диске другой реплики этим не удаляется и остаётся лежать там до чистки
истории заявок (не меньше 30 суток от завершения), занимая место в
GOTCHA_EXPORT_DISK_BUDGET_BYTES той реплики.