ReqLLM.OpenTelemetry (ReqLLM v1.12.0)

View Source

Bridges ReqLLM request lifecycle telemetry into OpenTelemetry GenAI client spans.

This module listens to the existing [:req_llm, :request, *] events and emits a single client span per model call. The span follows the OpenTelemetry Generative AI semantic conventions where ReqLLM has normalized data available, including:

  • gen_ai.provider.name
  • gen_ai.operation.name
  • gen_ai.request.model
  • gen_ai.output.type
  • gen_ai.response.finish_reasons
  • gen_ai.usage.*
  • error.type

Span export remains opt-in at the application level. You still need OpenTelemetry dependencies and SDK/exporter configuration in your host app. When the OpenTelemetry API modules are not available, attach/2 returns {:error, :opentelemetry_unavailable}.

For custom tracer integrations that want richer message and tool-call mapping without binding ReqLLM to a specific OpenTelemetry SDK, see ReqLLM.Telemetry.OpenTelemetry.

Summary

Functions

Attaches the OpenTelemetry bridge to ReqLLM request lifecycle events.

Returns whether the configured OpenTelemetry adapter is available.

Detaches the OpenTelemetry bridge and clears any in-flight spans for the handler.

Returns the request lifecycle events used by the bridge.

Removes in-flight span entries older than ttl_ms for handler_id.

Returns the GenAI span name for a ReqLLM request.

Types

attach_opt()

@type attach_opt() ::
  {:adapter, module()}
  | {:handler_id, term()}
  | {:content, content_mode() | boolean()}
  | {:langfuse, boolean()}
  | {atom(), term()}

content_mode()

@type content_mode() :: :none | :attributes | :event

Functions

attach(handler_id \\ "req-llm-open-telemetry", opts \\ [])

@spec attach(
  term(),
  keyword()
) :: :ok | {:error, :already_exists | :opentelemetry_unavailable}

Attaches the OpenTelemetry bridge to ReqLLM request lifecycle events.

Options:

  • :adapter — alternate adapter module (defaults to ReqLLM.OpenTelemetry.OTelAdapter).
  • :content — content capture mode. :none (default) emits no message payloads; :attributes promotes gen_ai.input.messages, gen_ai.system_instructions, gen_ai.tool.definitions, and gen_ai.output.messages onto the span; :event emits the same payload as a single gen_ai.client.inference.operation.details span event on the terminal lifecycle event. true is accepted as an alias for :attributes.
  • :langfuse — when true, also adds langfuse.observation.cost_details (JSON-encoded breakdown) when ReqLLM has computed a cost.

Content capture additionally requires telemetry: [payloads: :raw] on the call so the request/response payloads are available to map.

In-flight spans are tracked in a named ETS table keyed by handler id and request id. If a :start event is observed but no :stop/:exception follows (e.g. the calling process crashed before emission), the entry stays in the table until detach/1 runs. Long-running hosts can call prune_stale_spans/2 periodically to clear out entries older than a TTL.

available?(opts \\ [])

@spec available?(keyword()) :: boolean()

Returns whether the configured OpenTelemetry adapter is available.

detach(handler_id \\ "req-llm-open-telemetry")

@spec detach(term()) :: :ok

Detaches the OpenTelemetry bridge and clears any in-flight spans for the handler.

events()

@spec events() :: [[atom()]]

Returns the request lifecycle events used by the bridge.

prune_stale_spans(handler_id \\ "req-llm-open-telemetry", ttl_ms)

@spec prune_stale_spans(term(), non_neg_integer()) :: non_neg_integer()

Removes in-flight span entries older than ttl_ms for handler_id.

Returns the number of entries pruned. Use this from a host-side scheduler (e.g. an :erlang.send_after/3 loop or a periodic GenServer tick) to contain the ETS table when requests start without a matching stop or exception event.

span_name(metadata)

@spec span_name(map()) :: String.t()

Returns the GenAI span name for a ReqLLM request.