2011-06-20 45 views

回答

106

您可以編寫一個僞IO類,它將寫入多個IO對象。喜歡的東西:

class MultiIO 
    def initialize(*targets) 
    @targets = targets 
    end 

    def write(*args) 
    @targets.each {|t| t.write(*args)} 
    end 

    def close 
    @targets.each(&:close) 
    end 
end 

然後設置爲你的日誌文件:

log_file = File.open("log/debug.log", "a") 
Logger.new MultiIO.new(STDOUT, log_file) 

每次Logger電話putsMultiIO對象,其將同時寫入STDOUT和你的日誌文件。

編輯:我繼續前進,想通了接口的其餘部分。日誌設備必須響應writeclose(而不是puts)。只要MultiIO對這些響應並將它們代理到真正的IO對象,這應該工作。

+0

的提示,如果你看看記錄儀的監視器,你會看到這會弄亂日誌的旋轉。如果log.respond_to?(:write)和log.respond_to ?,則返回false。如果log.respond_to?(:write)和log.respond_to? (:關閉) \t @dev = log else \t @dev = open_logfile(log) \t @dev。sync = true \t @filename = log \t @shift_age = opt [:shift_age] || 7 \t @shift_size = opt [:shift_size] || 1048576 結束 結束' – JeffCharter

+1

注意在Ruby 2.2中,'@ targets.each(&:close)'折舊。 – xis

45

@大衛的解決方案非常好。我已經根據他的代碼爲多個目標製作了一個通用委託類。

require 'logger' 

class MultiDelegator 
    def initialize(*targets) 
    @targets = targets 
    end 

    def self.delegate(*methods) 
    methods.each do |m| 
     define_method(m) do |*args| 
     @targets.map { |t| t.send(m, *args) } 
     end 
    end 
    self 
    end 

    class <<self 
    alias to new 
    end 
end 

log_file = File.open("debug.log", "a") 
log = Logger.new MultiDelegator.delegate(:write, :close).to(STDOUT, log_file) 
+0

您能解釋一下,這是如何更好的,或者這種方法的增強效用是什麼,而不是大衛建議的普通效用。 –

+2

這是關注的分離。 MultiDelegator只知道將呼叫委託給多個目標。記錄設備需要寫入和關閉方法的事實在調用者中實現。這使得MultiDelegator可以在除日誌之外的其他情況下使用。 – jonas054

+0

美麗的解決方案。 – Lykos

10

您還可以添加多個設備的日誌記錄功能,直接進入記錄儀:

require 'logger' 

class Logger 
    # Creates or opens a secondary log file. 
    def attach(name) 
    @logdev.attach(name) 
    end 

    # Closes a secondary log file. 
    def detach(name) 
    @logdev.detach(name) 
    end 

    class LogDevice # :nodoc: 
    attr_reader :devs 

    def attach(log) 
     @devs ||= {} 
     @devs[log] = open_logfile(log) 
    end 

    def detach(log) 
     @devs ||= {} 
     @devs[log].close 
     @devs.delete(log) 
    end 

    alias_method :old_write, :write 
    def write(message) 
     old_write(message) 

     @devs ||= {} 
     @devs.each do |log, dev| 
     dev.write(message) 
     end 
    end 
    end 
end 

例如:

logger = Logger.new(STDOUT) 
logger.warn('This message goes to stdout') 

logger.attach('logfile.txt') 
logger.warn('This message goes both to stdout and logfile.txt') 

logger.detach('logfile.txt') 
logger.warn('This message goes just to stdout') 
9

這裏的另一種實現方式,通過啓發@ jonas054的回答。

這使用類似於Delegator的模式。這樣,您就不必列出所有要委派的方法,因爲它會委派任何目標對象定義的所有方法:

class Tee < DelegateToAllClass(IO) 
end 

$stdout = Tee.new(STDOUT, File.open("#{__FILE__}.log", "a")) 

您應該能夠爲使用與記錄儀好。

delegate_to_all.rb可從這裏:https://gist.github.com/TylerRick/4990898

+2

這似乎是一個超級優雅的方式來解決問題。 –

8

雖然我很喜歡的其他建議,我發現我有同樣的問題,但希望有STDERR和文件不同的日誌記錄級別的能力(比如我可以與更大的日誌框架,如NLog)。我結束了路由策略的記錄水平,而不是在IO級複用,使每個記錄然後可以在獨立的日誌級別操作:

class MultiLogger 
    def initialize(*targets) 
    @targets = targets 
    end 

    %w(log debug info warn error fatal unknown).each do |m| 
    define_method(m) do |*args| 
     @targets.map { |t| t.send(m, *args) } 
    end 
    end 
end 

$stderr_log = Logger.new(STDERR) 
$file_log = Logger.new(File.open('logger.log','a')) 

$stderr_log.level = Logger::INFO 
$file_log.level = Logger::DEBUG 

$log = MultiLogger.new($stderr_log, $file_log) 
0

