Перейти к содержимому

Всё, что присылает SDK, — ошибка, строка лога, спан, визит — попадает в Buglapse как событие: один JSON-объект с type и зависящим от типа payload. Типов пять, у каждого своя страница в этом разделе:

ТипКто отправляетКак хранится
exceptioncaptureException(), captureMessage(), интеграции ошибокисключение, сгруппированное в инцидент
loglog.info() / log.warn() / … , ConsoleIntegrationстрока лога
spanspan() / asyncSpan() / startInactiveSpan()спан и его метрики
sessionSessionIntegration (Browser), HttpServerIntegration (Node)одна строка на сессию, обновляется на месте
replayReplayIntegration (Browser)один сегмент записи сессии

Отдельного события для трейса нет: трейс — это просто traceId, общий для его спанов, и Buglapse создаёт его из первого спана с новым id (см. Span → Трейсы).

Руками эти объекты не собирают — это делает SDK, — но знать их форму полезно, когда пишете препроцессор, свой транспорт или читаете вывод makeConsoleTransport. Как отправлять их из языка без официального SDK — см. Свой SDK.

Envelope ​

У каждого события, какого бы типа оно ни было, одни и те же пять полей верхнего уровня:

json
{
    "type": "log",
    "timestamp": "2026-09-24T10:15:02.481Z",
    "tags": { "checkout.step": "payment" },
    "contexts": {
        "environment": "production",
        "release": "web@1.4.2",
        "user.id": "42",
        "trace.id": "5b7c0e1a-…"
    },
    "payload": { "level": "info", "message": "Order placed" }
}
  • type — exception, log, span, session или replay. Всё остальное отклоняется как некорректное. (trace, который отправляли SDK до 0.1.0, принимается и молча игнорируется.)
  • timestamp — время события на клиенте в ISO 8601. Обязательно; неразбираемое значение делает событие некорректным. Значение, опережающее часы сервера больше чем на минуту (часы устройства спешат), заменяется временем получения события.
  • tags — плоский словарь string → string из setTag() / scope.setTag(). Короткие индексируемые значения для фильтрации.
  • contexts — плоский словарь с ключами через точку (user.email, order.total) из setContext(), setUser(), environment/release в init() и интеграций. Значения null отбрасываются.
  • payload — часть, зависящая от типа; описана на странице каждого типа.

Контексты, которые читает сервер ​

Большинство контекстов хранятся как есть и показываются в событии, но некоторые ключи влияют на поведение:

  • environment, release — копируются в отдельные колонки; используются фильтрами, поиском по окружению и отслеживанием релизов.
  • user.* (user.id, user.email, user.username, user.name) — определяют пользователя; события привязываются к пользователю проекта. Сервер всегда добавляет user.ip и user.agent из самого запроса. user.agent используется фильтрами и хранится у пользователя проекта, а не в каждом событии — событие несёт то, во что он был разобран (browser.*, os.*, device.*).
  • device.userAgent — при приёме разбирается в контексты browser.*, os.* и device.*. Все остальные входящие ключи device.* отбрасываются.
  • trace.id, span.id — SDK проставляет их в любое событие, пойманное во время активного трейса/спана, чтобы панель могла связать лог или исключение с его трейсом.
  • replay.id — задаёт ReplayIntegration; связывает исключение с записью сессии, в которой оно произошло.

Отправка пачки ​

SDK собирает события в пачки на клиенте и отправляет их вместе (см. Транспорты):

http
POST https://app.buglapse.com/api/envelope
X-Buglapse-Auth: Buglapse buglapse_key={key}
Content-Type: application/json

{
    "events": [ { "type": "session", … }, { "type": "log", … } ],
    "sent_at": "2026-09-24T10:15:03.120Z",
    "client_report": { "discarded_events": [ { "reason": "before_send", "category": "log", "quantity": 3 } ] }
}

Обязательно только events — про sent_at см. Расхождение часов, про client_report — Отчёты клиента.

