Building a Distributed Logger with Distribunaut
So now that you have a good understanding of how Distribunaut works, and what it does under the covers, let's try to create a distributed logger and see how it goes. To create our distributed logger, we want to create a RemoteLogger class. Here's what that would look like:
require 'rubygems' require 'distribunaut' require 'logger' configatron.distribunaut.app_name = :remote_logger LOGGER = ::Logger.new(STDOUT) class RemoteLogger include Distribunaut::Distributable class << self def new LOGGER end [:debug, :info, :warn, :error, :fatal].each do |meth| define_method(meth) do |*args| LOGGER.send(meth, *args) end end end end DRb.thread.join
Although this looks a lot more intimidating than our HelloWorld class, it really isn't. The extra code comes from making it a bit easier to access the underlying Ruby Logger class we want to wrap. We could have just harnessed the incredible power of Ruby and opened up the Logger class and included the Distribunaut::Distributable module directly into it, but that is generally not considered good practice. Besides, this way lets us talk about a few things we couldn't talk about otherwise. Let's look at it in a bit more depth; you'll see it isn't that complex.
After we require the correct classes and define our application name (this time we are calling it :remote_logger), we create a constant called LOGGER to act as a holder for our Logger instance. We want only one instance of the Logger class. That is why we assign it to the global constant—so that we can access it throughout the rest of our code.
After we have included the Distribunaut::Distributable module into our RemoteLogger class, we then add a few methods for convenience. The first of these methods is a class-level override of the new method. We do this so that when our clients try to create a new instance of the RemoteLogger class, they are actually getting the wrapped Logger class instead. Next we generate the five standard logging methods on Logger, putting them at the class level of the RemoteLogger class. These methods simply proxy the methods onto the single instance of our Logger class that we have stored in our LOGGER constant. We do this so that our clients can call these methods at the class level of RemoteLogger without having to create a new instance of it. This is easier to demonstrate in the client code.
With all of that out of the way, let's see what our client code would look like:
require 'rubygems' require 'distribunaut' logger = Distribunaut::Distributed::RemoteLogger.new logger.debug("Hello, World!") Distribunaut::Distributed::RemoteLogger.error("oops!")
In this client we first create a new "instance" of the RemoteLogger class. I put "instance" in quotes for a reason. Remember that we don't actually get a new instance of the RemoteLogger class. Instead, we simply get back a reference to the global instance of the Logger class we set up earlier.
As soon as we have the RemoteLogger, we can call the standard logging methods, such as debug. We should see our message printed to the server's screen, not the client's. After we call the debug method, we call the class method error on the RemoteLogger class and pass it the message "oops!".
If we were to run all of this, we would get the following:
Hello, World! oops!
As you can see, creating a new distributed logger with Distribunaut is actually quite easy. We could have simplified the code by not giving class-level convenience methods for the common logging methods. But it was only a few more lines of code, and it could make the end user's life a little easier.