class TraceWrapper
Wraps methods on given classes or modules to output a call/return tree.
Constants
- VERSION
Current version of
TraceWrapper
Public Class Methods
Create a new TraceWrapper
Options:
:output - IO
object to write trace output to (default STDOUT
) :colour - True to use shell colours in output (default nil
will colour if output is a TTY)
# File lib/trace_wrapper.rb, line 35 def initialize(output: $stdout, colour: nil) @output = output @colour = colour @unwrappers = [] @main_process_id = process_id @processes = {} process end
Wraps methods on given receivers
with tracing
options will be passed to .new and wrap
respectively
If a block is given, it will be passed to wrap
# File lib/trace_wrapper.rb, line 18 def wrap(*receivers, **options, &block) # :yields: a_trace_wrapper init_keys = %i[output colour] init_args = options.select { |k, _| init_keys.include?(k) } wrap_args = options.reject { |k, _| init_keys.include?(k) } new(**init_args).wrap(*receivers, **wrap_args, &block) end
Public Instance Methods
Remove any wrappers set by this tracer
# File lib/trace_wrapper.rb, line 88 def unwrap @unwrappers.each(&:call) @unwrappers = [] end
Wraps methods on given receivers
with tracing
Options
:method_type
- Types of methods to wrap (default: :all
). Choices are: :instance
(for instance methods), :self
(for receiver methods, i.e. class/module functions), :all
for both
:visibility
- Lowest method visibility level to wrap (default: :protected
). Choices are: :public, :protected, :private.
If a block is given, the wrappers will be created just around the block and the block's result will be returned. The TraceWrapper
instance will be yielded to the block to allow further wraps to be added.
If no block is given, you should call unwrap after use.
# File lib/trace_wrapper.rb, line 63 def wrap(*receivers, method_type: :all, visibility: :protected) # :yields: a_trace_wrapper unwrappers = [] Array(*receivers).each do |receiver| if %i[all self].include?(method_type) unwrappers += wrap_methods(receiver, visibility: visibility) end if %i[all instance].include?(method_type) unwrappers += wrap_instance_methods(receiver, visibility: visibility) end end if block_given? begin yield(self) ensure unwrappers.each(&:call) end else @unwrappers += unwrappers self end end
Private Instance Methods
# File lib/trace_wrapper.rb, line 216 def decr_indent process[:indent] = [process[:indent] - 1, 0].max end
# File lib/trace_wrapper.rb, line 202 def function(receiver, dot, name) return colour(id, :teal) if main == receiver "#{colour(receiver, :b_green)}#{dot}#{colour(name, :teal)}" end
# File lib/trace_wrapper.rb, line 168 def get_methods(receiver, method_type, visibility) visibilities = %i[public protected private] unless visibilities.include?(visibility) raise "visibility option not recognised: #{visibility.inspect}" end visibilities = visibilities[0..visibilities.find_index(visibility)] visibilities.map do |vis| lister = LIST_METHODS[method_type][vis] receiver.public_send(lister, false) - Object.public_send(lister) end.compact.flatten end
# File lib/trace_wrapper.rb, line 212 def incr_indent process[:indent] += 1 end
# File lib/trace_wrapper.rb, line 220 def indent ' ' * (process[:indent] + 1) end
# File lib/trace_wrapper.rb, line 194 def key_args?(method) method.parameters.any? { |k, _| %i[keyrest key].include?(k) } end
# File lib/trace_wrapper.rb, line 198 def main TOPLEVEL_BINDING.receiver end
# File lib/trace_wrapper.rb, line 224 def process proc_colours = %i[b_purple orange blue red purple cyan yellow b_blue b_red] @processes[process_id.join(':')] ||= { colour: proc_colours[@processes.size], indent: 0 } end
# File lib/trace_wrapper.rb, line 232 def process_id [Process.pid, Thread.current.hash] end
# File lib/trace_wrapper.rb, line 236 def process_label return if process_id == @main_process_id pid, tid = process_id return pid if Thread.current == Thread.main pid = '' if pid == @main_process_id.first "#{pid}:#{tid.to_s[-4..-1]}" end
# File lib/trace_wrapper.rb, line 260 def short_inspect(obj, limit = 20) text = obj.inspect return text if text.length <= limit text[0...limit] + ELLIPSIS + text[-1] end
# File lib/trace_wrapper.rb, line 249 def show_args(*args, **kwargs) return if args.empty? && kwargs.empty? parts = args.map do |v| colour(short_inspect(v), :purple) end parts += kwargs.map do |k, v| "#{k}: #{colour(short_inspect(v), :purple)}" end parts.join(', ') end
# File lib/trace_wrapper.rb, line 244 def show_pid return if process_id == @main_process_id colour("[#{process_label}]", process[:colour]) end
# File lib/trace_wrapper.rb, line 181 def trace_call(receiver, dot, method_name, *args, **kwargs) writeln("#{show_pid}#{function(receiver, dot, method_name)}" \ "(#{show_args(*args, **kwargs)})") incr_indent end
# File lib/trace_wrapper.rb, line 187 def trace_return(receiver, dot, method_name, result) decr_indent writeln("#{show_pid}#{function(receiver, dot, method_name)} " \ "#{colour('return', :yellow)} " \ "#{colour(short_inspect(result), :purple)}") end
Wrap instance methods (called on an instance of the class given) with tracing
# File lib/trace_wrapper.rb, line 108 def wrap_instance_methods(*receivers, visibility: :protected) unwrappers = [] Array(*receivers).each do |receiver| mod, unwrapper = wrapping_module(receiver, :instance, visibility) unwrappers << unwrapper receiver.send(:prepend, mod) end unwrappers end
Wrap standard methods (methods on the object given) with tracing
# File lib/trace_wrapper.rb, line 96 def wrap_methods(*receivers, visibility: :protected) unwrappers = [] Array(*receivers).each do |receiver| mod, unwrapper = wrapping_module(receiver, :self, visibility) unwrappers << unwrapper receiver.singleton_class.send(:prepend, mod) end unwrappers end
# File lib/trace_wrapper.rb, line 118 def wrapping_module(receiver, method_type, visibility) method_names = get_methods(receiver, method_type, visibility) get_method = method_type == :instance ? :instance_method : :method dot = method_type == :instance ? '#' : '.' trace_call = method(:trace_call) trace_return = method(:trace_return) key_args = method(:key_args?) mod = Module.new do method_names.each do |name| wrap_method = if key_args.call(receiver.public_send(get_method, name)) lambda do |*args, **kwargs, &block| trace_call.call(receiver, dot, name, *args, **kwargs) result = super(*args, **kwargs, &block) trace_return.call(receiver, dot, name, result) result end else lambda do |*args, &block| trace_call.call(receiver, dot, name, *args) result = super(*args, &block) trace_return.call(receiver, dot, name, result) result end end define_method(name, wrap_method) end end unwrapper = lambda do method_names.each do |name| mod.send(:remove_method, name) if mod.method_defined?(name) end end [mod, unwrapper] end
# File lib/trace_wrapper.rb, line 208 def writeln(text) @output.write("#{indent}#{text}\n") end