Skip to main content
@kubit-ai/otel sends OpenTelemetry spans from a Node or TypeScript app to Kubit. It has two entry points: configure() when Kubit is your only OpenTelemetry setup, and KubitSpanProcessor when your app already creates a tracer provider or a NodeSDK. This page covers both, the span filter, and the settings to check before production.

Before you start

  • Node.js 18 or later
  • OpenTelemetry JS SDK v2. Version 1 isn’t supported.
  • A Kubit API key in KUBIT_API_KEY
Kubit authenticates spans with an ingestion API key, read from the KUBIT_API_KEY environment variable. Each key belongs to one workspace. The app shows a new key only once: This is the only time we'll show you this key. Copy it now. Copy it, or click Download for a .env file, then click I’ve Saved It. The downloaded file names the key KUBIT_OTEL_API_KEY, so rename it to KUBIT_API_KEY. Availability: API Keys shows only to the Owner and Admin roles, and only in Agent and User workspaces.
Keep the key out of source control. Put it in .env or your secrets manager, and check that .env is in .gitignore.

Prompt for your coding agent

Install

Install the package and its OpenTelemetry peer dependencies:

Choose configure() or KubitSpanProcessor

@kubit-ai/otel has no attach(), unlike the Python SDK. OpenTelemetry JS SDK v2 takes span processors only when the provider is created, so there’s no way to add one to a provider that’s already running.
Don’t call configure() if your app already sets up a tracer provider or NodeSDK. configure() registers a second global provider and replaces the first, so your existing traces stop reaching their current destination.

You have no OpenTelemetry setup

Create kubit-instrumentation.ts and import it first in your entrypoint, before any LLM client is created:
kubit-instrumentation.ts
index.ts
Change serviceName, serviceVersion and deployment.environment to match your app. Kubit uses them to tell agents and releases apart.

configure() options

configure() returns the tracer provider it registered.

You already have a tracer provider

Pass KubitSpanProcessor in spanProcessors next to your other processors, when you create the provider.

NodeTracerProvider

tracing.ts

NodeSDK

instrumentation.ts

KubitSpanProcessor options

KubitSpanProcessor is an OpenTelemetry BatchSpanProcessor that drops non-LLM spans before they’re queued. For full control, KubitExporter is the plain exporter without filtering, masking or batching. Wrap it in your own processor.

Span filtering

By default, KubitSpanProcessor sends only LLM-related spans to Kubit. HTTP, database and framework spans from other auto-instrumentation stay out of your workspace. A span is sent when any of these is true: KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES exports the full prefix list. The default filter drops these spans even when they match a rule above:

Keep spans from your own framework

tracing.ts

Send every span

tracing.ts
If shouldExportSpan throws, the span is dropped. The workspace applies its own filter after the SDK’s. A new workspace starts with the same keep and drop rules, so spans you keep with shouldExportSpan can still be dropped there. Change it in Data > Collection > Filtering. Sampling and masking rules are on the Sampling and Masking tabs there.

Trace LLM and tool calls

Kubit receives whatever spans your app produces. Spans from an LLM instrumentation library pass the default filter. With the Vercel AI SDK, turn on telemetry per call:
chat.ts
For tool calls, wrap each tool in its own span so it shows up as a step in the execution path:
tools.ts

Add user and session IDs

Kubit groups traces into sessions and joins them to users from IDs on your spans. Set user.id and session.id on the root span of each request. The gen_ai.operation.name attribute keeps your own span past the default filter, which drops spans with no gen_ai.* attribute:
handler.ts
Use the same user ID your product analytics events use, so agent runs join to user behavior. The default extraction mapping reads enduser.id or user.id for the user, and session.id or gen_ai.conversation.id for the session. To read other keys, edit the mapping as described in Check the extraction mapping.

Mask sensitive content

The mask option redacts span content before the span leaves your process. KubitSpanProcessor runs your function on each span that passes the filter, before the span is queued for export. mask needs @kubit-ai/otel 0.7.3 or later and works with configure() and KubitSpanProcessor.
kubit-instrumentation.ts
This mask replaces card numbers in gen_ai.prompt and drops gen_ai.user.message events, where newer GenAI instrumentors record the prompt. The helpers in @kubit-ai/otel/mask are the supported way to change a span inside mask: Constraint: When mask throws or returns null or undefined, the SDK sends a tombstone in place of the span. The tombstone keeps the trace and span IDs, parent, timing and kind. Its attributes and events are removed, its status is ERROR with the message kubit-otel mask failed, and its kubit.sdk.mask_error attribute holds the error’s name or returned_null. Unmasked data is never sent. The SDK logs one of these lines:
Output

Before production

@kubit-ai/otel doesn’t read the standard OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_TRACES_ENDPOINT variables. They apply to the whole process and would redirect Kubit’s traces if another OpenTelemetry SDK set them. Use KUBIT_OTEL_ENDPOINT instead.

Verify

Send a test span with debug logging on:
send-test-span.ts
The output includes these lines, among other debug lines:
Output
endpoint=<redacted> is what @kubit-ai/otel prints when you don’t pass endpoint. The SDK still sends to KUBIT_OTEL_ENDPOINT or https://otel.kubit.ai/v1/traces. Exported batch to kubit means the collector accepted the batch. No Dropped span due to shouldExportSpan filter line for chat.completion means the span passed the filter. Then open Agent > Activity, set Time Range to Last 1 day and click Execute. The chat.completion trace is in the list. Validate in Activity shows what to check in the trace.

If it fails

Next steps