Обзор
Хлебная крошка — небольшая запись с временем о том, что произошло перед ошибкой: лог консоли, клик, сетевой запрос, переход. Они хранятся на клиенте в скользящем следе в памяти и прикрепляются к следующему пойманному исключению, так что вы видите последовательность событий, приведших к сбою, а не только сам сбой.
Хлебные крошки путешествуют только с исключениями. Логи, трейсы и спаны следа не несут — у вызова лога нет того, что «ведёт к нему», как у падения.
Добавление вручную
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, а не плоский список «ключ — значение», ведь данные крошки бывают любой формы.