【问题标题】:How do I get ruby to print a full backtrace that includes arguments passed to functions?如何让 ruby​​ 打印包含传递给函数的参数的完整回溯?
【发布时间】:2017-10-28 21:11:52
【问题描述】:

有时回溯足以诊断问题。但有时在不知道传递给函数的内容的情况下,崩溃的原因并不明显。

获取传递给导致崩溃的函数的信息将非常有用,尤其是在复制不明显的情况下,因为它是由例如网络连接中的异常、奇怪的用户输入或因为程序依赖于随机化或进程而引起的来自外部传感器的数据。

假设有以下程序

def handle_changed_input(changed_input)
    raise 'ops' if changed_input =~ /magic/
end

def do_something_with_user_input(input)
    input = "#{input.strip}c"
    handle_changed_input(input)
end

input = gets
do_something_with_user_input(input)

用户输入“魔法”作为输入。通常有一个

test.rb:2:in `handle_changed_input': ops (RuntimeError)
    from test.rb:7:in `do_something_with_user_input'
    from test.rb:11:in `<main>'

作为输出。可以做什么来显示传递给函数的内容?类似的东西

test.rb:2:in `handle_changed_input("magic")': ops (RuntimeError)
    from test.rb:7:in `do_something_with_user_input("magi\n")'
    from test.rb:11:in `<main>'

它在许多情况下都很有用(在参数不能表示为合理长度的字符串时并不是真正有用,默认情况下不启用它是有充分理由的)。

如何添加此功能?程序在正常运行期间必须像往常一样工作,最好在崩溃之前没有额外的输出。

我试过了

def do_something_with_user_input(input)
    method(__method__).parameters.map do |_, name|
        puts "#{name}=#{binding.local_variable_get(name)}"
    end
    raise 'ops' if input =~ /magic/
end

input = gets

在 Is there a way to access method arguments in Ruby? 中找到,但它会在每个入口处打印,以实现既会淹没输出又会使程序显着变慢的功能。

【问题讨论】:

  • 如果我理解正确,您需要打印出哪些参数被传递给引发错误的方法。对吗?
  • 对所有方法调用进行概括是没有意义的,因为对象参数可能没有有意义的字符串表示。你所看到的只是:#&lt;Object:0x007ff6d3b7af08&gt;
  • @fylooi 我知道有很多很多情况下这是没有意义的(没有转换为字符串的对象、非常长的字符串、处理私有数据的程序以及许多其他情况)。但是在方法的参数是字符串或实现到字符串转换的对象的情况下,它会非常有用。另请参阅“默认情况下不启用它是有充分理由的”。
  • @Efesto 非常感谢,问题的初始版本不清楚。我想打印出堆栈跟踪中所有方法的参数 - 从发生异常的地方开始。我修改了问题,使我想查看所有方法的参数更加明显。
  • @MateuszKonieczny:我想收集那个赏金,但我担心你想做的事情实际上是不可能的。

标签: ruby debugging stack-trace printstacktrace


【解决方案1】:

我没有完整的解决方案,但是...但是您可以使用 Ruby 核心库中的 TracePoint 类在受控环境中获取所有调用方法的方法参数。

看例子:

trace = TracePoint.new(:call) do |tp|
  puts "===================== #{tp.method_id}"
  b_self = tp.binding.eval('self')
  names = b_self.method(tp.method_id).parameters.map(&:last)
  values = names.map { |name| tp.binding.eval(name.to_s) }
  p names.zip(values)
end

trace.enable

def method_a(p1, p2, p3)
end

method_a(1, "foobar", false)

#=> ===================== method_a
#=> [[:p1, 1], [:p2, "foobar"], [:p3, false]]

