The tracer
gem provides helpful tracing utilities to help users observe their program's runtime behaviour.
The currently supported tracers are:
It also comes with experimental IRB integration to allow quick access from REPL.
$ bundle add tracer --group=development,test
Or directly add it to your Gemfile
group :development, :test do
gem "tracer"
end
If bundler is not being used to manage dependencies, install the gem by executing:
$ gem install tracer
Tracer.trace(object) { ... } # trace object's activities in the given block
Tracer.trace_call { ... } # trace method calls in the given block
Tracer.trace_exception { ... } # trace exceptions in the given block
Example
require "tracer"
obj = Object.new
def obj.foo
100
end
def bar(obj)
obj.foo
end
Tracer.trace(obj) { bar(obj) }
#depth:1 #<Object:0x000000010903c190> is used as a parameter obj of Object#bar at test.rb:13:in `block in <main>'
#depth:2 #<Object:0x000000010903c190> receives .foo at test.rb:10:in `bar'
If you want to avoid the Tracer
namespace, you can do require "tracer/helper"
instead:
require "tracer/helper"
trace(object) { ... } # trace object's activities in the given block
trace_call { ... } # trace method calls in the given block
trace_exception { ... } # trace exceptions in the given block
If you want to have more control over individual traces, you can use individual tracer classes:
class User
def initialize(name) = (@name = name)
def name() = @name
end
def authorized?(user)
user.name == "John"
end
user = User.new("John")
tracer = ObjectTracer.new(user)
tracer.start do
user.name
authorized?(user)
end
#depth:3 #<User:0x000000010696cad8 @name="John"> receives #name (User#name) at test.rb:14:in `block in <main>'
#depth:3 #<User:0x000000010696cad8 @name="John"> is used as a parameter user of Object#authorized? at test.rb:15:in `block in <main>'
#depth:4 #<User:0x000000010696cad8 @name="John"> receives #name (User#name) at test.rb:8:in `authorized?'
Note
Ruby 3.0 and below's accessor calls don't trigger TracePoint properly so the result may be inaccurate with those versions.
require "tracer"
class Cat
attr_accessor :name
end
cat = Cat.new
tracer = IvarTracer.new(cat, :@name)
tracer.start do
cat.name = "Kitty"
cat.instance_variable_set(:@name, "Ketty")
end
#depth:3 Cat#name= sets @name = "Kitty" at test.rb:11
#depth:3 Kernel#instance_variable_set sets @name = "Ketty" at test.rb:12
ExceptionTracer.new.start
begin
raise "boom"
rescue StandardError
nil
end
#depth:0 #<RuntimeError: boom> raised at test.rb:4
#depth:1 #<RuntimeError: boom> rescued at test.rb:6
class User
def initialize(name) = (@name = name)
def name() = @name
end
def authorized?(user)
user.name == "John"
end
user = User.new("John")
tracer = CallTracer.new
tracer.start do
user.name
authorized?(user)
end
#depth:4 > block at test.rb:13
#depth:5 > User#name at test.rb:4
#depth:5 < User#name #=> "John" at test.rb:4
#depth:5 > Object#authorized? at test.rb:7
#depth:6 > User#name at test.rb:4
#depth:6 < User#name #=> "John" at test.rb:4
#depth:6 > String#== at test.rb:8
#depth:6 < String#== #=> true at test.rb:8
#depth:5 < Object#authorized? #=> true at test.rb:9
#depth:4 < block #=> true at test.rb:16
class User
def initialize(name) = (@name = name)
def name() = @name
end
def authorized?(user)
user.name == "John"
end
user = User.new("John")
tracer = LineTracer.new
tracer.start do
user.name
authorized?(user)
end
#depth:4 at test.rb:14
#depth:4 at test.rb:15
#depth:5 at test.rb:8
Once required, tracer
registers a few IRB commands to help you trace Ruby expressions:
trace Trace the target object (or self) in the given expression. Usage: `trace [target,] <expression>`
trace_call Trace method calls in the given expression. Usage: `trace_call <expression>`
trace_exception Trace exceptions in the given expression. Usage: `trace_exception <expression>`
Example
# test.rb
require "tracer"
obj = Object.new
def obj.foo
100
end
def bar(obj)
obj.foo
end
binding.irb
irb(main):001:0> trace obj, bar(obj)
#depth:23 #<Object:0x0000000107a86648> is used as a parameter obj of Object#bar at (eval):1:in `<main>'
#depth:24 #<Object:0x0000000107a86648> receives .foo at test.rb:10:in `bar'
=> 100
irb(main):002:0> trace_call bar(obj)
#depth:23> Object#bar at (eval):1:in `<main>'
#depth:24> #<Object:0x0000000107a86648>.foo at test.rb:10:in `bar'
#depth:24< #<Object:0x0000000107a86648>.foo #=> 100 at test.rb:10:in `bar'
#depth:23< Object#bar #=> 100 at (eval):1:in `<main>'
=> 100
TBD
A big shout-out to @ko1 (Koichi Sasada) for his awesome work on ruby/debug
.
The tracers in ruby/debug
were an inspiration and laid the groundwork for this project.
After checking out the repo, run bin/setup
to install dependencies. Then, run rake test-unit
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and the created tag, and push the .gem
file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/tracer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
The gem is available as open source under the terms of the 2-Clause BSD License.
Everyone interacting in the Ruby::Tracer project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.