Теги и контекст
И то, и другое едет с событиями, но задачи у них разные:
- Теги (
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"