【讨论】:

    【解决方案2】:

    为了打印异常回溯,Ruby 使用来自 error.c (exc_backtrace on github) 的 C 函数 exc_backtrace。除非您使用所需的功能修补 Ruby,否则我认为没有办法更改异常回溯输出。 这是一个您可能会发现有用的 sn-p (trace.rb):

    set_trace_func -> (event, file, line, id, binding, classname) do
      if event == 'call' && meth = binding.eval('__method__')
        params = binding.method(meth).parameters.select{|e| e[0] != :block}
        values = params.map{|_, var| [var, binding.local_variable_get(var)]}
        printf "%8s %s:%-2d %15s %8s %s\n", event, file, line, id, classname, values.inspect
      else
        printf "%8s %s:%-2d %15s %8s\n", event, file, line, id, classname
      end
    end
    
    
    def foo(a,b = 0)
      bar(a, foo: true)
    end
    
    def bar(c, d = {})
      puts "!!!buz!!!\n"
    end
    
    foo('lol')
    

    那个sn-p的输出是:

    c-return /path/to/trace.rb:1   set_trace_func   Kernel
        line /path/to/trace.rb:12
      c-call /path/to/trace.rb:12    method_added   Module
    c-return /path/to/trace.rb:12    method_added   Module
        line /path/to/trace.rb:16
      c-call /path/to/trace.rb:16    method_added   Module
    c-return /path/to/trace.rb:16    method_added   Module
        line /path/to/trace.rb:20
        call /path/to/trace.rb:12             foo   Object [[:a, "lol"], [:b, 0]]
        line /path/to/trace.rb:13             foo   Object
        call /path/to/trace.rb:16             bar   Object [[:c, "lol"], [:d, {:foo=>true}]]
        line /path/to/trace.rb:17             bar   Object
      c-call /path/to/trace.rb:17            puts   Kernel
      c-call /path/to/trace.rb:17            puts       IO
      c-call /path/to/trace.rb:17           write       IO
    !!!buz!!!
    c-return /path/to/trace.rb:17           write       IO
    c-return /path/to/trace.rb:17            puts       IO
    c-return /path/to/trace.rb:17            puts   Kernel
      return /path/to/trace.rb:18             bar   Object
      return /path/to/trace.rb:14             foo   Object
    

    我希望这对你有帮助,就像它对我有帮助一样。

    【讨论】:

      【解决方案3】:

      我认为这是可能的。下面的代码并不完美,需要一些额外的工作,但它掩盖了带有参数值的堆栈跟踪的主要思想。请注意,为了了解调用站点,我将原始堆栈跟踪与跟踪功能捕获的入口站点一起压缩。为了区分这些条目,我分别使用 '>' 和 '

      class Reporting
        def self.info(arg1)
          puts "*** #{arg1} ***"
        end
      end
      
      
      def read_byte(arg1)
        Reporting.info(arg1)
        raise Exception.new("File not found")
      end
      
      def read_input(arg1)
        read_byte(arg1)
      end
      
      def main(arg1)
        read_input(arg1)
      end
      
      
      class BetterStacktrace
        def self.enable
          set_trace_func -> (event, file, line, id, binding, classname) do
            case event
            when 'call'
              receiver_type = binding.eval('self.class')
              if receiver_type == Object
                meth = binding.eval('__method__')
                params = binding.method(meth).parameters.select{|e| e[0] != :block}
                values = params.map{|_, var| [var, binding.local_variable_get(var)]}
                self.push(event, file, line, id, classname, values)
              else
                self.push(event, file, line, id, classname)
              end
            when 'return'
              self.pop
            when 'raise'
              self.push(event, file, line, id, classname)
              Thread.current[:_keep_stacktrace] = true
            end
          end
        end
      
        def self.push(event, file, line, id, classname, values=nil)
          Thread.current[:_saved_stacktrace] = [] unless Thread.current.key?(:_saved_stacktrace)
          unless Thread.current[:_keep_stacktrace]
            if values
              values_msg = values.map(&:last).join(", ")
              msg = "%s:%d:in `%s(%s)'" % [file, line, id, values_msg]
            else
              msg = "%s:%d:in `%s'" % [file, line, id]
            end
            Thread.current[:_saved_stacktrace] << msg
          end
        end
      
        def self.pop()
          Thread.current[:_saved_stacktrace] = [] unless Thread.current.key?(:_saved_stacktrace)
          unless Thread.current[:_keep_stacktrace]
            value = Thread.current[:_saved_stacktrace].pop
          end
        end
      
        def self.disable
          set_trace_func nil
        end
      
        def self.print_stacktrace(calls)
          enters = Thread.current[:_saved_stacktrace].reverse
          calls.zip(enters).each do |call, enter|
            STDERR.puts "> #{enter}"
            STDERR.puts "< #{call}"
          end
          Thread.current[:_saved_stacktrace] = []
        end
      end
      
      BetterStacktrace.enable
      
      begin
        main(10)
      rescue Exception => ex
        puts "--- Catched ---"
        puts ex
        BetterStacktrace.print_stacktrace(ex.backtrace)
      end
      
      
      BetterStacktrace.disable
      begin
        main(10)
      rescue Exception
        puts "--- Catched ---"
        puts ex
        puts ex.backtrace
      end
      

      上述代码的输出如下:

      *** 10 ***
      --- Catched ---
      File not found
      > work/tracing_with_params.rb:10:in `read_byte'
      < work/tracing_with_params.rb:10:in `read_byte'
      > work/tracing_with_params.rb:8:in `read_byte(10)'
      < work/tracing_with_params.rb:14:in `read_input'
      > work/tracing_with_params.rb:13:in `read_input(10)'
      < work/tracing_with_params.rb:18:in `main'
      > work/tracing_with_params.rb:17:in `main(10)'
      < work/tracing_with_params.rb:82:in `<main>'
      *** 10 ***
      --- Catched ---
      File not found
      work/tracing_with_params.rb:10:in `read_byte'
      work/tracing_with_params.rb:14:in `read_input'
      work/tracing_with_params.rb:18:in `main'
      work/tracing_with_params.rb:82:in `<main>'
      

      编辑:

      不记录对类函数的调用。必须修复此问题,以便堆栈跟踪打印功能不会获得无效输出。 此外,我使用 STDERR 作为输出来轻松获得一个或另一个输出。如果你愿意,你可以改变它。

      【讨论】:

        猜你喜欢
        • 2010-09-27
        • 1970-01-01
        • 1970-01-01
        • 2012-05-10
        • 1970-01-01
        • 2023-03-19
        • 1970-01-01
        • 2019-11-09
        • 2016-02-23
        相关资源
        最近更新 更多