log4r is currently broken in MacRuby, and I was desperate for a log4j-ish logger in the MacRuby app I’m working on, so I put together this very basic hierarchical logger based on the standard ruby logger. I’m posting it in case anybody else has the same problem with MacRuby, or wants something that is a little simpler than log4j to configure.

require 'logger'
require 'forwardable'

class LogGroup < Logger
  extend Forwardable

  def_delegators :@child_loggers, :[]=, :has_key?

  attr_accessor :child_loggers

  def initialize(logdev, shift_age = 0, shift_size = 1048576)
    super
    @child_loggers = {}
  end

  def self.init_with_config(config)
    logdev = config['device'] || STDOUT
    shift_age = config['shift_age'] || 0
    shift_size = config['shift_size'] || 1048576
    root = LogGroup.new(logdev, shift_age, shift_size).with_name('root')

    config['loggers'].each do |name, level|
      name_parts = name.split('.')
      log_group = root
      name_parts.each do |name_part|
        unless log_group.has_key?(name_part)
          log_group[name_part] = LogGroup.new(logdev, shift_age, shift_size).with_name("#{log_group.progname}.#{name_part}")
        end
        log_group = log_group[name_part]
      end
      log_group.level =  Logger::Severity.const_get(level.upcase.to_sym)
    end
    root
  end

  def with_name(name)
    @progname = name
    self
  end

  def inspect
    "#{@progname}(#{@level}) -- #{@child_loggers.inspect}"
  end

  def [](dotpath)
    logger = self
    name_parts = dotpath.split('.')
    while (name = name_parts.shift) && logger.has_key?(name)
      logger = logger.child_loggers[name]
    end
    logger
  end
end

To use it, create a config file like so:

device: logfile
loggers:
  cocoa.document:           debug
  cocoa.codeview:           warn
  cocoa.diagramview:        debug

And use it like this:

require "yaml"
require "log_group"
log_cfg = YAML.load_file("log_config.yaml")

$log = LogGroup.init_with_config(log_cfg)

$log['cocoa'].debug("this is a debug message from cocoa")
$log['cocoa.codeview'].debug("this is a debug message from cocoa.codeview")
$log['cocoa.diagramview'].debug("this is a debug message from cocoa.diagramview")

This will output to logfile:

  # Logfile created on 2009-12-19 02:42:05 -0800 by logger.rb/20321
  D, [2009-12-19T02:42:05.549777 #49807] DEBUG -- root.cocoa: this is a debug message from cocoa
  D, [2009-12-19T02:42:05.586840 #49807] DEBUG -- root.cocoa.diagramview: this is a debug message from cocoa.diagramview