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

Обзор ​

Хлебная крошка — небольшая запись с временем о том, что произошло перед ошибкой: лог консоли, клик, сетевой запрос, переход. Они хранятся на клиенте в скользящем следе в памяти и прикрепляются к следующему пойманному исключению, так что вы видите последовательность событий, приведших к сбою, а не только сам сбой.

Хлебные крошки путешествуют только с исключениями. Логи, трейсы и спаны следа не несут — у вызова лога нет того, что «ведёт к нему», как у падения.

Добавление вручную ​

ts
Buglapse.addBreadcrumb({
    type: 'checkout',
    level: 'info',
    message: 'Applied promo code SUMMER10',
    data: { code: 'SUMMER10', discount: 0.1 },
})

try {
    await chargeCard(order)
} catch (error) {
    Buglapse.captureException(error) // уходит с крошкой выше уже в следе
}

Обязательно только message:

  • type — произвольная строка, по которой панель выбирает иконку и отрисовку (см. Хлебные крошки в панели). По умолчанию "debug".
  • level — log | info | warn | error | debug | success. По умолчанию "debug". Определяет цвет маркера крошки, независимо от type.
  • data — необязательный объект с подробностями (payload, тело ответа, значения формы). Показывается свёрнутым под крошкой в панели и раскрывается как JSON.
  • timestamp — проставляется автоматически (new Date().toISOString()) при вызове addBreadcrumb(); задавать его самим незачем.

Хлебные крошки общие для всего клиента, а не привязаны к одному вызову captureException(): всё, добавленное где угодно в приложении, копится в одном следе, пока следующее исключение его не отправит.

Слияние повтора с предыдущей крошкой ​

То, что происходит сериями, — опрос, повтор, обработчик прокрутки — заполнило бы след копиями одной строки. Для этого у addBreadcrumb() есть второй аргумент:

ts
Buglapse.addBreadcrumb(
    { type: 'sync', level: 'info', message: 'Synced the cart' },
    {
        merge: (previous) => previous.type === 'sync'
            ? { ...previous, data: { ...previous.data, count: (previous.data?.count ?? 1) + 1 } }
            : null,
    },
)

merge вызывается с последней крошкой следа (и не вызывается вовсе, если след пуст). Возвращённая крошка заменяет ту на месте, и ничего не добавляется; null добавляет новую крошку как обычно. beforeBreadcrumb сначала выполняется для входящей крошки — отброшенная им ни с чем не сливается, — и ещё раз для результата слияния перед сохранением: null там оставляет предыдущую крошку как была. Панель показывает data.count как ×N в строке.

Автоматические крошки ​

Вместо ручных вызовов addBreadcrumb() по всему приложению BreadcrumbsIntegration записывает такой же след автоматически: вызовы консоли, клики и нажатия клавиш в DOM, запросы fetch/XHR и переходы в SPA (history.pushState/popstate) становятся крошками без изменений кода. Она подключаемая — включите через integrations: [breadcrumbsIntegration()] в init() или переключателем в Настройки → Скрипт загрузчика проекта, если подключаетесь через CDN. Каждый источник (консоль/DOM/fetch/XHR/история) выключается отдельно; формат настроек — на странице BreadcrumbsIntegration.

Ручные вызовы addBreadcrumb() и автоматические крошки интеграции попадают в один след и перемешиваются по времени — выбирать одно из двух не нужно.

Клик или нажатие клавиши называет свой элемент так, как назвал бы человек, — click on button "Pay" in dialog "Confirm order" — по подписи, вашему data-buglapse-name или test id, но никогда по CSS-классам. Текст элемента управления для этого читается по умолчанию; data-buglapse-mask его скрывает. См. Как называется элемент.

Интеграция также читает след по ходу записи: повторы сливаются в одну крошку с data.count и data.duration_ms, а серия сверх порога называется в data.kind — rage_click, key_mash, request_loop, error_storm, а также slow_interaction и reload. Условия и пороги — в разделе Поведения.

Ограничение следа ​

След хранит не больше 50 последних крошек (MAX_BREADCRUMBS); когда он полон, при добавлении новой отбрасывается самая старая. Исключение, пойманное после долгой сессии, уносит только последние 50 крошек перед ним, а не всю историю сессии. Слитая крошка (см. выше) занимает одно место, сколько бы событий она ни представляла.

Хлебные крошки в панели ​

Страница исключения (или инцидента) показывает след хлебных крошек в хронологическом порядке над стектрейсом. Маркер каждой строки окрашен по level (error — красный, warn — жёлтый, success — зелёный, info — синий, log/debug — нейтральный) независимо от type — type только выбирает, какой компонент рисует строку и как он читает data. Крошка с data.count больше 1 показывает ×N в конце строки, а поведение (data.kind со значением rage_click, key_mash, request_loop, error_storm или slow_interaction) показывает вместо маркера значок предупреждения, а заголовок — в тоне предупреждения. Сопоставление сначала идёт точно по type, затем по префиксу до первой ./- (так что свой тип "cart.added" уходит на префикс cart, у которого нет своего компонента, и попадает на общую строку ниже).

  • console — заголовок «Console {level}» (например, «Console warn»). Подзаголовок читает data.args: массив исходных аргументов вызова консоли через пробел (не строки по отдельности проходят через JSON.stringify). Если data.args не массив, берётся message. error_storm помечается в конце строки как «error storm».
  • interaction (и устаревшие типы ui.click/ui.keypress) — заголовок очеловечивается из data.kind ("click" → «Click», "keydown" → «Keyboard», "input" → «Typing», "rage_click" → «Rage click», "key_mash" → «Key mash», "slow_interaction" → «Slow click» / «Slow key press» с длительностью в конце строки, всё остальное — с заглавной буквы); без data.kind он выводится из типа ui.* или становится «Interaction», если нет ни того, ни другого. Подзаголовок — data.target, имя элемента (button "Pay" in dialog "Confirm order"), с data.key впереди, если нажата именованная клавиша (Enter · input "Search"), иначе message. Строка с data.path раскрывается и показывает CSS-путь элемента.
  • navigation — заголовок «Navigation» или «Reload» для data.kind: "reload". Подзаголовок читает data.from/data.to: "{from} → {to}", если есть оба, только to, если нет from, или message, если нет ни одного.
  • fetch / xhr / http — заголовок «Fetch»/«XHR»/«HTTP» по типу (по умолчанию «HTTP» для всего остального, что сюда попало). Статус ответа (data.status) показан рядом с заголовком — красным при level "error", жёлтым при "warn" и зелёным иначе; request_loop подписан рядом как «request loop». Подзаголовок склеивает data.method и data.url, иначе message. Все прочие ключи в data, кроме method/url/status/kind/count, показываются в свёрнутом разделе «Data» как JSON — раздел появляется, только если что-то осталось.
  • Всё остальное рисуется общей строкой: заголовок — тип с заглавной буквы, подзаголовок — всегда message. Иконка выбирается по точному совпадению type с небольшим фиксированным набором (log, exception, error, nav, request, form, checkout, cart, query, search — у каждого своя иконка; всё незнакомое получает общую иконку «инфо»). Совпадение точное, а не по префиксу, так что "cart.added" получит общую иконку, хотя "cart" бы совпал. Если у крошки есть data (и это не пустой массив), он показывается свёрнутым разделом «Data» — раскройте, чтобы увидеть JSON, а не плоский список «ключ — значение», ведь данные крошки бывают любой формы.