Working on a little Ruby script that goes out to the web and crawls various services. I\'ve got a module with several classes inside:
module Crawler
class
Based on your comment
All of this could be avoided if you could just dynamically change the output location of an already-instantiated Logger (similar to how you change the log level).
If you are not restricted to the default logger you may use another log-gem.
As an example with log4r:
require 'log4r'
module Crawler
LOGGER = Log4r::Logger.new('mylog')
class Runner
def initialize
LOGGER.info('Created instance for %s' % self.class)
end
end
end
ARGV << 'test' #testcode
#...
case ARGV.first
when 'test'
Crawler::LOGGER.outputters = Log4r::StdoutOutputter.new('stdout')
when 'prod'
Crawler::LOGGER.outputters = Log4r::FileOutputter.new('file', :filename => 'test.log') #append to existing log
end
#...
Crawler::Runner.new
In prod mode the logging data are stored in a file (attached to existing file, but there are options to create new log files or implement rolling log files).
The result:
INFO main: Created instance for Crawler::Runner
If you use the inheritance mechanism of log4r (a), you may define a logger for each class (or in my following example for each instance) and share the outputter.
The example:
require 'log4r'
module Crawler
LOGGER = Log4r::Logger.new('mylog')
class Runner
def initialize(id)
@log = Log4r::Logger.new('%s::%s %s' % [LOGGER.fullname,self.class,id])
@log.info('Created instance for %s with id %s' % [self.class, id])
end
end
end
ARGV << 'test' #testcode
#...
case ARGV.first
when 'test'
Crawler::LOGGER.outputters = Log4r::StdoutOutputter.new('stdout')
when 'prod'
Crawler::LOGGER.outputters = Log4r::FileOutputter.new('file', :filename => 'test.log') #append to existing log
end
#...
Crawler::Runner.new(1)
Crawler::Runner.new(2)
The result:
INFO Runner 1: Created instance for Crawler::Runner with id 1
INFO Runner 2: Created instance for Crawler::Runner with id 2
(a) A logger name like A::B
has the name B
and is a child of the logger with the name A
. As far as I know this is no object inheritance.
One advantage of this approach: If you want to use a single logger for each class, you need only to change the name of the logger.
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)
end
end
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
end
end
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
end
end
Your Widget
now logs messages with its classname, and didn't need to change one bit :)
How about wrapping the logger in a singleton then you could access it using MyLogger.instance