Ruby - 在模块/类之间共享记录器实例

Rob*_*ron 48 ruby logging class-design

处理一个小的Ruby脚本,该脚本可以访问Web并抓取各种服务.我有一个包含几个类的模块:

module Crawler
  class Runner
  class Options
  class Engine
end
Run Code Online (Sandbox Code Playgroud)

我想在所有这些类中共享一个记录器.通常我只是将它放在模块中的常量中并像这样引用它:

Crawler::LOGGER.info("Hello, world")
Run Code Online (Sandbox Code Playgroud)

问题是,在我知道输出的位置之前,我无法创建我的记录器实例.您可以通过命令行启动爬网程序,此时您可以告诉它您要在开发中运行(日志输出转到STDOUT)或生产(日志输出转到文件,crawler.log):

crawler --environment=production
Run Code Online (Sandbox Code Playgroud)

我有一个类Options解析通过命令行传入的选项.只有在那时我才知道如何使用正确的输出位置实例化记录器.

所以,我的问题是:我如何/在哪里放置我的记录器对象,以便我的所有类都可以访问它?

我可以将我的记录器实例传递给new()我创建的每个类实例的每个调用,但我知道必须有一个更好的Rubyish方法来实现它.我正在想象与模块共享的一些奇怪的类变量class << self或其他魔法.:)

更多细节:Runner通过将命令行选项传递给Options类来启动所有内容,并获取具有几个实例变量的对象:

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
Run Code Online (Sandbox Code Playgroud)

我需要代码Engine才能访问logger对象(以及更多内部初始化的类Engine).救命!

如果您可以动态更改已实例化的Logger的输出位置(类似于更改日志级别的方式),则可以避免所有这些.我将它实例化为STDOUT,然后如果我正在制作中则转换为文件.我确实看到了一个关于更改Ruby的$ stdout全局变量的建议,这会将输出重定向到除STDOUT之外的某个地方,但这看起来很糟糕.

谢谢!

Jac*_*cob 99

我喜欢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
Run Code Online (Sandbox Code Playgroud)

然后,在你的课程中:

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
Run Code Online (Sandbox Code Playgroud)

因为该Logging#logger方法可以访问模块混合的实例,所以扩展日志记录模块以使用日志消息记录类名是很简单的:

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
Run Code Online (Sandbox Code Playgroud)

Widget现在使用其类名记录消息,并且不需要更改一位:)

  • 这很棒,只是它不能在类方法(`self.some_method`)中使用。如果您使用“extend”,它可以解决问题,但要使用记录器作为实例方法,您需要在“logger”调用前面加上类名。即“Widget.logger”或使用“self.class.logger” 。我个人认为“extend”在这种情况下更有用。 (2认同)
  • 如何将这个结果组合在日志类名(和方法名?)之上,以便为类方法工作? (2认同)

Chu*_*uck 22

根据您已经设计的设计,看起来最简单的解决方案是为Crawler提供一个返回模块ivar的模块方法.

module Crawler
  def self.logger
    @logger
  end
  def self.logger=(logger)
    @logger = logger
  end
end
Run Code Online (Sandbox Code Playgroud)

或者你可以使用" class <<self魔法",如果你想:

module Crawler
  class <<self
    attr_accessor :logger
  end
end
Run Code Online (Sandbox Code Playgroud)

它完全一样.


ped*_*edz 13

正如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
Run Code Online (Sandbox Code Playgroud)

预期用途是通过"包括":

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
Run Code Online (Sandbox Code Playgroud)

生产:

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
Run Code Online (Sandbox Code Playgroud)

请注意,记录器的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
Run Code Online (Sandbox Code Playgroud)

同样的程序现在产生:

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
Run Code Online (Sandbox Code Playgroud)

请注意,前两个id是相同的,但与前两个id不同,表明我们有两个实例 - 每个类一个.