Каждый captureException(), log.*() и завершённый спан заканчивается одинаково: событие передаётся транспорту клиента, который доставляет его по сети. По умолчанию это пакетный транспорт на глобальном fetch() Node, отправляющий в /api/envelope проекта.
По умолчанию: пакетный fetch-транспорт
События не отправляются по одному запросу на каждое. Они копятся в очереди и уходят вместе одним POST https://app.buglapse.com/api/envelope с { events: [...] } и ключом DSN в заголовке X-Buglapse-Auth: Buglapse buglapse_key={key} — при первом из условий:
- в очереди набралось
maxBatchSizeсобытий (по умолчанию20); - в очередь попало событие типа из
immediateTypes(по умолчаниюexception— ошибки уходят сразу); - прошло
flushIntervalмс с начала заполнения очереди (по умолчанию2000); - завершился корневой спан — весь трейс уходит, как только готов;
- вы вызвали
flush().
Выталкивание перед выходом
Когда процесс завершается сам, ничего не отправляется — аналога браузерного хука выгрузки страницы нет. Всё, что осталось в очереди на момент process.exit() или когда serverless-платформа замораживает функцию после возврата, теряется. Сначала сделайте await flush():
ts
import { flush } from '@buglapse/node'
// CLI-скрипт
await run()
await flush()
process.exit(0)ts
import { captureException, flush } from '@buglapse/node'
// serverless-обработчик
export async function handler(event) {
try {
return await handle(event)
} catch (error) {
captureException(error)
throw error
} finally {
await flush()
}
}При плавной остановке выталкивайте после того, как сервер перестал принимать запросы:
ts
process.on('SIGTERM', () => {
server.close(async () => {
await flush()
process.exit(0)
})
})Процесс, у которого просто закончилась работа, завершается после отправки ожидающей пачки — таймер выталкивания держит его до этого момента. Падение обрабатывает UncaughtExceptionIntegration, которая выталкивает буфер перед выходом. flush() разрешается, когда на всё пришёл ответ — на пачку в буфере, на уже отправляемую пачку, на сессии запросов, посчитанные HttpServerIntegration.
Что SDK отбрасывает сам
События, которые SDK отбрасывает до сервера, — спаны несэмплированного трейса, то, что отбросили beforeSend или интеграция, тип, который сервер попросил придержать, пачка, так и не получившая ответа, — считаются и отправляются со следующей пачкой как client report, так что Статистика и использование показывает их рядом с тем, что сервер отфильтровал или отсеял сам. flush() отправляет ожидающий отчёт, даже если нет событий, с которыми он мог бы уйти.
Повторы и сбои
Запрос, который не удался совсем (сетевая ошибка, таймаут), по умолчанию повторяется один раз. Ответ4xx/5xx не повторяется: отклонённые данные или превышенную квоту повтор не исправит.
Когда от пачки отказались, её события отбрасываются — очереди на диске нет. Подпишитесь на onError, чтобы знать, когда это происходит:
ts
import { init } from '@buglapse/node'
init({
dsn: process.env.BUGLAPSE_DSN,
transportOptions: {
timeout: 5000,
retries: 2,
onError: (error, event) => {
process.stderr.write(`Buglapse dropped a ${event.type} event: ${error.message}\n`)
},
},
})onError вызывается один раз на каждое отброшенное событие, после всех повторов. Не сообщайте из него через Buglapse — бэкенд, который только что не ответил, не примет и этот отчёт, поэтому SDK отбрасывает такие повторные события, а не зацикливается.
403 с Origin not allowed for this project означает, что у проекта задан список Разрешённые домены в Настройки → Безопасность и приватность: запросы с сервера не несут заголовок Origin и отклоняются. Очистите список или заведите для сервера отдельный проект.
Ограничения частоты
Когда бэкенд придерживает тип — поток сверх аварийного лимита или исчерпанный месячный пул (см. События → Динамическое сэмплирование), — он отвечает заголовком X-Buglapse-Rate-Limits или 429 с Retry-After. Транспорт тогда отбрасывает события затронутых типов ещё до очереди, пока не истечёт время. Настраивать нечего; включите debug, чтобы видеть, когда это происходит.
Динамическое сэмплирование
Пока бэкенд сэмплирует тип (События → Динамическое сэмплирование), его ответы несут частоту и самые горячие группы этого типа. Транспорт тогда сам сэмплирует события этих групп — по трейсу, сессии или реплею, так же, как бэкенд, — вместо того чтобы отправлять то, что там всё равно отбросят. События любых других групп, логи ошибок и спаны вне транзакции отправляются как обычно, так что новая ошибка всегда доходит до бэкенда. Настраивать нечего; включите debug, чтобы видеть отброшенные события.
Опции transportOptions
Настраивают транспорт по умолчанию; игнорируются, если передан свой transport.
| Опция | По умолчанию | Описание |
|---|---|---|
timeout | 5000 | мс до прерывания отправляемого запроса |
retries | 1 | Дополнительные попытки после сетевого сбоя (не после ответа 4xx/5xx) |
maxBatchSize | 20 | Событий в очереди до автоматической отправки |
flushInterval | 2000 | мс после первого события в очереди до автоматической отправки |
immediateTypes | ['exception'] | Типы событий, которые сразу выталкивают очередь |
onError | — | (error, event) => void, вызывается на каждое отброшенное событие |
Замена транспорта
makeConsoleTransport печатает каждое событие в консоль вместо отправки — удобно для локальной разработки без бэкенда:
ts
import { init, makeConsoleTransport } from '@buglapse/node'
init({
transport: makeConsoleTransport,
})transport принимает саму фабрику — makeConsoleTransport, а не makeConsoleTransport(). Со своим транспортом dsn больше не обязателен.
Свой транспорт
Транспорт — объект такой формы:
ts
interface Transport {
send(event: BuglapseEvent): void | Promise<void>
flush?(options?: { report?: boolean }): void | Promise<void>
recordDiscard?(reason: DiscardReason, type: BuglapseEvent['type'], quantity?: number): void
readonly endpoint?: string
}send(event)— вызывается один раз на событие.flush()— необязательно; реализуйте, еслиsend()что-то буферизует, чтобыflush()и выталкивание в конце трейса было что отправлять.recordDiscard()— необязательно. Клиент вызывает его для каждого события, отброшенного доsend()(beforeSend, интеграция, несэмплированный трейс); транспорт по умолчанию превращает счётчики вclient_reportпачки. Без него такие отбрасывания просто не попадают в отчёт.endpoint— необязательно. HTTP-интеграции (хлебные крошки,httpTracingIntegration,httpErrorsIntegration) следят за каждым исходящим запросом; запрос на этот адрес распознаётся как собственный запрос SDK и пропускается. Если ваш транспорт отправляет по HTTP, задайте его — иначе каждая доставка будет появляться отдельной крошкой, спаном или логом.
transport принимает фабрику, (options) => Transport. В options — dsn и debug верхнего уровня плюс ваши transportOptions. Этот транспорт пишет события в локальный файл NDJSON — например, чтобы их забирал сборщик логов:
ts
import { appendFileSync } from 'node:fs'
import { init, type Transport } from '@buglapse/node'
function makeFileTransport(): Transport {
return {
send(event) {
appendFileSync('/var/log/buglapse-events.ndjson', JSON.stringify(event) + '\n')
},
}
}
init({ transport: makeFileTransport })