Ruby - share logger instance among module/classes Ruby - share logger instance among module/classes ruby ruby

Ruby - share logger instance among module/classes


I like to have a logger method available in my classes, but I don't like sprinkling @logger = Logging.logger in all my initializers. Usually, I do this:

module Logging  # This is the magical bit that gets mixed into your classes  def logger    Logging.logger  end  # Global, memoized, lazy initialized instance of a logger  def self.logger    @logger ||= Logger.new(STDOUT)  endend

Then, in your classes:

class Widget  # Mix in the ability to log stuff ...  include Logging  # ... and proceed to log with impunity:  def discombobulate(whizbang)    logger.warn "About to combobulate the whizbang"    # commence discombobulation  endend

Because the Logging#logger method can access the instance that the module is mixed into, it is trivial to extend your logging module to record the classname with log messages:

module Logging  def logger    @logger ||= Logging.logger_for(self.class.name)  end  # Use a hash class-ivar to cache a unique Logger per class:  @loggers = {}  class << self    def logger_for(classname)      @loggers[classname] ||= configure_logger_for(classname)    end    def configure_logger_for(classname)      logger = Logger.new(STDOUT)      logger.progname = classname      logger    end  endend

Your Widget now logs messages with its classname, and didn't need to change one bit :)


With the design you've laid out, it looks like the easiest solution is to give Crawler a module method that returns a module ivar.

module Crawler  def self.logger    @logger  end  def self.logger=(logger)    @logger = logger  endend

Or you could use "class <<self magic" if you wanted:

module Crawler  class <<self    attr_accessor :logger  endend

It does the exact same thing.


As Zenagray points out, logging from class methods was left out of Jacob's answer. A small addition solves that:

require 'logger'module Logging  class << self    def logger      @logger ||= Logger.new($stdout)    end    def logger=(logger)      @logger = logger    end  end  # Addition  def self.included(base)    class << base      def logger        Logging.logger      end    end  end  def logger    Logging.logger  endend

The intended use is via "include":

class Dog  include Logging  def self.bark    logger.debug "chirp"    puts "#{logger.__id__}"  end  def bark    logger.debug "grrr"    puts "#{logger.__id__}"  endendclass Cat  include Logging  def self.bark    logger.debug "chirp"    puts "#{logger.__id__}"  end  def bark    logger.debug "grrr"    puts "#{logger.__id__}"  endendDog.new.barkDog.barkCat.new.barkCat.bark

Produces:

D, [2014-05-06T22:27:33.991454 #2735] DEBUG -- : grrr70319381806200D, [2014-05-06T22:27:33.991531 #2735] DEBUG -- : chirp70319381806200D, [2014-05-06T22:27:33.991562 #2735] DEBUG -- : grrr70319381806200D, [2014-05-06T22:27:33.991588 #2735] DEBUG -- : chirp70319381806200

Note the id of the logger is the same in all four cases. If you want a different instance for each class, then don't use Logging.logger, rather use self.class.logger:

require 'logger'module Logging  def self.included(base)    class << base      def logger        @logger ||= Logger.new($stdout)      end      def logger=(logger)        @logger = logger      end    end  end  def logger    self.class.logger  endend

The same program now produces:

D, [2014-05-06T22:36:07.709645 #2822] DEBUG -- : grrr70350390296120D, [2014-05-06T22:36:07.709723 #2822] DEBUG -- : chirp70350390296120D, [2014-05-06T22:36:07.709763 #2822] DEBUG -- : grrr70350390295100D, [2014-05-06T22:36:07.709791 #2822] DEBUG -- : chirp70350390295100

Note that the first two id's are the same but are different from the 2nd two ids showing that we have two instances -- one for each class.