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.