我去了同樣的想法「委派所有子元素的方法「,其他人已經探索過,但是爲每個人返回最後一次調用方法的返回值。 如果我沒有,它破壞了logger-colors這期待Integer和地圖是返回Array

class MultiIO 
    def self.delegate_all 
    IO.methods.each do |m| 
     define_method(m) do |*args| 
     ret = nil 
     @targets.each { |t| ret = t.send(m, *args) } 
     ret 
     end 
    end 
    end 

    def initialize(*targets) 
    @targets = targets 
    MultiIO.delegate_all 
    end 
end 

這將重新調用每個方法到所有目標,並只返回最後一次調用的返回值。另外,如果你想要顏色,STDOUT或STDERR必須放在最後,因爲它只有兩個顏色應該輸出。但是,它也會將顏色輸出到您的文件。

logger = Logger.new MultiIO.new(File.open("log/test.log", 'w'), STDOUT) 
logger.error "Roses are red" 
logger.unknown "Violets are blue" 
1

我已經寫了一個小RubyGem,讓你做一些這些事情:

# Pipe calls to an instance of Ruby's logger class to $stdout 
require 'teerb' 

log_file = File.open("debug.log", "a") 
logger = Logger.new(TeeRb::IODelegate.new(log_file, STDOUT)) 

logger.warn "warn" 
$stderr.puts "stderr hello" 
puts "stdout hello" 

您可以在github代碼:我認爲你的標準輸出用於teerb

-1

提出關鍵的運行時信息和錯誤。

於是我就用

$log = Logger.new('process.log', 'daily') 

登錄調試和定期日誌記錄,然後寫了幾

puts "doing stuff..." 

,我需要看到STDOUT信息,我的腳本是運行在所有!

呸,只是我的10美分:-)

23

如果你在Rails的3或4,爲this blog post指出,Rails 4 has this functionality built in。所以,你可以這樣做:

# config/environment/production.rb 
file_logger = Logger.new(Rails.root.join("log/alternative-output.log")) 
config.logger.extend(ActiveSupport::Logger.broadcast(file_logger)) 

或者如果你on Rails的3,你可以反向移植它:

# config/initializers/alternative_output_log.rb 

# backported from rails4 
module ActiveSupport 
    class Logger < ::Logger 
    # Broadcasts logs to multiple loggers. Returns a module to be 
    # `extended`'ed into other logger instances. 
    def self.broadcast(logger) 
     Module.new do 
     define_method(:add) do |*args, &block| 
      logger.add(*args, &block) 
      super(*args, &block) 
     end 

     define_method(:<<) do |x| 
      logger << x 
      super(x) 
     end 

     define_method(:close) do 
      logger.close 
      super() 
     end 

     define_method(:progname=) do |name| 
      logger.progname = name 
      super(name) 
     end 

     define_method(:formatter=) do |formatter| 
      logger.formatter = formatter 
      super(formatter) 
     end 

     define_method(:level=) do |level| 
      logger.level = level 
      super(level) 
     end 
     end 
    end 
    end 
end 

file_logger = Logger.new(Rails.root.join("log/alternative-output.log")) 
Rails.logger.extend(ActiveSupport::Logger.broadcast(file_logger)) 
+0

是否適用於導軌或導軌? –

+0

它基於ActiveSupport,所以如果你已經有了這個依賴關係,你可以像上面那樣擴展任何'ActiveSupport :: Logger'實例。 – phillbaker

+0

謝謝,這很有幫助。 – Lucas

0

另一種方式。 如果您正在使用標記記錄和需要標籤的另一個日誌文件以及,你可以做這樣

# backported from rails4 
# config/initializers/active_support_logger.rb 
module ActiveSupport 
class Logger < ::Logger 

# Broadcasts logs to multiple loggers. Returns a module to be 
# `extended`'ed into other logger instances. 
def self.broadcast(logger) 
    Module.new do 
    define_method(:add) do |*args, &block| 
     logger.add(*args, &block) 
     super(*args, &block) 
    end 

    define_method(:<<) do |x| 
     logger << x 
     super(x) 
    end 

    define_method(:close) do 
     logger.close 
     super() 
    end 

    define_method(:progname=) do |name| 
     logger.progname = name 
     super(name) 
    end 

    define_method(:formatter=) do |formatter| 
     logger.formatter = formatter 
     super(formatter) 
    end 

    define_method(:level=) do |level| 
     logger.level = level 
     super(level) 
    end 

    end # Module.new 
end # broadcast 

def initialize(*args) 
    super 
    @formatter = SimpleFormatter.new 
end 

    # Simple formatter which only displays the message. 
    class SimpleFormatter < ::Logger::Formatter 
    # This method is invoked when a log event occurs 
    def call(severity, time, progname, msg) 
    element = caller[4] ? caller[4].split("/").last : "UNDEFINED" 
    "#{Thread.current[:activesupport_tagged_logging_tags]||nil } # {time.to_s(:db)} #{severity} #{element} -- #{String === msg ? msg : msg.inspect}\n" 
    end 
    end 

