Crux
API ReferencePlugins

@use-crux/otel

OpenTelemetry integration, spans for every instrumented Crux event.

import { withTelemetry } from '@use-crux/otel'
import { createUrlExporter, createCallbackExporter } from '@use-crux/otel'

For setup and usage, see the Production Telemetry guide.

withTelemetry(options?)

Creates a CruxPlugin that instruments all Crux operations with OpenTelemetry spans or lightweight structured traces.

config({
  plugins: [withTelemetry({ serviceName: 'my-app' })],
})

Options:

FieldTypeDefaultDescription
serviceNamestring'@use-crux/otel'Tracer name for span identification
attributesRecord<string, string>n/aCustom attributes added to every span
exporterUrlExporter | CallbackExportern/aExport strategy. Omit for standard OTel TracerProvider path
captureMessageContentbooleanfalseExport GenAI input/output message attributes
semconvVersion'genai-dev-2026-06'current tableForward compatibility knob for semconv names
baggageAttributeAllowlistreadonly string[]unsetBaggage keys copied onto resumed root spans as crux.baggage.*

Payload capture is controlled by the core observability policy: config({ observability: { recordInputs, recordOutputs, redactRecord } }). recordInputs and recordOutputs accept inline, reference, or off. The OTel mapper also drops known payload attribute keys by default, even when local graph capture is inline.

Returns: CruxPlugin with name 'crux:otel'.

withTelemetry() has a process-level double-install guard. A second active install warns once and becomes a no-op, preventing duplicate exported span trees.

If the standard OTel path is used without a registered TracerProvider, Crux detects the invalid OTel span context, warns once, and falls back to lightweight span tracking. Register a provider before installing the plugin or pass exporter.

Open span registries are bounded. Registry-pressure evictions force-end spans with crux.expired: true and UNSET status.

Crux emits embedding spans as embeddings {model} with attributes for embedding name, kind, input count, chunk count, dense dimensions, token usage, and cost when available.

Generation and stream spans project Crux graph metrics into OTel GenAI and Crux attributes: gen_ai.client.operation.duration, gen_ai.server.time_to_first_token, crux.gen.output_tokens_per_second, and crux.gen.time_per_output_chunk_ms.

Corpus sync emits crux.corpus.sync spans and per-source crux.corpus.source spans. Namespace and source identifiers are exported as hashes so file paths, URLs, and tenant namespaces do not become raw telemetry attributes.

Ingest parsers emit crux.ingest.parse spans. These include parser name, source format, byte length, part count, warning count, and hashed namespace/source identifiers. Parser errors mark the span as ERROR.

Retrieval emits direct crux.retrieval.query spans for retriever calls. Retrieval recipes emit a parent crux.retrieval.recipe span and one crux.retrieval.step span per recipe step. Step spans include recipe id, step id, step kind, input/output query counts, input/output hit counts, warning count, and status. They intentionally do not include raw query text, hit content, metadata, filters, or embeddings.

Quality evaluations export crux.eval.run and crux.eval.case spans. Baseline comparison, case membership, and replay provenance are canonical graph edges for local/devtools read models; OTel export keeps those relationships as aggregate span metadata rather than raw case payloads.

UrlExporter

Configuration for HTTP POST export (ephemeral runtimes).

FieldTypeDescription
urlstringEndpoint to POST span batches to
headersRecord<string, string>?Optional headers (API keys, auth)

CallbackExporter

type CallbackExporter = (
  spans: ReadonlyArray<TraceSpan>,
) => void | Promise<void>

A function that receives completed span batches. Use for custom handling (Convex actions, PostHog, etc.).

TraceSpan

Structured span data used by the lightweight exporter path.

FieldTypeDescription
spanIdstringW3C span ID for lightweight exports; provider-issued ID on the standard OTel path
parentSpanIdstring?Parent span ID for nesting
traceIdstringW3C trace ID grouping related spans
namestringSpan name (e.g., 'chat gpt-4o')
startTimenumberStart time (Unix ms)
endTimenumberEnd time (Unix ms)
durationMsnumberDuration in milliseconds
attributesRecord<string, string | number | boolean | Array<string | number | boolean>>Key-value attributes
statusSpanStatus{ code: 'OK' | 'ERROR' | 'UNSET', message?: string }
eventsArray<{ name, time, attributes? }>?Point-in-time events (e.g., exceptions)

Propagation helpers

ExportPurpose
injectCruxPropagationCarrier(carrier, target)Write W3C traceparent / tracestate / baggage (+ optional OTel propagator fields) into a header-like carrier with .set
extractCruxPropagationCarrier(source, options?)Read a validated carrier + allowlisted baggage attributes from a header-like carrier with .get; never throws

