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

Теги и контекст ​

И то, и другое едет с событиями, но задачи у них разные:

  • Теги (setTag(key, value)) — плоские пары key: string; числа и булевы приводятся к строкам. По ним в первую очередь ищет поиск панели в фильтре key:value, например tenant:acme в Исключениях/Трейсах/Логах (см. Поиск).
  • Контекст (setContext(key, object)) прикрепляет вложенный объект под пространством имён — setContext('order', { id, total }) становится order.id / order.total в событии. Он для описательных данных на странице события; поиск обращается к нему, если тега с тем же ключом нет.

Глобально и на запрос ​

Это единственное, что работает иначе, чем в браузере. Сервер обрабатывает много запросов сразу на одном общем клиенте, поэтому уровней два:

Чем задаётсяНа что действует
ГлобальноsetTag(), setContext(), setUser()На каждое событие всего процесса, с этого момента
ОбластьwithScope((scope) => ...) → scope.setTag(), scope.setContext(), scope.setUser()Только на события, пойманные внутри этого колбэка, — через все await в нём

Глобальные функции — для того, что одинаково для всего процесса: имя сервиса, регион, очередь, которую читает воркер:

ts
import { setTag } from '@buglapse/node'

setTag('service', 'billing-api')
setTag('region', process.env.REGION)

Не вызывайте setUser() или setTag() с данными запроса внутри обработчика. Это меняет глобальное значение, так что параллельный запрос — и каждый следующий — попадёт с чужим пользователем. Данным запроса место в области.

Область запроса через withScope() ​

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

ts
import { captureException, withScope } from '@buglapse/node'

await withScope(async (scope) => {
    scope.setTag('tenant', tenant.slug)
    scope.setContext('order', { id: order.id, total: order.total })

    try {
        await chargeCard(order)
    } catch (error) {
        captureException(error) // несёт tenant и order.*
    }
})

Область накладывается на глобальные значения; ключ, заданный и там, и там, берёт значение области. Всё, что поймано после возврата из withScope(), наложения не видит.

Во веб-фреймворке ​

Middleware, которое оборачивает остаток цепочки в withScope(), даёт каждому запросу свою область. Вызывайте next() внутри колбэка, чтобы обработчики маршрутов выполнялись в ней:

ts
import { withScope } from '@buglapse/node'

app.use((request, response, next) => {
    withScope((scope) => {
        if (request.user) {
            scope.setUser({ id: request.user.id, email: request.user.email })
        }
        scope.setTag('tenant', request.tenant.slug)

        next()
    })
})

scope.setUser() — версия setUser() для запроса: пишет те же ключи user.*, по которым панель привязывает события к пользователю, но только для событий внутри области.

HttpServerIntegration уже выполняет каждый запрос в своей области (с заданными request.url, request.method и request.referrer), со своим следом крошек и трейсом. withScope() вашего middleware вкладывается в неё и дополняет её.

Задачи и скрипты ​

Всему, что начинается не с HTTP-запроса, — задаче очереди, cron — нужна та же обёртка, чтобы получить свою область: withScope() или asyncSpan(), если нужен ещё и трейс. Без неё события делят общую корневую область процесса.

Пользователи ​

setUser() — сокращение для setContext('user', ...). Все поля необязательны; повторный вызов дополняет уже заданное:

ts
import { setUser } from '@buglapse/node'

setUser({ id: '42', email: 'jane@example.com', username: 'jane' })

На сервере это правильно, только если весь процесс работает за одного пользователя — CLI-утилита, бэкенд десктопного приложения. В многопользовательском сервере вызывайте scope.setUser() в области запроса (см. выше).

Панель привязывает события к пользователю по user.id, затем user.email, затем IP. Без user.* она видит IP вашего сервера, так что все события попадают на одного «пользователя» — задавайте пользователя явно, чтобы число пользователей имело смысл.

environment и release ​

Они задаются один раз, в init(), — а не через setTag()/setContext(), — потому что панель обращается с ними особо: по environment работают фильтр environment: в поиске и условия в выражении фильтра, а с release сопоставляются source maps. См. Настройка.

Автоматический контекст ​

  • NodeContextIntegration добавляет runtime.*, os.* и host.* — версию Node, операционную систему и имя машины — в каждое событие.
  • HttpServerIntegration добавляет request.url, request.method и request.referrer в каждое событие, пойманное при обработке запроса.
  • Пока спан активен, каждое событие получает trace.id и span.id — см. Трейсинг.

Контекст в панели ​

Страница события группирует развёрнутые ключи обратно по префиксу (order.* под «Order» и т.д.). У trace.* и user.* свой интерфейс (ссылка на трейс, карточка пользователя), и в этом списке они не повторяются. Нажатие на любую строку ищет именно эту пару key:value:

order.currency:"USD"