backtracie
A Ruby gem for beautiful backtraces.
Aims to offer alternatives to Thread#backtrace
, Thread#backtrace_locations
, Kernel#caller
and Kernel#caller_locations
with similar (or better!?) performance, but with better metadata (such as class names).
Warning
|
⚠️ This gem is currently an exploration ground for the use of low-level techniques to get at internal Ruby VM structures. Due to the way it works, bugs can easily cause your whole Ruby VM to crash; Please take this into account if you plan on using this gem. Still, contributions and bug reports are mega welcome, and I hope to one day be able to remove this warning ⚠️. |
Contents
- Installation
- Usage
- Development
- Feedback and success stories
- Contributing
- Code of Conduct
- Interesting Links
Installation
Add this line to your application’s gems.rb
or Gemfile
:
gem 'backtracie'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install backtracie
This gem is versioned according to Semantic Versioning.
Usage
Currently, backtracie
exposes two APIs:
-
Backtracie.backtrace_locations(thread)
: Returns an array representing the backtrace of the giventhread
. Similar toThread#backtrace_locations
. -
Backtracie.caller_locations
: Returns an array representing the backtrace of the current thread, starting from the caller of the current method. Similar toKernel#caller_locations
.
These methods, inspired by their original Ruby counterparts, return arrays of Backtracie::Location
items. These items return A LOT more information than Ruby usually exposes:
$ bundle exec pry
[1] pry(main)> require 'backtracie'
[2] pry(main)> class Example
[2] pry(main)* def foo
[2] pry(main)* bar
[2] pry(main)* end
[2] pry(main)* def bar
[2] pry(main)* Backtracie.caller_locations.first
[2] pry(main)* end
[2] pry(main)* end
[3] pry(main)> Example.new.foo
=> #<Backtracie::Location:0x0000561b236c9208
@absolute_path="(pry)",
@base_label="foo",
@debug=
{:ruby_frame?=>true,
:should_use_iseq=>false,
:should_use_cfunc_name=>false,
:vm_method_type=>0,
:line_number=>3,
:called_id=>:foo,
:defined_class_refinement?=>false,
:self_class=>Example,
:real_class=>Example,
:self=>#<Example:0x0000561b236f3a08>,
:self_class_singleton?=>false,
:iseq_is_block?=>false,
:iseq_is_eval?=>false,
:cfunc_name=>nil,
:iseq=>
{:path=>"(pry)",
:absolute_path=>"(pry)",
:label=>"foo",
:base_label=>"foo",
:full_label=>"foo",
:first_lineno=>2,
:classpath=>nil,
:singleton_method_p=>false,
:method_name=>"foo",
:qualified_method_name=>"foo"},
:callable_method_entry=>
{:path=>"(pry)",
:absolute_path=>"(pry)",
:label=>"foo",
:base_label=>"foo",
:full_label=>"Example#foo",
:first_lineno=>2,
:classpath=>"Example",
:singleton_method_p=>false,
:method_name=>"foo",
:qualified_method_name=>"Example#foo"}},
@label="foo",
@lineno=3,
@path="(pry)",
@qualified_method_name="Example#foo">
This information can be used to to create much richer stack traces than the ones exposed by Ruby, including details such as class and module names, if methods are singletons, etc.
Development
After checking out the repo, run bundle install
to install dependencies. Then, run rake spec
to run the tests.
To open a console with the gem loaded, run bundle console
.
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 tags, and push the .gem
file to rubygems.org.
To test on specific Ruby versions you can use docker. E.g. to test on Ruby 2.6, use docker-compose run ruby-2.6
.
To test on all rubies using docker, you can use bundle exec rake test-all
.
Feedback and success stories
Your feedback is welcome!
Contributing
Bug reports and pull requests are welcome at https://github.com/ivoanjo/backtracie.
This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
Maintained with ❤️ by Ivo Anjo.
Code of Conduct
Everyone interacting in the backtracie project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.
Interesting Links
Here’s some gems that are doing similar things to backtracie
:
-
https://github.com/tmm1/stackprof: A sampling call-stack profiler for Ruby
-
https://github.com/ko1/pretty_backtrace: Pretty your exception backtrace
-
https://github.com/Shopify/stack_frames: This library allows backtraces to be captured and accessed without object allocations by leveraging MRI’s profile frames API
Other interesting links on this matter:
-
ruby/ruby#3299: vm_backtrace.c: let rb_profile_frames show cfunc frames
-
ruby/ruby#2713: Fix use of the rb_profile_frames start parameter
-
https://github.com/rake-compiler/rake-compiler: Provide a standard and simplified way to build and package Ruby C and Java extensions using Rake as glue.
-
https://github.com/ko1/rubyhackchallenge: "Ruby Hack Challenge" (RHC) is a short guide to hack MRI (Matz Ruby Interpreter) internals
-
https://docs.ruby-lang.org/en/3.0.0/doc/extension_rdoc.html: Creating Extension Libraries for Ruby
-
https://ruby-hacking-guide.github.io/: Ruby Hacking Guide
-
This is one of the most through and deep guides out there to the MRI internals. Very detailed and in depth, but outdated.
-
-
http://patshaughnessy.net/ruby-under-a-microscope: Book with a really good introduction to MRI internals.
-
https://www.youtube.com/watch?v=QDbj4Y0E5xo: Video on the implementation of backtrace APIs in MRI and how partial backtraces were optimized in 3.0/3.1.
-
ruby/ruby#4336 and ruby/ruby#4108: Segfaults due to execution context changes to support Ractors in Ruby 3.0
-
https://kumisystems.dl.sourceforge.net/project/elftoolchain/Documentation/libelf-by-example/20120308/libelf-by-example.pdf: Looking inside elf files 😱
My blog posts on better backtraces:
-
https://ivoanjo.me/blog/2020/07/05/ruby-experiment-include-class-names-in-backtraces/: ruby experiment: include class names in backtraces
-
https://ivoanjo.me/blog/2020/07/19/better-backtraces-in-ruby-using-tracepoint/: better backtraces in ruby using tracepoint