See the telemetry guide for suspend/resume and queue examples.

createUrlExporter(options)

Creates a SpanExporter that POSTs span batches to a URL. Fire-and-forget with 5-second timeout, failures are silently ignored.

const exporter = createUrlExporter({
  url: 'https://collector.example.com/v1/traces',
  headers: { 'X-Api-Key': 'key' },
})

createCallbackExporter(callback)

Creates a SpanExporter that delivers span batches to a callback function.

const exporter = createCallbackExporter((spans) => {
  for (const span of spans) {
    console.log(`${span.name}: ${span.durationMs}ms`)
  }
})

SpanExporter

Interface for span export backends.

MethodDescription
export(spans)Export a batch of completed spans
shutdown()Flush pending spans and shut down

Span attribute constants

GenAI semantic convention constants are exported from @use-crux/otel:

ConstantValueConvention
SEMCONV_VERSIONgenai-dev-2026-06Crux
GEN_AI_OPERATION_NAMEgen_ai.operation.nameOTel GenAI
GEN_AI_PROVIDER_NAMEgen_ai.provider.nameOTel GenAI
GEN_AI_REQUEST_MODELgen_ai.request.modelOTel GenAI
GEN_AI_RESPONSE_MODELgen_ai.response.modelOTel GenAI
GEN_AI_CLIENT_OPERATION_DURATIONgen_ai.client.operation.durationOTel GenAI
GEN_AI_SERVER_TIME_TO_FIRST_TOKENgen_ai.server.time_to_first_tokenOTel GenAI
CRUX_GEN_OUTPUT_TOKENS_PER_SECONDcrux.gen.output_tokens_per_secondCrux
CRUX_GEN_TIME_PER_OUTPUT_CHUNK_MScrux.gen.time_per_output_chunk_msCrux
GEN_AI_RESPONSE_FINISH_REASONSgen_ai.response.finish_reasonsOTel GenAI
GEN_AI_INPUT_MESSAGESgen_ai.input.messagesOTel GenAI
GEN_AI_OUTPUT_MESSAGESgen_ai.output.messagesOTel GenAI
GEN_AI_SYSTEM_INSTRUCTIONSgen_ai.system_instructionsOTel GenAI
GEN_AI_USAGE_INPUT_TOKENSgen_ai.usage.input_tokensOTel GenAI
GEN_AI_USAGE_OUTPUT_TOKENSgen_ai.usage.output_tokensOTel GenAI
CRUX_PROMPT_IDcrux.prompt.idCrux
CRUX_COSTcrux.costCrux
CRUX_EMBEDDING_NAMEcrux.embedding.nameCrux
CRUX_EMBEDDING_KINDcrux.embedding.kindCrux
CRUX_EMBEDDING_OPERATIONcrux.embedding.operationCrux
CRUX_CORPUS_IDcrux.corpus.idCrux
CRUX_CORPUS_NAMESPACE_HASHcrux.corpus.namespace_hashCrux
CRUX_CORPUS_SOURCE_ID_HASHcrux.corpus.source_id_hashCrux
CRUX_CORPUS_ACTIONcrux.corpus.actionCrux
CRUX_INGEST_PARSERcrux.ingest.parserCrux
CRUX_INGEST_FORMATcrux.ingest.formatCrux
CRUX_INGEST_SOURCE_ID_HASHcrux.ingest.source_id_hashCrux
CRUX_INGEST_PART_COUNTcrux.ingest.part_countCrux
CRUX_RETRIEVAL_RECIPE_IDcrux.retrieval.recipe.idCrux
CRUX_RETRIEVAL_STEP_IDcrux.retrieval.step.idCrux
CRUX_RETRIEVAL_STEP_KINDcrux.retrieval.step.kindCrux
CRUX_RETRIEVAL_STEP_INPUT_QUERY_COUNTcrux.retrieval.step.input_query_countCrux
CRUX_RETRIEVAL_STEP_OUTPUT_QUERY_COUNTcrux.retrieval.step.output_query_countCrux
CRUX_RETRIEVAL_STEP_INPUT_HIT_COUNTcrux.retrieval.step.input_hit_countCrux
CRUX_RETRIEVAL_STEP_OUTPUT_HIT_COUNTcrux.retrieval.step.output_hit_countCrux
CRUX_RETRIEVAL_STEP_WARNING_COUNTcrux.retrieval.step.warning_countCrux
CRUX_TOOL_NAMEcrux.tool.nameCrux
CRUX_FLOW_IDcrux.flow.idCrux
CRUX_FLOW_NAMEcrux.flow.nameCrux
CRUX_FLOW_PARENT_IDcrux.flow.parent_idCrux
CRUX_COMPOSITION_KINDcrux.composition.kindCrux

On this page