Ruby – 在模块/类中共享logging器实例

使用一个Ruby脚本发送到Web并爬取各种服务。 我有一个内部有几个类的模块:

module Crawler class Runner class Options class Engine end 

我想在这些类别中分享一个logging器。 通常情况下,我只是把这个在模块中的常量,并引用它是这样的:

 Crawler::LOGGER.info("Hello, world") 

问题是,我不能创build我的logging器实例,直到我知道输出到哪里。 您可以通过命令行启动爬网程序,并且可以告诉它您希望在开发中运行(日志输出转到STDOUT)或生产(日志输出转到文件crawler.log):

 crawler --environment=production 

我有一个Options ,parsing通过命令行传入的选项。 只有在那一点,我才知道如何用正确的输出位置实例化logging器。

所以,我的问题是:我怎么把我的logging器对象,以便我的所有类都可以访问它?

我可以将我的logging器实例传递给每个创build的类实例的每个new()调用,但是我知道必须有一个更好的Rubyish方法来实现。 我正在想象模块上有一些奇怪的类variables,这些variables与class << self或其他一些魔法共享。 🙂

更多的细节: Runner通过将命令行选项传递给Options类来启动一切,并返回一个具有几个实例variables的对象:

 module Crawler class Runner def initialize(argv) @options = Options.new(argv) # feels like logger initialization should go here # @options.log_output => STDOUT or string (log file name) # @options.log_level => Logger::DEBUG or Logger::INFO @engine = Engine.new() end def run @engine.go end end end runner = Runner.new(ARGV) runner.run 

我需要Engine的代码能够访问logging器对象(以及更多在Engine中初始化的类)。 帮帮我!

所有这一切都可以避免,只要dynamic地改变一个已经实例化的logging器的输出位置(类似于你如何改变日志级别)。 我将它实例化为STDOUT,然后转换为文件,如果我在生产。 我在某处看到了一个关于改变Ruby的$ stdout全局variables的build议,这个variables会将输出redirect到除STDOUT以外的其他地方,但是这看起来很不起眼。

谢谢!

通过你所devise的devise,最简单的解决scheme就是给Crawler一个返回模块伊娃的模块方法。

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

或者,如果你想要的话,你可以使用“ class <<self魔法”

 module Crawler class <<self attr_accessor :logger end end 

它完全一样的东西。

我喜欢在我的类中使用logger方法,但是我不喜欢在所有初始化器中使用@logger = Logging.logger 。 通常,我这样做:

 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 

然后,在你的课堂上:

 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 

由于Logging#logger方法可以访问模块混入的实例,因此扩展日志logging模块以logging带有日志消息的类名是微不足道的:

 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 

你的Widget现在用它的类名logging消息,并且不需要改变一点:)

正如Zenagray所指出的,从雅各布的答案中排除了采用阶级方法。 一小部分解决了这个问题:

 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 end end 

预期的用途是通过“包括”:

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

