Tracing with a dynamic Proxy, in Ruby
12 May, 2005
Recently, I was writing a (Ruby) script to sync email between two IMAP servers. My unit-tests were all working, but something was going screwy when I plugged in a real server.
I wanted to be able to trace the conversation with the IMAP server (or at least, Ruby's IMAP API), to see what was going on. Initially, I started sprinkling tracing statements throughout my code, until I realised that it was going to be easier to define a simple "tracing proxy", and wrap it around the object I wanted to trace:
imap_handle = TracingProxy.new(imap_handle, $stderr) # ... do stuff with imap_handle ...
It turned out to be straightforward to implement:
class TracingProxy def initialize(obj, dest) @obj = obj @dest = dest end def method_missing(symbol, *args) arglist = args.map { |a| a.inspect }.join(', ') @dest.puts "#{symbol}(#{arglist})" rval = @obj.send(symbol, *args) @dest.puts ">> #{rval.inspect}" rval end end
method_missing is a fallback method invoked when the called method isn't found - it's great for implementing dynamic proxies. There's nothing particularly ground-breaking going on here - this kind of trick is fairly common in Ruby-land.
My point is: implementing a dynamic-proxy for tracing was so easy in Ruby that I actually did it. I could have done something similar in Java, using java.lang.reflect.Proxy, or cglib - but I most likely wouldn't have bothered.
In Ruby, implementing the proxy made my life easier, not harder. Ruby encourages me to produce better designs.
Feedback