OpenTelemetry

С помощью OpenTelemetry можно собирать трассировки и отправлять их в Tracer. В SDK для этого есть три класса: TracerSpanProcessor добавляет атрибуты Tracer, TracerUploadSpanExporter отправляет спаны, а TracerContextManager помогает сохранить связь ошибок с трассировкой. Интеграция доступна в браузере и Node.js. Зависимости OpenTelemetry нужно установить отдельно.

При переходе с отдельного пакета см. руководство по миграции OpenTelemetry.

Подключение

Примеры используют OpenTelemetry SDK 2.x. Инициализируйте провайдер один раз при запуске приложения.

npm install @apptracer/sdk @opentelemetry/api@1.9.0 @opentelemetry/sdk-trace-web@2.5.0
import { WebTracerProvider, BatchSpanProcessor, StackContextManager } from '@opentelemetry/sdk-trace-web'; import { TracerSpanProcessor, TracerUploadSpanExporter, TracerContextManager, initTracerErrorAsyncStack, } from '@apptracer/sdk'; const provider = new WebTracerProvider({ spanProcessors: [ new TracerSpanProcessor(), new BatchSpanProcessor(new TracerUploadSpanExporter({ appToken: 'appToken', // appToken из настроек проекта в Tracer })), ], }); initTracerErrorAsyncStack(); provider.register({ contextManager: new TracerContextManager(new StackContextManager()), });

Создайте и завершите спан через настроенный выше provider. Полученный tracer используется в следующих примерах:

const tracer = provider.getTracer('checkout'); const span = tracer.startSpan('submit-order'); span.end();

Передача traceparent

Чтобы передать контекст спана в HTTP-запросе, используйте getTracerTraceparent(spanContext: SpanContext): string | undefined. Функция доступна во всех сборках SDK и работает без инициализации Tracer.

import { getTracerTraceparent } from '@apptracer/sdk'; const span = tracer.startSpan('load-order'); try { const traceparent = getTracerTraceparent(span.spanContext()); const headers: Record<string, string> = {}; if (traceparent !== undefined) headers.traceparent = traceparent; await fetch('/api/orders/123', { headers }); } finally { span.end(); }

Передайте контекст нужного спана через span.spanContext(). При некорректных идентификаторах или флагах функция возвращает undefined.

Для продолжения трейса принимающая сторона должна извлечь traceparent и использовать полученный контекст как родительский. Если требуется также передавать tracestate или baggage, используйте propagation.inject(...) из OpenTelemetry с настроенными propagators.

Контекст и связь ошибок

startSpan() создаёт спан, но не делает его активным. Чтобы связать выполняемый код с этим спаном, используйте startActiveSpan() или context.with(). Если внутри уже активного контекста открыть новый, после выхода из него восстановится предыдущий.

Для отправки ошибок подключите модуль ошибок:

import { initTracerError, initTracerErrorUploader } from '@apptracer/sdk'; const tracerError = initTracerError(); initTracerErrorUploader({ appToken: 'appToken', // appToken из настроек проекта в Tracer versionName: '1.0.0', // версия вашего приложения });

При вызове tracerError.error(error) SDK добавляет traceId / spanId из сохранённого контекста ошибки или текущего активного спана. Без контекста ошибка отправляется без привязки. UUID сессии Tracer и traceId — отдельные идентификаторы.

Для переноса контекста через обработчики Promise, таймеры и addEventListener подключите TracerContextManager вместе с модулем асинхронных стеков. Их можно подключить в любом порядке: контекст будет сохраняться у обработчиков, зарегистрированных после подключения обоих модулей.

Другой менеджер контекста сам по себе не включает этот механизм SDK. Если вы используете AbortSignal в среде без WeakRef, привяжите обработчик к контексту через context.bind(...).

После нативного await браузер может потерять активный контекст. Чтобы связать ошибку с нужной операцией, сохраните её контекст и передайте его в context.with() при регистрации ошибки:

import { context, trace, SpanStatusCode } from '@opentelemetry/api'; async function loadOrder(orderId: string) { const span = tracer.startSpan('load-order'); const requestContext = trace.setSpan(context.active(), span); try { const response = await fetch('/api/orders/' + encodeURIComponent(orderId)); if (!response.ok) throw new Error('HTTP ' + response.status); return await response.json(); } catch (error) { span.setStatus({ code: SpanStatusCode.ERROR }); context.with(requestContext, function () { tracerError.error(error); }); throw error; } finally { span.end(); } }

Завершение отправки

Завершите спаны перед await provider.forceFlush(): провайдер отправит накопленную очередь и дождётся текущих запросов. При остановке приложения вызовите await provider.shutdown().

Процессор

TracerSpanProcessor фиксирует атрибуты при старте спана:

  • apptracer.sessionUuid — текущая сессия Tracer;
  • user.id — текущий пользователь выбранного экземпляра SDK.
import { TracerSpanProcessor } from '@apptracer/sdk'; const processor = new TracerSpanProcessor();

Процессор не хранит спаны и сам их не отправляет. После shutdown() он перестаёт добавлять атрибуты.

Экспортёр

new TracerUploadSpanExporter({ appToken, apiHost? }) принимает обязательный токен и необязательный API-хост, отправляет спаны через /api/otel/trace. Параметры versionName, versionCode, environment и Node.js tlsAgent из конфигурации обычных uploader-ов сюда не передаются.

Сбор и пакетирование настраиваются в OpenTelemetry, например через BatchSpanProcessor.

  • export() вызывает обработчик результата после завершения отправки.
  • forceFlush() дожидается запросов, начатых до его вызова, включая повторные отправки.
  • shutdown() прекращает приём новых спанов на отправку и дожидается текущих запросов.

Если ожидаемый запрос завершится окончательной ошибкой, Promise от forceFlush() или shutdown() будет отклонён.

Число повторных отправок не ограничено. Пока сервер недоступен и SDK повторяет запросы, ожидание продолжается. Таймаут OpenTelemetry не отменяет запросы Tracer.

disableTracerModules() не останавливает отдельно подключённый экспортёр. Для его завершения используйте методы OpenTelemetry.

SDK не включает полифилы. Если в вашей среде нет Promise или других возможностей, необходимых выбранной версии OpenTelemetry, добавьте их в приложение.