生产:

 D, [2014-05-06T22:27:33.991454 #2735] DEBUG -- : grrr 70319381806200 D, [2014-05-06T22:27:33.991531 #2735] DEBUG -- : chirp 70319381806200 D, [2014-05-06T22:27:33.991562 #2735] DEBUG -- : grrr 70319381806200 D, [2014-05-06T22:27:33.991588 #2735] DEBUG -- : chirp 70319381806200 

注意logging器的id在所有四种情况下是相同的。 如果你想为每个类使用不同的实例,那么不要使用Logging.logger ,而应该使用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 end end 

现在生成同样的程序:

 D, [2014-05-06T22:36:07.709645 #2822] DEBUG -- : grrr 70350390296120 D, [2014-05-06T22:36:07.709723 #2822] DEBUG -- : chirp 70350390296120 D, [2014-05-06T22:36:07.709763 #2822] DEBUG -- : grrr 70350390295100 D, [2014-05-06T22:36:07.709791 #2822] DEBUG -- : chirp 70350390295100 

请注意,前两个ID是相同的,但不同于第二个两个ID显示我们有两个实例 – 每个类一个。

这可能是一些奇怪的Ruby魔法,可以让你避免它,但有一个相当简单的解决scheme,不需要怪异的。 只需将logging器放入模块并直接访问它,并设置一个机制。 如果你想冷静一点,定义一个“懒惰的logging器”,让一个标志来说明它是否有一个logging器,或者静静地放下消息,直到logging器被设置,在logging器被logging之前抛出exception设置日志消息,或将日志消息添加到列表中,以便在logging器定义后logging。

一小段代码来演示这是如何工作的。 我只是创build一个新的基本对象,以便我可以观察到object_id在整个调用中保持不变:

 module M class << self attr_accessor :logger end @logger = nil class C def initialize puts "C.initialize, before setting M.logger: #{M.logger.object_id}" M.logger = Object.new puts "C.initialize, after setting M.logger: #{M.logger.object_id}" @base = D.new end end class D def initialize puts "D.initialize M.logger: #{M.logger.object_id}" end end end puts "M.logger (before C.new): #{M.logger.object_id}" engine = M::C.new puts "M.logger (after C.new): #{M.logger.object_id}" 

这个代码的输出看起来像(一个object_id为4意味着nil ):

 M.logger (before C.new): 4 C.initialize, before setting M.logger: 4 C.initialize, after setting M.logger: 59360 D.initialize M.logger: 59360 M.logger (after C.new): 59360 

谢谢你的帮助!

如何将logging器包装成单件,然后使用MyLogger.instance访问它

受此线程启发,我创build了easy_logging gem。

它结合了所有讨论的function,如:

  • 用一个简短的自描述命令在任何地方添加日志loggingfunction
  • logging器可以在类和实例方法中使用
  • logging器是特定于类,并包含类名称

安装:

 gem install 'easy_logging 

用法:

 require 'easy_logging' class YourClass include EasyLogging def do_something # ... logger.info 'something happened' end end class YourOtherClass include EasyLogging def self.do_something # ... logger.info 'something happened' end end YourClass.new.do_something YourOtherClass.do_something 

产量

 I, [2017-06-03T21:59:25.160686 #5900] INFO -- YourClass: something happened I, [2017-06-03T21:59:25.160686 #5900] INFO -- YourOtherClass: something happened 

有关GitHub的更多详细信息。

根据你的评论

所有这一切都可以避免,只要dynamic地改变一个已经实例化的logging器的输出位置(类似于你如何改变日志级别)。

如果您不限于默认logging器,则可以使用其他logginggem。

以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 

在prod模式下,日志logging数据存储在一个文件中(附加到现有文件中,但有选项可用来创build新的日志文件或实现滚动日志文件)。

结果:

  INFO main: Created instance for Crawler::Runner 

如果你使用log4r(a)的inheritance机制,你可以为每个类定义一个logging器(或者在下面的例子中为每个实例)并共享输出器。

这个例子:

 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) 

结果:

  INFO Runner 1: Created instance for Crawler::Runner with id 1 INFO Runner 2: Created instance for Crawler::Runner with id 2 

(a)A A::B这样的logging器名称的名字是B ,是logging器A的名字。 据我所知这不是对象inheritance。

这种方法的一个优点是:如果你想为每个类使用一个logging器,你只需要改变logging器的名称。

虽然是一个老问题,但我认为值得logging一个不同的方法。

build立在雅各布的答案上,我会build议一个模块,你可以在需要的时候join。

我的版本是这样的:

 # saved into lib/my_log.rb require 'logger' module MyLog def self.logger if @logger.nil? @logger = Logger.new( STDERR) @logger.datetime_format = "%H:%M:%S " end @logger end def self.logger=( logger) @logger = logger end levels = %w(debug info warn error fatal) levels.each do |level| define_method( "#{level.to_sym}") do |msg| self.logger.send( level, msg) end end end include MyLog 

我把它保存到一个方便的模块库中,我会这样使用它:

 #! /usr/bin/env ruby # require_relative '../lib/my_log.rb' MyLog.debug "hi" # => D, [19:19:32 #31112] DEBUG -- : hi MyLog.warn "ho" # => W, [19:20:14 #31112] WARN -- : ho MyLog.logger.level = Logger::INFO MyLog.logger = Logger.new( 'logfile.log') MyLog.debug 'huh' # => no output, sent to logfile.log instead 

我发现这比迄今为止我看过的其他选项更容易和更多样化,所以我希望它能帮助你。