Module: BacktraceShortener

Defined in:
lib/backtrace_shortener.rb,
lib/backtrace_shortener/version.rb

Overview

This can patch the Exception class to prune the size of the backtraces and to make each line shorter. The idea is to improve the developer experience, because exceptions in apps using rbenv and gems can be taller than one terminal screen and each line can be long. See how painful this is with a 50 line backtrace:

...
/Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sequel-3.28.0/lib/sequel/adapters/mysql.rb:175:in `query'
/Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sequel-3.28.0/lib/sequel/adapters/mysql.rb:175:in `block in _execute'
...

To apply this patch, invoke BacktraceCleanear.monkey_patch_the_exception_class!

If you want to access the full backtrace while debugging, you can use my_exception.full_backtrace. If you want to write your own filter, append your own Proc to BacktraceShortener.filters:

BacktraceShortener.filters.unshift(Proc.new { |backtrace| backtrace[0, 10] }) # Shortens to 10 lines.

Constant Summary collapse

VERSION =
"0.1.0"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.filtersObject

Returns the value of attribute filters


67
68
69
# File 'lib/backtrace_shortener.rb', line 67

def filters
  @filters
end

Class Method Details

.abbreviate_gem_directory_name(backtrace) ⇒ Object

Abbreviate any long gem paths, e.g.

   /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sequel-3.28.0/lib
=> .../gems/1.9.1/gems/sequel-3.28.0/lib

33
34
35
36
37
# File 'lib/backtrace_shortener.rb', line 33

def self.abbreviate_gem_directory_name(backtrace)
  backtrace.map do |line|
    line.sub(Gem.dir, "...")
  end
end

.collapse_gems(backtrace) ⇒ Object

Backtraces which involve gems can include many lines from within the gem's internals. This usually isn't helpful. Collapse those long sequences of lines and include just the first and last line.


41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/backtrace_shortener.rb', line 41

def self.collapse_gems(backtrace)
  current_gem = nil
  current_gem_line_number = nil
  i = backtrace.size - 1

  while i >= 0
    line_gem = gem_from_line(backtrace[i])
    if line_gem != current_gem || i == 0
      if current_gem && (current_gem_line_number - i) > 2
        backtrace[i..current_gem_line_number] = [backtrace[i], "<..>", backtrace[current_gem_line_number]]
      end
      current_gem = line_gem
      current_gem_line_number = i
    end
    i -= 1
  end
  backtrace
end

.gem_from_line(backtrace_line) ⇒ Object

Returns the gem in the given backtrace line, or nil if the line does not include a gem in it.


61
62
63
64
# File 'lib/backtrace_shortener.rb', line 61

def self.gem_from_line(backtrace_line)
  # Pull out "sequel-3.28.0" from this path: ".../lib/ruby/gems/1.9.1/gems/sequel-3.28.0/lib/..."
  (%r{/ruby/gems/[^/]+/gems/([^/]+)/}.match(backtrace_line) || [])[1]
end

.monkey_patch_the_exception_class!Object


15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/backtrace_shortener.rb', line 15

def self.monkey_patch_the_exception_class!
  return if Exception.new.respond_to?(:backtrace_prior_to_backtrace_shortener_monkey_patch)

  Exception.class_eval do
    alias :backtrace_prior_to_backtrace_shortener_monkey_patch :backtrace
    alias :full_backtrace :backtrace_prior_to_backtrace_shortener_monkey_patch

    def backtrace
      backtrace = backtrace_prior_to_backtrace_shortener_monkey_patch
      return nil if backtrace.nil?
      BacktraceShortener.filters.inject(backtrace) { |backtrace, filter| filter.call(backtrace) }
    end
  end
end