Tracer
Introduction
Overview
Tracers let you observe what the runtime is doing. They hook into requests, tool calls, compactions, and other events. Debug a misbehaving agent, monitor request latency, or export spans to an observability backend. A provider-wide tracer intercepts every request through that provider. An agent-local tracer only covers requests made by that agent.
How it works
When you want to observe runtime events, subclass
LLM::Tracer
and implement the hooks you need, or use one of the built-in
tracers. The built-in tracers include
LLM::Tracer::Logger
(writes structured JSON to stdout or a file),
LLM::Tracer::PrettyLogger
(writes human-readable single-line logs to stderr), and
LLM::Tracer::Telemetry
(exports spans via OTLP for OpenTelemetry). Attach a tracer to a
provider or an agent:
llm = LLM.deepseek(key: ENV["KEY"])
llm.tracer = LLM::Tracer::PrettyLogger.new(llm)
agent = LLM::Agent.new(llm)
agent.talk "Hello"
Why would I use it?
Tracers give you visibility into what the runtime is doing. Debug
a misbehaving agent by tracing every request it makes. Monitor
request latency and token usage across providers. Export spans to
OpenTelemetry for integration with existing observability pipelines.
LLM::Tracer::PrettyLogger
is the best choice during development for compact, human-readable
output.
LLM::Tracer::Logger
provides structured JSON, and
LLM::Tracer::Telemetry
exports spans to OpenTelemetry for production observability.
Notes
The tracer is extensible. You can implement custom hooks for any
runtime event. The scope can be an individual agent or every
request a provider makes. Three built-in tracers are available:
LLM::Tracer::PrettyLogger
(human-readable),
LLM::Tracer::Logger
(structured JSON), and
LLM::Tracer::Telemetry
(OpenTelemetry).
For a shorter way to build a
LLM::Tracer::Logger,
use the LLM.logger
convenience method. It takes a provider and forwards the options as
a positional hash, so LLM.logger(llm, io: $stdout) is equivalent
to LLM::Tracer::Logger.new(llm, io: $stdout).
Provider
Overview
A provider-wide tracer intercepts every request made through that provider. All agents sharing the same provider share the same tracer. Use this to trace at the infrastructure level without configuring each agent individually.
How it works
When you want every request through a provider to be traced, set the tracer on the provider directly. Every request made through that provider, regardless of which agent initiates it, flows through the same tracer hooks. The provider holds a reference to the tracer and passes it to every new context it creates. This ensures consistent observability without configuring each agent individually.
llm = LLM.deepseek(key: ENV["KEY"])
llm.tracer = LLM::Tracer::Logger.new(llm, io: $stdout)
Why would I use it?
A provider-wide tracer captures every request at the infrastructure level. All agents sharing the same provider share the same tracer.
Notes
The tracer can also write to a file with the path: option to
LLM::Tracer::Logger.new.
Agent
Overview
An agent-local tracer only covers requests made by that agent.
Attach it via the tracer: keyword argument to
LLM::Agent.new
and it follows that agent wherever it goes. Different agents can
have different tracers.
How it works
When you want a tracer for a specific agent, pass it to the agent on creation. Only requests made by that agent flow through the tracer, leaving other agents on the same provider unaffected.
llm = LLM.deepseek(key: ENV["KEY"])
agent = LLM::Agent.new(llm, tracer: LLM::Tracer::Logger.new(llm, io: $stdout))
Why would I use it?
Agent-local tracers let each agent log differently. One agent might log to stdout, another to a file, a third to OpenTelemetry.
Notes
The tracer can also write to a file with the path: option to
LLM::Tracer::Logger.new.
PrettyLogger
Overview
LLM::Tracer::PrettyLogger
writes human-readable single-line logs to stderr. Unlike
LLM::Tracer::Logger
(which emits structured JSON), the pretty logger is designed for
interactive development sessions where you want to see request and
tool-call activity at a glance.
How it works
Each request and tool call produces a single line on stderr with
the model, duration, and a summary of the activity. The logger
accepts an io: option to redirect output.
Provider-wide
llm = LLM.deepseek(key: ENV["KEY"])
llm.tracer = LLM::Tracer::PrettyLogger.new(llm)
Agent-local
llm = LLM.deepseek(key: ENV["KEY"])
agent = LLM::Agent.new(llm, tracer: LLM::Tracer::PrettyLogger.new(llm))
Custom output
tracer = LLM::Tracer::PrettyLogger.new(llm, io: $stdout)
tracer = LLM::Tracer::PrettyLogger.new(llm, io: File.open("trace.log", "a"))
Why would I use it?
The pretty logger is the best choice for development. The output is
compact enough to follow in real time while still showing the model
name, duration, and tool calls. Switch to
LLM::Tracer::Logger
when you need structured JSON for programmatic analysis, or to
LLM::Tracer::Telemetry
when you need OpenTelemetry exports.
Notes
The pretty logger writes to $stderr by default. Set io: to
redirect output. All three built-in tracers share the same interface,
so switching between them requires changing only the class name.