end # class Logger 
end # module ActiveSupport 

custom_logger = ActiveSupport::Logger.new(Rails.root.join("log/alternative_#{Rails.env}.log")) 
Rails.logger.extend(ActiveSupport::Logger.broadcast(custom_logger)) 

在此之後,你會在替代記錄器獲取的uuid標籤

["fbfea87d1d8cc101a4ff9d12461ae810"] 2015-03-12 16:54:04 INFO logger.rb:28:in `call_app' -- 
["fbfea87d1d8cc101a4ff9d12461ae810"] 2015-03-12 16:54:04 INFO logger.rb:31:in `call_app' -- Started POST "/psp/entrypoint" for 192.168.56.1 at 2015-03-12 16:54:04 +0700 

希望這有助於某人。

9

對於那些誰喜歡簡單:

log = Logger.new("| tee test.log") # note the pipe ('|') 
log.info "hi" # will log to both STDOUT and test.log 

source

或打印在記錄器格式化消息:

log = Logger.new("test.log") 
log.formatter = proc do |severity, datetime, progname, msg| 
    puts msg 
    msg 
end 
log.info "hi" # will log to both STDOUT and test.log 

我實際使用這種技術打印到日誌文件,雲記錄器服務(logentries)以及它是否是開發環境 - 也可以打印到STDOUT。

2

您是否僅限於標準記錄器?

如果不是你可以使用log4r

require 'log4r' 

LOGGER = Log4r::Logger.new('mylog') 
LOGGER.outputters << Log4r::StdoutOutputter.new('stdout') 
LOGGER.outputters << Log4r::FileOutputter.new('file', :filename => 'test.log') #attach to existing log-file 

LOGGER.info('aa') #Writs on STDOUT and sends to file 

一個優點:你也可以定義不同的日誌級別stdout和文件。

3

@ jonas054上面的答案很好,但它污染MultiDelegator類與每個新的代表。如果您多次使用MultiDelegator,它將繼續向課程添加方法,這是不可取的。 (請參閱下面的示例)

以下是相同的實現,但使用匿名類,以便方法不污染委託人類。

class BetterMultiDelegator 

    def self.delegate(*methods) 
    Class.new do 
     def initialize(*targets) 
     @targets = targets 
     end 

     methods.each do |m| 
     define_method(m) do |*args| 
      @targets.map { |t| t.send(m, *args) } 
     end 
     end 

     class <<self 
     alias to new 
     end 
    end # new class 
    end # delegate 

end 

下面是原來實行的方法的污染,與修改後的實施對比的例子:

tee = MultiDelegator.delegate(:write).to(STDOUT) 
tee.respond_to? :write 
# => true 
tee.respond_to? :size 
# => false 

所有高於良好。 tee有一個write方法,但沒有size方法如預期。現在,考慮當我們創建另一個代表:

tee2 = MultiDelegator.delegate(:size).to("bar") 
tee2.respond_to? :size 
# => true 
tee2.respond_to? :write 
# => true !!!!! Bad 
tee.respond_to? :size 
# => true !!!!! Bad 

哦,不,tee2響應size如預期,但它也迴應write因爲第一個委託。由於方法污染,即使tee現在也響應size

此相反的是匿名一流的解決方案,如預期的一切:

see = BetterMultiDelegator.delegate(:write).to(STDOUT) 
see.respond_to? :write 
# => true 
see.respond_to? :size 
# => false 

see2 = BetterMultiDelegator.delegate(:size).to("bar") 
see2.respond_to? :size 
# => true 
see2.respond_to? :write 
# => false 
see.respond_to? :size 
# => false 
0

一個選項可用;-)

require 'logger' 

class MultiDelegator 
    def initialize(*targets) 
    @targets = targets 
    end 

    def method_missing(method_sym, *arguments, &block) 
    @targets.each do |target| 
     target.send(method_sym, *arguments, &block) if target.respond_to?(method_sym) 
    end 
    end 
end 

log = MultiDelegator.new(Logger.new(STDOUT), Logger.new(File.open("debug.log", "a"))) 

log.info('Hello ...') 
0

我喜歡MultiIO的方法。它適用於Ruby Logger。如果您使用純粹的IO它會停止工作,因爲它缺少一些IO對象應具有的方法。 管道之前提到這裏:How can I have ruby logger log output to stdout as well as file?。這裏是最適合我的。

def watch(cmd) 
    output = StringIO.new 
    IO.popen(cmd) do |fd| 
    until fd.eof? 
     bit = fd.getc 
     output << bit 
     $stdout.putc bit 
    end 
    end 
    output.rewind 
    [output.read, $?.success?] 
ensure 
    output.close 
end 

result, success = watch('./my/shell_command as a String') 

注意我知道這並不直接回答這個問題,但它有很大關係。每當我搜索輸出到多個IO時,我都會遇到這個線程。所以,我希望你也發現它也很有用。

相關問題