2009-05-27 56 views
42

使用一个可以发送到Web并爬取各种服务的小Ruby脚本。我有一个内部有几个类的模块:Ruby - 在模块/类中共享记录器实例

module Crawler 
    class Runner 
    class Options 
    class Engine 
end 

我想在这些类中共享一个记录器。一般情况下我只是把这个在一个恒定的模块中引用它,像这样:

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

的问题是,我无法创建我的记录器实例,直到我知道那里的输出是怎么回事。你可以通过命令行启动爬行,在这一点上,你可以告诉它要在发展上运行(日志输出到stdout)或生产(日志输出为一个文件,crawler.log):

crawler --environment=production 

我有一个类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 

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

如果您可以动态更改已经实例化的记录器的输出位置(类似于更改日志级别),所有这些都可以避免。我将它实例化为STDOUT,然后在生产环境中转换为文件。我在某处看到了一个关于更改Ruby的$ stdout全局变量的建议,这个变量会将输出重定向到除STDOUT以外的其他位置,但这看起来很诡异。

谢谢!

回答

21

随着您设计的设计,它看起来像最简单的解决方案是给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 

它做同样的事情。

+0

谢谢查克!这正是我在结束一些测试后最终做的事情! – 2009-05-27 23:14:28

2

这可能是一些奇怪的Ruby魔法,可以让你避免它,但有一个相当简单的解决方案,不需要怪异。只需将记录器放入模块并直接访问它,并使用一种机制来设置它。如果你想冷静一点,定义一个“懒惰的记录器”,让一个标志告诉它是否有一个记录器,并且直到记录器被设置时才会静静地丢弃消息,并在记录器被记录之前引发异常设置或将日志消息添加到列表中,以便在记录器定义后记录日志消息。

2

一小段代码来演示这是如何工作的。我只是创建一个新的基本对象,这样我可以观察到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}" 

这段代码的输出看起来像(4:3的object_id意味着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 

感谢您的帮助!

1

怎么样在一个单包装的记录器,那么你使用MyLogger.instance

+0

除非你打算继承`Logger`,否则请阅读[The Chainsaw Infanticide Logger Manuever](http://groups.google.com/group/comp.lang.ruby/browse_frm/thread/aaec68ab9088e3ef/d0f054886e0bf71c?lnk= gst&q = chainsaw#d0f054886e0bf71c)[原文如此]让Logger成为可能重复使用您的代码的每个人的单身人士。 – Phrogz 2010-11-22 16:46:42

91

我喜欢在我的课可用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方法可访问对象的模块混入,这是微不足道的你的日志模块延伸到记录类名与日志消息:

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现在记录其类名的消息,并没有需要改变一个位:)

+0

将其添加到您的日志记录模块中,现在可以对其进行配置。 `@out = STDOUT(如模块变量)` 并将此作为一个类的方法: `DEF配置(配置) 注销=配置[ '注销'] 如果注销= 'STDOUT' 然后 @ out = logout#应该是一个日志路径,如/tmp/log.txt end end` – sethcall 2012-06-30 03:46:20

+1

除了它不能在类方法(`self.some_method`)中使用,这是非常棒的。如果你使用`extend`来代替它修复问题,但是然后使用logger作为实例方法,你将需要在类名前加上`logger`调用。例如`Widget.logger`或者使用`self.class.logger` 。就我个人而言,我认为在这种情况下`扩展'更有用。 – zanegray 2013-08-07 03:14:15

+0

我测试了更改progname的最新版本,但Logger.new(STDOUT)总是为我提供了smae实例..所以这不起作用;我错过了什么? – Notalifeform 2013-09-04 12:20:34

10

正如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 

注意记录器的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不同,这表明我们有两个实例 - 每个实例一个实例。

0

基于您的评论

所有这一切是可以避免的,如果你可以只动态改变一个已经实例化的记录器的输出位置(类似于你如何更改日志级别)。

如果您不限于默认记录器,则可以使用其他记录宝石。

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模式的记录数据被存储在一个文件中(附加到现有的文件,但也有选项来创建新的日志文件或执行滚动日志文件)。

结果:

INFO main: Created instance for Crawler::Runner 

如果使用log4r的(A)的继承机制,您可以定义为每个类记录器(或在我下面的例子为每个实例)和共享输出器。

的例子:

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::B甲记录器名称的名称为B并与名称A记录器的子级。据我所知这不是对象继承。

这种方法的一个优点是:如果您想为每个类使用单个记录器,则只需更改记录器的名称。

1

受此线程启发,我创建了easy_logging宝石。

它结合了所有的特征讨论,例如:

  • 任何地方添加记录的功能被一个,短, 自描述命令
  • 记录仪工作在两个类和实例方法
  • Logger是特定包括班级名称

安装:

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 

输出上GitHub

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

更多细节。

0

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

建立在雅各布的答案上,我会建议一个模块,您可以在需要时添加。

我的版本是这样的:

# 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 

我觉得这是一个极大的方便,更多功能比我目前看到的其他选项,所以我希望它可以帮助你与你的。