@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_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.
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.
You have no OpenTelemetry setup
Createkubit-instrumentation.ts and import it first in your entrypoint, before any LLM client is created:
kubit-instrumentation.ts
index.ts
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
PassKubitSpanProcessor 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
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
tools.ts
Add user and session IDs
Kubit groups traces into sessions and joins them to users from IDs on your spans. Setuser.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
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
Themask 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
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
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
- Quickstart: check the extraction mapping and validate in Activity.
- Agent plugin: keep Langfuse or Braintrust as your sink and add Kubit next to it.
- Is my data flowing?: step-by-step checks.