Observer

The observer interface allows you to track VM execution for profiling, debugging, and auditing. An observer receives callbacks as the virtual machine executes bytecode instructions, giving you visibility into script behavior without modifying the script itself.

Attaching an Observer

Pass an observer to Eval, Compile, or Run using the WithObserver option:

result, err := risor.Eval(ctx, source,
    risor.WithEnv(risor.Builtins()),
    risor.WithObserver(myObserver),
)

The Observer Interface

An observer implements the vm.Observer interface. The VM calls the observer on each instruction step, providing information about the current execution state.

type Observer interface {
    Step(state *vm.State)
}

Use Cases

Profiling

Count instructions, measure execution time per function, or identify hot paths in a script.

type Profiler struct {
    steps int64
}
 
func (p *Profiler) Step(state *vm.State) {
    p.steps++
}

After execution, p.steps contains the total number of instructions executed.

Debugging

Log or inspect the VM state at each step. This is useful for building step-through debuggers or execution traces.

type Tracer struct {
    log []string
}
 
func (t *Tracer) Step(state *vm.State) {
    t.log = append(t.log, fmt.Sprintf("ip=%d", state.IP))
}

Auditing

Track which functions or operations a script invokes. This can be used to enforce security policies or generate audit logs.

Combining with Other Options

The observer works alongside all other options. A common pattern is to combine an observer with resource limits for both visibility and safety:

profiler := &Profiler{}
result, err := risor.Eval(ctx, source,
    risor.WithEnv(risor.Builtins()),
    risor.WithObserver(profiler),
    risor.WithMaxSteps(100000),
    risor.WithTimeout(5 * time.Second),
)
fmt.Printf("executed %d instructions\n", profiler.steps)

Performance Considerations

The observer is called on every instruction step, so the callback should be lightweight. Avoid blocking operations, heavy allocations, or synchronization in the Step method. For high-throughput use cases, consider sampling (e.g. only recording every Nth step) or batching observations for later analysis.


Risor © 2026 Deep Noodle