I would be inclined to prepend to the class a dynamically-created anonymous module whose instance methods use super
to call an instance method of the class of the same name, after printing a method-entry message and before printing a method-exit message.
Let's begin by creating a class with two instance methods, one passed a block when involked.
class C
def mouse(nbr_mice, impairment)
puts "%d %s mice" % [nbr_mice, impairment]
end
def hubbard(*args)
puts yield args
end
end
C.ancestors
#=> [C, Object, Kernel, BasicObject]
c = C.new
c.mouse(3, 'blind')
# 3 blind mice
c.hubbard('old', 'mother', 'hubbard') { |a| a.map(&:upcase).join(' ') }
# OLD MOTHER HUBBARD
Now we construct a method that creates the anonymous module and prepends it to the class.
def loggem(klass, *methods_to_log)
log_mod = Module.new do
code = methods_to_log.each_with_object('') { |m,str| str <<
"def #{m}(*args); puts \"entering #{m}\"; super; puts \"leaving #{m}\"; end\n" }
class_eval code
end
klass.prepend(log_mod)
end
We are now ready to invoke this method with the arguments equal to the class to which the module is to be prepended and the instance methods of that class that are to be logged.
loggem(C, :mouse, :hubbard)
C.ancestors
#=> [#<Module:0x007fedab9ccf48>, C, Object, Kernel, BasicObject]
c = C.new
c.method(:mouse).owner
#=> #<Module:0x007fedab9ccf48>
c.method(:mouse).super_method
#=> #<Method: Object(C)#mouse>
c.method(:hubbard).owner
#=> #<Module:0x007fedab9ccf48>
c.method(:hubbard).super_method
#=> #<Method: Object(C)#hubbard>
c.mouse(3, 'blind')
# entering mouse
# 3 blind mice
# leaving mouse
c.hubbard('old', 'mother', 'hubbard') { |a| a.map(&:upcase).join(' ') }
# entering hubbard
# OLD MOTHER HUBBARD
#leaving hubbard
See Module::new and Module#prepend.