{key} — пользовательская часть DSN (https://{key}@{host}), только она определяет проект. В пачке можно свободно смешивать типы. navigator.sendBeacon() не умеет задавать заголовки, поэтому там SDK передаёт ключ параметром ?buglapse_key={key}. Отсутствующий или неизвестный ключ получает 401 («Invalid DSN: unknown project or key.»). Кроме ключа, запрос никак не авторизуется — вместо этого его Origin/Referer должен входить в разрешённые домены проекта (Настройки → Безопасность и приватность), иначе весь запрос отклоняется с 403.

Повтор пачки, которая уже прошла (транспорт после таймаута отправляет то же тело), распознаётся, и на него приходит 202 с { "success": true, "duplicate": true } — ничего не сохраняется и не тарифицируется дважды.

Расхождение часов ​

sent_at — время по часам клиента в момент сериализации пачки (ISO 8601). Если оно отличается от момента получения запроса сервером больше чем на минуту, считается, что часы клиента сбиты на эту разницу: timestamp каждого события, startTime / endTime спана и время хлебных крошек сдвигаются на неё, а событие получает contexts['clock_drift'] — поправку в секундах. Сессии не трогаются: начало сессии — часть того, что её определяет. Без sent_at или с неразбираемым значением ничего не сдвигается.

Повтор должен отправлять то же тело, включая sent_at (см. ниже) — он опаздывает максимум на секунды.

Отчёты клиента ​

О том, что SDK отбросил до отправки, — несэмплированный трейс, событие, отброшенное beforeSend, пачку, на которую так и не пришёл ответ, — сервер бы никогда не узнал. client_report сообщает ему об этом: discarded_events — список {reason, category, quantity}, где category — тип события (exception, log, span, replay). Числа показываются в Статистика и использование рядом с собственными итогами сервера и никогда не тарифицируются:

reasonКогдаПоказывается как
sample_rateспан трейса, который не прошёл tracesSampleRate; событие, сэмплированное на клиенте динамическим сэмплированиемsampled
ratelimit_backoffтип, который сервер попросил придержатьrate limited
before_sendbeforeSend вернул null или бросил исключениеfiltered
event_processorпрепроцессор / интеграция отбросили событие (dedupe, inboundFilters, …)filtered
network_errorнет ответа после повторов; переполнена очередь sendBeacondropped
send_errorсервер ответил 5xxdropped

Любая другая причина считается dropped. Пачка, на которую сервер ответил 4xx / 429, в отчёт не попадает — сервер её уже посчитал, — а сессии не попадают никогда. Отчёт может прийти с пустым списком events; повторно отправленное тело считается один раз.

Что происходит с каждым событием ​

Каждое событие пачки проходит одни и те же шаги по порядку и получает ровно один итог:

  1. Проверка — отсутствующий/неизвестный type или плохой timestamp → invalid.
  2. Тариф — тип, функцию которого тариф организации не включает, → disabled: не хранится, не тарифицируется, нигде не считается, а SDK велят прекратить отправку этого типа.
  3. Фильтры — приём выключен, сработал встроенный фильтр (краулеры, гидратация, загрузка чанков) или выражение фильтра проекта (Настройки → Фильтры, см. Фильтрация шума) → filtered.
  4. Сэмплирование — доля трейсов проекта, решаемая на трейс целиком (см. span), → sampled: не хранится, не тарифицируется и никогда не становится причиной для SDK притормозить.
  5. Динамическое сэмплирование — пока тип во всплеске или его месячного пула не хватит до конца месяца, он сэмплируется (см. Динамическое сэмплирование) → sampled. Внезапный наплыв, далеко превышающий потолок, придерживается на минуту → rate limited. Ни то, ни другое не расходует квоту.
  6. Квота — месячный лимит тарифа организации для этого типа (ошибки, спаны, логи, записи — у каждого свой пул, общий для всех проектов) → quota exceeded.
  7. Иначе событие сохраняется → processed.

Отфильтрованные, сэмплированные, некорректные, ограниченные по частоте и сверх квоты события не хранятся, но учитываются на странице Статистика и использование (ограниченные и сверх квоты — вместе, как Rate limited). Ответ сообщает итог по пачке:

json
{ "success": true, "processed": 18, "filtered": 1, "sampled": 0, "quota_exceeded": 0, "rate_limited": 0, "invalid": 1, "disabled": 0 }

Статус — 200, или 202, когда сервер ставит принятые события в очередь на запись, а не сохраняет их во время запроса: итог тот же, но обработанные события 202 могут появиться с небольшой задержкой. Считайте успехом оба.

Если из пачки ничего не прошло и хотя бы одно событие упёрлось в лимит частоты или квоту, ответ — 429 с заголовком Retry-After. Пока очередь приёма сервера перегружена, он так же отклоняет события span и replay (до того, как они потратят квоту), чтобы ошибки и логи продолжали приниматься.

Динамическое сэмплирование ​

Наплыв — скажем, каждая загрузка страницы бросает одну и ту же ошибку, пока лежит API, — сэмплируется, а не отрезается: проект продолжает получать представительную долю весь час и весь месяц, а то, что важно для мониторинга, никогда не сэмплируется.

Что сохраняется всегда. Первое событие новой группы за каждый час — новая ошибка (по типу, сообщению и строке, откуда брошена), новое сообщение лога (с заменёнными числами и id), новая транзакция (имя корневого спана) — сохраняется на любом тарифе, при любой нагрузке, в любой день месяца. Как и следующие несколько событий группы (её голова: 3 на Free, 10 на Pro, 25 на Team), логи уровня error и все события релиза в первый час после его первого события, пока хватает часового бюджета на них (20 / 30 / 40 % потолка); сверх него они сохраняются с долей в 4 раза выше.

Что сэмплируется. Всё остальное — с одной долей на тип, степенью двойки (½, ¼, ⅛, …):

  • Всплески. Защита от всплесков (Настройки → Фильтры, включена по умолчанию) ограничивает каждый из exception, log, replay и span удвоенным средним часовым объёмом за предыдущие два часа, но не ниже порога тарифа (1000 / 5000 / 20 000 в час на Free / Pro / Team). Среднее считается только по принятым событиям, так что всплеск не может поднять собственный потолок. Пока текущий темп превысил бы потолок до конца часа, тип сэмплируется до него. Защита не действует, пока у проекта нет событий старше двух часов, так что новую установку не сэмплируют.
  • Месячный пул. Пока при текущем темпе организации пула не хватит до конца месяца, этот тип дозируется так, чтобы хватило. Часть каждого пула (5 / 10 / 10 %) резервируется под события, которые сохраняются всегда, так что новая ошибка попадёт и в конце загруженного месяца.

Решение принимается на трейс (спаны, а также ошибки и логи, пойманные во время трейса) и на запись сессии, так что трейс или запись сохраняются или отбрасываются целиком. Сессии не сэмплируются никогда. Каждое сохранённое событие помнит долю, с которой сохранено, и счётчики — срабатывания инцидентов, счётчики релизов, обзор и главная — экстраполируются по ней. Пока тип сэмплируется, обзор проекта сообщает об этом, а алерт Всплеск уходит раз на тип и час.

Сэмплированные события не хранятся и не тарифицируются, никогда не заставляют SDK притормозить и учитываются как Sampled на странице Статистика и использование. Пока тип сэмплируется, каждый ответ несёт его долю и самые горячие группы:

X-Buglapse-Sample-Rates: log=0.125;span=0.03125
X-Buglapse-Hot-Groups: log=1a2b3c4d,5e6f7a8b;span=9c0d1e2f

SDK сам сэмплирует события этих групп с этой долей, а не отправляет то, что сервер отбросил бы, — всё остальное (новая группа, лог уровня error) отправляется как обычно. Свой клиент может игнорировать оба заголовка.

Аварийное ограничение. Наплыв, приходящий быстрее 50 × потолка, слишком быстр, чтобы сэмплирование успевало: его остаток отклоняется на минуту, а новые группы всё равно проходят. Как и для квоты, ответ сообщает, какие типы придержать и на сколько, даже при 200/202:

X-Buglapse-Rate-Limits: 60:log

Транспорт SDK до этого времени отбрасывает такие типы на клиенте, а не отправляет пачки, которые всё равно отклонят. Свой клиент должен делать так же. Тот же заголовок называет типы, чей месячный пул исчерпан, и типы, которых нет в тарифе.

Квоты кратко ​

Каждое сохранённое событие идёт в месячный лимит своего типа — у каждого отдельный пул, общий для всех проектов организации, так что наплыв одного типа никогда не блокирует другой:

ТипУчитывается в
exceptionмесячный лимит ошибок
logмесячный лимит логов
spanмесячный лимит спанов — метрики спана ничего не стоят
replayмесячный лимит записей — по одному на сегмент
sessionнигде — сессии бесплатны