I am in the process of learning Ruby and while browsing the web I found TeensyMud which is a simple MUD server written in Ruby. The thing that caught my eye was how they implemented logging with Log4r. This here is a stripped down version of Log.rb found in the TeensyMud repository. But just so you get the idea, I’ll go through the modified version really quick.

First the Log class is created, notice that it includes Singleton and Log4r, this gives it all the functionality. Initialize is really simple, set the global level, create a pattern format, and the outputters.

The one other thing is the creation of loginit to make it easy to create a logger, pass it the name, level, and outputter and presto! you got your self a log! That so far is pretty slick! But they go even further.

class Log
  include Singleton
  include Log4r

  # Load logger configuration
  def initialize
    Logger['global'].level = DEBUG
    fmt = PatternFormatter.new(:pattern => "%d [%5l] (%c) %M",
      :date_pattern => "%y-%m-%d %H:%M:%S")
    StderrOutputter.new('stderr', :level => INFO, :formatter => fmt)
    FileOutputter.new('file', :level => DEBUG, :formatter => fmt,
      :filename => 'logs/trace.log' ,
      :trunc => 'false')
  end

  def loginit(logname, loglevel, logto)
    Logger.new(logname, Log4r.const_get(loglevel)).outputters = logto
    Logger[logname]
  end

end

At the end of the file they add this. At first I was confused but then it clicked! They created a logger function in the Module class. Big deal you say. Why would you want to do that? Well as it turn out the Module class defines what a ruby “module” should do. So by adding this function anything inside a module will have access to this function, which is pretty much everything!

class Module
  def logger(loglevel='DEBUG', logto=['stderr','server'])
    class_eval < <-EOD
    @log = Log.instance.loginit(self.name, "#{loglevel}", #{logto.inspect})
    def log
      self.class.instance_variable_get :@log
    end
    EOD
  end
end

Here’s a quick sample of how it would be used:

require 'Log'

class MyClass
  logger 'DEBUG'

  def initialize
    log.info "Initializing MyClass"
  end

end

Holly cow! How did that happen? Well when you call the logger method in the Module class it creates a new @log variable and a log method in your class. This helps hide all the logging details and makes it part of your class automatically!

I may not understand the complete details just yet, but I do know this is really nice!