Ruby提取调试打印逻辑及实现自动调用的技术问询
Ruby 调试日志复用与自动调用方案
问题背景
为了调试方便,每个方法里都写了打印命名空间和参数的逻辑,示例代码如下:
module Test def self.foo(arg1, arg2) args = method(__method__).parameters.to_h { |p| [p.last, binding.local_variable_get(p.last.to_s)] } puts "#{Module.nesting.first}.#{__method__}: #{args}" end def self.bar(arg1, arg2) args = method(__method__).parameters.to_h { |p| [p.last, binding.local_variable_get(p.last.to_s)] } puts "#{Module.nesting.first}.#{__method__}: #{args}" end end
现在每个函数都重复写了这两行逻辑,有两个问题需要解决:
- 怎么把重复逻辑提取成
print_function_info()函数,让foo()、bar()这些方法都能调用,方便后续单点修改? - 能不能不用在每个函数里显式调用
print_function_info(),让Ruby自动在函数执行时调用它?
问题解答
1. 提取重复逻辑为可复用函数
可以把打印逻辑封装成模块方法,通过传入当前方法的binding来获取上下文信息,这样任何方法都能调用:
module Test def self.print_function_info(bind) method_name = bind.local_variable_get(:__method__) current_method = bind.receiver.method(method_name) args = current_method.parameters.to_h { |p| [p.last, bind.local_variable_get(p.last.to_s)] } puts "#{Module.nesting.first}.#{method_name}: #{args}" end def self.foo(arg1, arg2) print_function_info(binding) # 这里写原有的业务逻辑 end def self.bar(arg1, arg2) print_function_info(binding) # 这里写原有的业务逻辑 end end
关键是用binding传递当前方法的上下文,这样封装后的函数就能拿到方法名、参数这些信息,以后要改打印格式,只需要改print_function_info这一个地方就行。
2. 自动调用打印逻辑(不用显式调用)
有两种常见方案,都是用Ruby的元编程特性实现:
方案一:用Module#prepend注入前置逻辑
定义一个带钩子的模块,把它前置到目标模块的类方法(或实例方法)集合里,这样方法执行前会自动跑打印逻辑:
module DebugPrinter def method_missing(name, *args, &block) # 先打印方法信息 method = self.method(name) args_hash = method.parameters.each_with_index.to_h do |param, idx| [param.last, args[idx]] end puts "#{self.class.name}.#{name}: #{args_hash}" # 再执行原方法 super end def respond_to_missing?(name, include_private = false) super || self.methods.include?(name) end end # 给Test模块的类方法注入调试逻辑 module Test class << self prepend DebugPrinter end def self.foo(arg1, arg2) # 原业务逻辑,不用加调用语句 end def self.bar(arg1, arg2) # 原业务逻辑,不用加调用语句 end end
这种方式完全不用修改原有方法代码,直接通过元编程注入,适合给已有模块批量加调试。
方案二:用TracePoint全局追踪
如果需要全局范围内监控所有方法调用,可以用Ruby内置的TracePoint API:
trace = TracePoint.new(:call) do |tp| # 过滤掉系统内置方法,避免打印太多冗余信息 next if tp.defined_class.name.start_with?('RubyVM', 'Kernel', 'Object') method = tp.defined_class.instance_method(tp.method_id) rescue nil next unless method # 从binding里获取参数值 args = method.parameters.to_h { |p| [p.last, tp.binding.local_variable_get(p.last.to_s)] } puts "#{tp.defined_class}.#{tp.method_id}: #{args}" end # 开启追踪 trace.enable # 调用Test的方法会自动打印信息 Test.foo(1, 2) Test.bar('hello', 'world') # 不需要时关闭追踪 trace.disable
TracePoint能监听Ruby解释器的各种事件,这里监听的是:call事件,方法一被调用就会触发打印逻辑,适合全局调试场景。
内容的提问来源于stack exchange,提问作者Mr.
相关产品推荐
相关产品推荐

