Instrumentation
Table of contents
Runtime tracing and debugging for operations and chains.
Basic Usage
Tracing a Single Call
result = MyOperation.explain(param: value)
Prints an execution tree to stdout and returns the result.
Tracing a Block
TypedOperation::Instrumentation.explaining do
MyOperation.call(params)
AnotherOperation.call(other_params)
end
Custom Output
Control where trace output is written and whether to use colors:
TypedOperation::Instrumentation.with_output($stderr, color: false) do
MyOperation.explain(param: value)
end
Method signature:
Instrumentation.with_output(io, color: nil, &block)io- Any IO object (e.g.,$stdout,$stderr,StringIO.new)color- Boolean to enable/disable ANSI colors (defaults to auto-detect based on TTY)
Pass Modes
When tracing chained operations, the trace output shows how values are passed between operations:
(**ctx)- Context splatted as keyword arguments- Appears with
.thenwhen the next operation accepts keyword parameters - Smart chaining automatically extracts matching parameters from context
- Appears with
(ctx)- Single value passed as positional argument- Appears with
.thenwhen the next operation accepts a single positional parameter - Also used with
.then_passesto explicitly pass the full context
- Appears with
(transform)- Value transformed by a block- Appears with
.transformmethod
- Appears with
(fallback)- Fallback operation executed after failure- Appears with
.or_elsemethod
- Appears with
Chain Tracing Examples
Smart Chaining with Extracted Parameters
When chaining operations that accept keyword arguments, the tracer shows which parameters were extracted from the context:
class CreateUser < TypedOperation::Base
include TypedOperation::Explainable
param :email, String
param :name, String
def perform
{id: 1, email: email, name: name}
end
end
class SendWelcome < TypedOperation::Base
include TypedOperation::Explainable
param :email, String
param :name, String
def perform
"Welcome email sent to #{email}"
end
end
chain = CreateUser.then(SendWelcome)
chain.explain(email: "user@example.com", name: "Alice")
# Output:
# CreateUser [2.1ms] ✓
# →
# SendWelcome [1.3ms] ✓ (**ctx) ← [email, name]
# => "Welcome email sent to user@example.com"
Understanding the output:
(**ctx)- Indicates keyword argument splatting was used← [email, name]- Shows which parameters were extracted from the previous operation’s result- This helps you see which fields from the context are being passed to each operation
- Only parameters that match the operation’s defined parameters are extracted
Fallback Chains
Fallback operations are shown with a special arrow:
class ApiCall < TypedOperation::Base
include TypedOperation::Explainable
param :should_fail, _Boolean
def perform
raise "API error" if should_fail
"success"
end
end
class FallbackHandler < TypedOperation::Base
include TypedOperation::Explainable
def perform
"fallback response"
end
end
chain = ApiCall.or_else(FallbackHandler)
chain.explain(should_fail: true)
# Output:
# ApiCall [0.5ms] ✗ (RuntimeError: API error)
# ⤷
# ⤷ FallbackHandler [0.2ms] ✓ (fallback)
# => "fallback response"
The ⤷ arrow indicates fallback execution, and (fallback) shows the pass mode.
How It Works
Instrumentation uses thread-local storage to track execution without modifying call signatures. Two mechanisms work together:
- Trace Stack - Maintains the call hierarchy as a stack of
Traceobjects - Chain Context - Captures metadata about how operations are invoked (pass mode, extracted parameters, fallback status)
When .explain is called, a root trace is pushed (enabling tracing? = true). As chains execute, they record context via set_chain_context before calling each operation. The operation’s Traceable wrapper consumes this context via take_chain_context, then pushes/pops traces as execution progresses.
Thread-locals provide a side-channel for passing instrumentation data without polluting the call interface. Since Thread.current[] isolates data per-thread and chains execute synchronously, this approach is thread-safe. The tracing? guard ensures instrumentation is a no-op when not actively debugging.