AOP style logging - is this possible in ruby?

In Java, with AOP magic and bytecode manipulation under the covers,
you could do something like this: (psuedocode, in real java it would
be xml or annotations)

For methods that are: public, name matches foo*, members of classes
Bar and Boo, intercept and log by calling myLogger

then myLogger would get the method object, and be able to log the
name, time of call, parameters, etc…

I know in Ruby you can very easily turn methods or blocks into
objects, and even do some things similiar to basic AOP, but can
something like this be done?

Module AOPLogger
log_methods :criteria => “all”, :log => :method_name, :time, :date

Where log_methods is a class method to be implemented in AOPLogger and
then extend’ed into classes where you want method calls intercepted
and logged.

I imagine something like this might be possible by establishing a
proxy object around objects you want mixed in, then tying into
method_missing, though I imagine it would get nasty quick.

Any ideas?

  • Rob

ps - a related, but simpler question - is there a way inside a method
body to get the name of that method reflectively?
ie: def foo
logger.debug("method called is #{method_name})
end
=> “method called is foo”

Rob,

You can “trap” methods as they’re being added to your classes and
rewrite
them. For example:
class Module
def method_added(meth_name)
# don’t muck with stuff added to Module
return if self == Module || meth_name.to_s.match(/^aop/)
# don’t infinitely call ourselves
return if caller[0] =~ /method_added/

# rename the newly added method
self.send(:alias_method,"__aop__#{meth_name}".intern, meth_name)

# get the method to determine its parameters
meth = self.instance_method(meth_name)
params = ''
a = meth.arity
fixed_cnt = a.abs
fixed_cnt -= 1 if a < 0

# build up a list of parameters
(0...fixed_cnt).each {|x| params << "p#{x},"}
params << "*argv" if a < 0
params.gsub!(/,$/, '')

# create the "AOP" method that calls the orignal method
self.module_eval("def #{meth_name}(#{params}) ; puts \"Called

#{meth_name} stack #{caller}" ; aop#{meth_name}(#{params}); end")
end
end

class Foo
def bar(a,b,c)
puts “I’m bar!! a = #{a} b = #{b} c = #{c}”
end
end

1.upto(3) do |x|
Foo.new.bar(1,“loop #{x}”, 4)
end

You’ve got all the wrapping functionality you want. In the method_added
method, you can test for class name, method visibility, etc. But the
above
code shows how to intercept methods as they’re added.

Does this answer your question?

Thanks,

David