2009-05-27 35 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 

我覺得這是一個極大的方便,更多功能比我目前看到的其他選項,所以我希望它可以幫助你與你的。