Module: PrettyBacktrace

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

Constant Summary collapse

CONFIG =
{
  truncate_length: 20,
  disabled_exception_classes: {},
  effective_lines: 0, # 0 is infinite
  file_contents: true,
  file_contents_lines: 2,

  multi_line: false,
  multi_line_truncate_length: 60,
  multi_line_indent: 10,
}
EXCEPTION_MODIFIER_TRACE =
TracePoint.new(:raise){|tp|
  begin
    e = tp.raised_exception

    CONFIG[:disabled_exception_classes].each{|klass, _|
      next if e.kind_of?(klass)
    }

    RubyVM::DebugInspector.open{|dc|
      locs = dc.backtrace_locations

      effective_lines = CONFIG[:effective_lines]
      effective_lines = locs.size - 2 if effective_lines == 0

      pretty_backtrace = locs.map.with_index{|loc, i|
        next if i < 2
        next loc.to_s unless (effective_lines -= 1) >= 0

        iseq = dc.frame_iseq(i)

        if iseq
          b = dc.frame_binding(i)
          lvs = iseq_local_variables(iseq)
          lvs_val = lvs.inject({}){|r, lv|
            v = b.local_variable_get(lv).inspect
            r[lv] = v; r
          }
        else
          lvs_val = {}
        end

        modify_trace_line loc, loc.absolute_path, loc.lineno, lvs_val
      }.compact
      e.set_backtrace pretty_backtrace
    }
  rescue => e
    puts e
    puts e.backtrace
    fail "PrettyBacktrace BUG"
  end
}
VERSION =
"0.1.1"

Class Method Summary collapse

Class Method Details

.disableObject



126
127
128
129
130
131
132
133
134
# File 'lib/pretty_backtrace.rb', line 126

def self.disable
  if block_given?
    EXCEPTION_MODIFIER_TRACE.disable{
      yield
    }
  else
    EXCEPTION_MODIFIER_TRACE.disable
  end
end

.effective_lines=(lines) ⇒ Object



144
145
146
# File 'lib/pretty_backtrace.rb', line 144

def self.effective_lines=(lines)
  CONFIG[:effective_lines] = lines
end

.enableObject

configuration



116
117
118
119
120
121
122
123
124
# File 'lib/pretty_backtrace.rb', line 116

def self.enable
  if block_given?
    EXCEPTION_MODIFIER_TRACE.enable{
      yield
    }
  else
    EXCEPTION_MODIFIER_TRACE.enable
  end
end

.file_contents=(setting) ⇒ Object



140
141
142
# File 'lib/pretty_backtrace.rb', line 140

def self.file_contents=(setting)
  CONFIG[:file_contents] = setting
end

.iseq_local_variables(iseq) ⇒ Object



59
60
61
62
# File 'lib/pretty_backtrace.rb', line 59

def self.iseq_local_variables iseq
  _,_,_,_,arg_info,name,path,a_path,_,type,lvs, * = iseq.to_a
  lvs
end

.modify_trace_line(backtrace_location, absolute_path, lineno, local_variables_values) ⇒ Object

local_variables_values is a Hash object containing pairs of a local variable name and value of local variable.



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/pretty_backtrace.rb', line 68

def self.modify_trace_line backtrace_location, absolute_path, lineno, local_variables_values
  trace_line = backtrace_location.to_s

  if CONFIG[:multi_line]
    indent = ' ' * CONFIG[:multi_line_indent]
    additional = ''

    # file scope
    if CONFIG[:file_contents] && File.exists?(absolute_path)
      fclines = CONFIG[:file_contents_lines]
      start_line = lineno - 1 - 1 * fclines
      start_line = 0 if start_line < 0

      additional << indent + "[FILE]\n"
      additional << open(absolute_path){|f| f.readlines[start_line, 1 + 2 * fclines]}.map.with_index{|line, i|
        ln = start_line + i + 1
        line = line.chomp
        '%s%4d|%s' % [ln == lineno ? indent[0..-3] + "->" : indent, ln, line]
      }.join("\n")
      additional << "\n"
      additional << "\n"
    end

    # local variables
    unless local_variables_values.empty?
      additional << indent + "[LOCAL VARIABLES]\n"
      additional << local_variables_values.map{|lv, v|
        v = v[0..CONFIG[:multi_line_truncate_length]] + '...' if v.length > CONFIG[:multi_line_truncate_length]
        indent + "  #{lv} = #{v.to_s}"
      }.join("\n") + "\n"
    end

    trace_line = "#{trace_line}\n#{additional}" unless additional.empty?
  else
    unless local_variables_values.empty?
      additional = local_variables_values.map{|lv, v|
        v = v[0..CONFIG[:truncate_length]] + '...' if v.length > CONFIG[:truncate_length]
        "#{lv} = #{v.to_s}"
      }.join(", ")
      trace_line = "#{trace_line} (#{additional})"
    end
  end

  trace_line
end

.multi_line=(setting) ⇒ Object



136
137
138
# File 'lib/pretty_backtrace.rb', line 136

def self.multi_line=(setting)
  CONFIG[:multi_line] = setting
end