Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using BacktraceCleaner in Rails console?

I use the Rails console in debug mode quite a lot, and its just a bit tedious having to change the size of the console window so I can find the top of the listing without having to scroll.

I thought the BacktraceCleaner could help with that, but I can't get it silence anything in the console.

I put this code in an initializer in my application.

bc = Rails.backtrace_cleaner
bc.add_filter { |line| line.gsub(Rails.root.to_s, '<root>') }
bc.add_silencer { |line| line.index('<root>').nil? and line.index('/') == 0 }
bc.add_silencer { |line| line.index('<root>/vendor/') == 0 }
bc.add_silencer { |line| line =~ /console.rb/ }
bc.add_silencer { |line| line =~ /ruby-debug.ide.rb/ }
bc.add_silencer { |line| line =~ /rdebug-ide/ }

but no effect on the console errors. So I tried it directly in the console:

>>bc = Rails.backtrace_cleaner
>>bc.add_silencer { |line| line =~ /console.rb/ }
>> 1/0
   ZeroDivisionError: divided by 0
from (irb):23:in `/'
from (irb):23
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/railties-4.0.3/lib/rails/commands /console.rb:90:in `start'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/railties-4.0.3/lib/rails/commands/console.rb:9:in `start'

--and still seeing backtrace lines containing 'console.rb'. Is Rails.backtrace_cleaner returning some other cleaner that isn't the one used in the Rails console environment?

How can I get a handle to (or install) a backtrace cleaner on the console backtraces?

like image 630
GGizmos Avatar asked Mar 29 '14 04:03

GGizmos


1 Answers

The problem is that the IRB console implements its own hard-wired backtrace silencer that is different from the Rails' one. But it can be overridden by monkey-patching IRB.

From IRB's source code we can see that the silencer is called here using the filter_backtrace method from the WorkSpace class. Thus, we can patch this method to use the Rails silencer on top of the default IRB's one.

The patch could be put in a Rails initializer but a cleaner approach in my opinion is to use the IRB_RCconfiguration variable that can be set to any ruby code and that will be called during IRB's initialization. Thus we'll keep the patch in IRB's context only and won't affect the Rails app code itself.

The following code goes to ~/.irbrc:

if ENV['RAILS_ENV']

  # silence console backtraces using BacktraceSilencer from Rails
  IRB.conf[:IRB_RC] = Proc.new do

    class IRB::WorkSpace
      alias_method :orig_filter_backtrace, :filter_backtrace

      def filter_backtrace(bt)
        filtered_bt = orig_filter_backtrace(bt)

        # The Rails silencer operates on the whole backtrace therefore
        # we need to temporarily convert the particular trace line to an array
        rails_backtrace_cleaner.clean(Array(filtered_bt)).first
      end

      private

      def rails_backtrace_cleaner
        Rails.backtrace_cleaner
      end

    end
  end
end

As commented in the code, we need to deal with one small issue - the Rails silencer operates on the whole backtrace (passed as an array of lines), whereas the IRB's silencing method is called for each line separately. That's why the line is temporarily converted to an array before passing to the Rails silencer.

If you really want a custom silencer, not the Rails one, use something like this instead:

  def rails_backtrace_cleaner                                  
    @rails_backtrace_cleaner ||= begin
      bc = ActiveSupport::BacktraceCleaner.new
      bc.add_filter { |line| line.gsub(Rails.root.to_s, '<root>') }
      bc.add_silencer { |line| line.index('<root>').nil? and line.index('/') == 0 }
      bc.add_silencer { |line| line.index('<root>/vendor/') == 0 }
      bc.add_silencer { |line| line =~ /console.rb/ }
      bc.add_silencer { |line| line =~ /ruby-debug.ide.rb/ }
      bc.add_silencer { |line| line =~ /rdebug-ide/ }
      bc
    end
  end

Test (with the default Rails silencer)

$ rails c
Loading development environment (Rails 4.2.5.1)
>> 1/0
ZeroDivisionError: divided by 0    
>>

Example with an exception raised inside a model method:

$ rails c
Loading development environment (Rails 4.2.5.1)
>> BaseUser.find(1234).update_rating
RuntimeError: Exception occured!
  from app/models/base_user.rb:51:in `update_rating'
>> 

As can be seen, Rails internal stacktrace lines are silenced and Rails root paths are filtered out.

like image 150
Matouš Borák Avatar answered Sep 21 '22 12:09

Matouš Borák