我怎样才能有rubylogging器日志输出到标准输出以及文件?

有点像logging器中的T恤function。

您可以编写一个将写入多个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调用puts MultiIO对象时,它都将写入STDOUT和您的日志文件。

编辑:我继续前进,并找出接口的其余部分。 日志设备必须响应writecloseputs )。 只要MultiIO对这些应用MultiIO响应,并将它们代理到真正的IO对象,就可以工作。

大卫的解决scheme非常好。 我已经根据他的代码为多个目标做了一个通用的委托类。

 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) 

如果你在Rails 3或4,正如这篇博客文章指出的那样, Rails 4内置了这个function 。 所以你可以这样做:

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

或者,如果您使用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)) 

您还可以将多个设备loggingfunction直接添加到logging器中:

 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') 

这是另一个实现,由@ jonas054的答案启发。

这使用与Delegator类似的模式。 这样,您不必列出所有要委派的方法,因为它将委托在任何目标对象中定义的所有方法:

 class Tee < DelegateToAllClass(IO) end $stdout = Tee.new(STDOUT, File.open("#{__FILE__}.log", "a")) 

你也应该可以使用Logger。

从这里可以使用delegate_to_all.rb: https : //gist.github.com/TylerRick/4990898

对于那些喜欢它的人来说简单:

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

资源

或者在logging器格式化程序中打印消息:

 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 

我实际上使用这种技术来打印到日志文件,云logging器服务(logentries),如果它是开发环境 – 也打印到STDOUT。

虽然我非常喜欢其他的build议,但是我发现我有同样的问题,但是希望能够为STDERR和文件提供不同的日志logging级别(就像我可以使用像NLog这样的更大的日志logging框架)。 我结束了一个路由策略,在logging器级而不是IO级复用,这样每个logging器就可以在独立的日志级别上运行:

 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 ) 

上面的答案是很好的,但是每个新的委托会污染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方法。 现在,请考虑我们何时创build另一个代表:

 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作出了响应,但是由于第一个代表的作用,它也会作出响应。 即使现在,由于方法的污染,现在对size做出反应。

与匿名类解决scheme相比,一切都如预期的那样:

 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 

你是否限于标准的logging器?

如果没有,你可以使用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 

一个好处:你也可以为标准输出和文件定义不同的日志级别。

我已经写了一个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上find代码: teerb

快速和肮脏(参考: https : //coderwall.com/p/y_b3ra/log-to-stdout-and-a-file-at-the-same-time )

 require 'logger' ll=Logger.new('| tee script.log') ll.info('test') 

我也有同样的想法:“将所有方法委托给子元素”,而其他人已经探索过了,但是为每个方法返回最后一次调用方法的返回值。 如果我没有,它打破了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" 

还有一种方法。 如果您使用标记日志logging,并且需要另一个日志文件中的标记,则可以这样做

 # 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)) 

在此之后,您将在备用logging器中获得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 

希望能帮助别人。

还有一个选项;-)

 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 ...') 

我喜欢MultiIO方法。 它与Ruby Logger运行良好。 如果使用纯IO,它将停止工作,因为它缺less一些IO对象应该具有的方法。 之前提到的pipe道 : 我怎样才能有rubylogging器日志输出到标准输出以及文件? 。 这是最适合我的。

 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') 

注意我知道这不直接回答这个问题,但是它是强相关的。 每当我search输出到多个IO,我碰到这个线程。所以,我希望你也觉得这个有用。

我认为你的STDOUT被用于提出关键的运行时信息和错误。

所以我用

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

loggingdebugging和定期logging,然后写几个

  puts "doing stuff..." 

我需要查看我的脚本运行的STDOUT信息!

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