OpenTelemetry Tracing

Distributed tracing for your agents. ask-opentelemetry subscribes to ask-instrumentation events and wraps every LLM operation in an OpenTelemetry span. Export to Langfuse, Datadog, Honeycomb, Jaeger, Arize Phoenix, or any OpenTelemetry-compatible backend.

gem "ask-opentelemetry"

Quick Start

require "ask/open_telemetry"

Ask::OpenTelemetry.install

That’s the whole setup. In a Rails app the railtie installs it automatically on boot — no manual call needed. Safe to call repeatedly; subsequent calls are no-ops.

From here, every chat completion, tool call, embedding, and image generation gets a span.

How Events Map to Spans

Instrumentation Event Span Name Attributes
chat.ask / chat.stream.ask llm.chat llm.provider, llm.model, llm.input_tokens, llm.output_tokens, llm.duration_ms
tool.ask llm.tool llm.tool, llm.tool_args, llm.duration_ms
embedding.ask llm.embedding llm.provider, llm.model, llm.duration_ms
image.ask llm.image llm.provider, llm.model, llm.image.size, llm.duration_ms

Metadata attached with Ask::Instrumentation.with_metadata(user_id:, session_id:) is forwarded as llm.metadata.* attributes on every span in that block.

Exporters

ask-opentelemetry is backend-agnostic. It emits standard OpenTelemetry spans; the exporter is configured by the OpenTelemetry SDK in your app, the same way you’d configure it for any other tracing.

For example, an OTLP exporter pointed at Langfuse:

require "opentelemetry-sdk"
require "opentelemetry-exporter-otlp"

OpenTelemetry::SDK.configure do |c|
  c.service_name = "my-agent"
  c.add_span_processor(
    OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
      OpenTelemetry::Exporter::OTLP::Exporter.new(
        endpoint: "https://cloud.langfuse.com/api/public/otel",
        headers: {
          "Authorization" => "Basic #{Base64.strict_encode64("#{ENV["LANGFUSE_PUBLIC_KEY"]}:#{ENV["LANGFUSE_SECRET_KEY"]}")}"
        }
      )
    )
  )
end

Datadog, Honeycomb, and Jaeger each ship their own OpenTelemetry exporters — same pattern, different endpoint. Point the SDK at them and the spans flow.

Manual Instrumentation

Add custom spans around any code with the standard OpenTelemetry API:

tracer = OpenTelemetry.tracer_provider.tracer("my-app")

tracer.in_span("custom.process") do |span|
  span.set_attribute("input.size", data.size)
  result = process(data)
  span.set_attribute("output.count", result.count)
  result
end

Next Steps


This site uses Just the Docs, a documentation theme for Jekyll.