Skip to main content
kubit-otel sends OpenTelemetry spans from a Python app to Kubit. It has two entry points: configure() when Kubit is your only exporter, and attach() when your app already creates a TracerProvider. This page covers both, plus the settings to check before production.

Before you start

  • Python 3.9 or later
  • OpenTelemetry Python SDK 1.20 or later. kubit-otel installs opentelemetry-sdk>=1.20.0 and opentelemetry-exporter-otlp-proto-http>=1.20.0,<2 if they’re missing.
  • 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

Choose configure() or attach()

If your app sets its own global TracerProvider, call configure() only after trace.set_tracer_provider(), or use attach(). Called after, configure() adds KubitSpanProcessor to your provider and merges its resource attributes into the provider’s resource. Kubit’s values win on a key collision, so pass service_name, or your provider’s service.name becomes default. Called before, configure() registers its own provider first. OpenTelemetry then ignores your app’s set_tracer_provider() call, and your existing exporters stop receiving spans.

You have no OpenTelemetry setup

Create kubit_instrumentation.py and import it first in your entrypoint.
kubit_instrumentation.py
main.py

configure() parameters

configure() has no batch or sampling parameters. It sends spans through KubitSpanProcessor with its default batch settings: a queue of 2048 spans, a batch every 5000 ms, up to 512 spans per export and a 30000 ms export timeout.

You already have a TracerProvider

Call attach() after trace.set_tracer_provider(), in the module that creates the provider:
tracing.py
attach() finds the global provider and adds KubitSpanProcessor to it. Your provider’s resource (service.name, service.version) applies to the spans Kubit receives.

attach() parameters

Availability: attach() must run after trace.set_tracer_provider(). Called earlier, it raises RuntimeError and adds nothing to any provider.

Trace LLM and tool calls

KubitSpanProcessor sends only LLM spans by default: spans from the kubit-sdk tracer, spans with any gen_ai.* attribute and spans from a known LLM instrumentation scope. To get one span per LLM call, add the OpenTelemetry GenAI instrumentor for your client after configure() or attach():
kubit_instrumentation.py
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true puts prompt and response text on the spans. Polaris labels spans from that text. For tool calls, wrap each tool in its own span so it shows up as a step in the execution path:
tools.py

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.py
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-otel 0.7.3 or later and works with configure(), attach() and KubitSpanProcessor.
kubit_instrumentation.py
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_otel.mask are the supported way to change a span inside mask: Constraint: When mask raises or returns None, 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 description kubit-otel mask failed, and its kubit.sdk.mask_error attribute holds the exception class name or returned_none. Unmasked data is never sent. The SDK logs one of these lines, with the traceback for a raised exception:
Output
Constraint: A KubitExporter that you wrap in your own span processor applies neither mask nor the default filter. Apply both in your processor.

Before production

Verify

Send a test span with debug logging on:
force_flush() sends the span now. Without it, the span waits up to 5000 ms in the batch. With the configure() file from You have no OpenTelemetry setup, the output on stderr includes these lines, among other debug lines:
Output
endpoint=<from OTEL_EXPORTER_OTLP_*> is the label kubit-otel prints when you don’t pass endpoint. The SDK still sends to KUBIT_OTEL_ENDPOINT or https://otel.kubit.ai/v1/traces. With attach(), the first line is [kubit-otel info] kubit_otel attached endpoint=<from OTEL_EXPORTER_OTLP_*>. KUBIT_OTEL_LOG_LEVEL takes debug, info, warn or error, and the default is info. Then open Agent > Activity, set Time Range to Last 1 day and click Execute. The hello-kubit trace is in the list. Validate in Activity shows what to check in the trace.

If it fails

Next steps