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

Каждый 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.

ОпцияПо умолчаниюОписание
timeout5000мс до прерывания отправляемого запроса
retries1Дополнительные попытки после сетевого сбоя (не после ответа 4xx/5xx)
maxBatchSize20Событий в очереди до автоматической отправки
flushInterval2000мс после первого события в очереди до автоматической отправки
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 })