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|
            begin
              v = b.local_variable_get(lv).inspect
              r[lv] = v
            rescue NameError,TypeError
              # ignore
            end
            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.3"

Class Method Summary collapse

Class Method Details

.disableObject



131
132
133
134
135
136
137
138
139
# File 'lib/pretty_backtrace.rb', line 131

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

.effective_lines=(lines) ⇒ Object



149
150
151
# File 'lib/pretty_backtrace.rb', line 149

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

.enableObject

configuration



121
122
123
124
125
126
127
128
129
# File 'lib/pretty_backtrace.rb', line 121

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

.file_contents=(setting) ⇒ Object



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

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

.iseq_local_variables(iseq) ⇒ Object



64
65
66
67
# File 'lib/pretty_backtrace.rb', line 64

def self.iseq_local_variables iseq
  _,_,_,_,arg_info,name,path,a_path,_,type,lvs, * = iseq.to_a
  lvs.select{|lv| lv.is_a?(Symbol) || lv.is_a?(String) }
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.



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
113
114
115
116
117
# File 'lib/pretty_backtrace.rb', line 73

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



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

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