Dav*_*vid 122

您可以编写一个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
Run Code Online (Sandbox Code Playgroud)

然后将其设置为您的日志文件:

log_file = File.open("log/debug.log", "a")
Logger.new MultiIO.new(STDOUT, log_file)
Run Code Online (Sandbox Code Playgroud)

每次Logger调用puts您的MultiIO对象时,它都会写入STDOUT您的日志文件.

编辑:我继续前进并找出了界面的其余部分.日志设备必须响应writeclose(而不是puts).只要MultiIO响应那些并将它们代理到真正的IO对象,这应该有效.

  • 注意在Ruby 2.2中,`@ targets.each(&:close)`是折旧的. (3认同)

jon*_*054 47

@David的解决方案非常好.我根据他的代码为多个目标创建了一个通用委托者类.

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

  • 这是关注点的分离.MultiDelegator只知道将调用委托给多个目标.日志设备需要write和close方法的事实在调用者中实现.这使得MultiDelegator可用于除日志记录之外的其他情况. (4认同)

phi*_*ker 33

如果您正在使用Rails 3或4,正如此博客文章指出的那样,Rails 4内置了此功能.所以你可以这样做:

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

或者,如果您使用的是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))
Run Code Online (Sandbox Code Playgroud)


Igo*_*gor 13

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

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

资源

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

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

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

  • `“ | tee test.log”`将覆盖旧的输出,可能是`“ | tee -a test.log”` (2认同)

dsz*_*dsz 12

虽然我非常喜欢其他建议,但我发现我有同样的问题,但希望能够为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)
Run Code Online (Sandbox Code Playgroud)


Ram*_*lle 11

您还可以将多个设备日志记录功能直接添加到Logger中:

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

例如:

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


Tyl*_*ick 9

这是另一个实现,受@ jonas054的回答启发.

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

class Tee < DelegateToAllClass(IO)
end

$stdout = Tee.new(STDOUT, File.open("#{__FILE__}.log", "a"))
Run Code Online (Sandbox Code Playgroud)

你也应该能够将它与Logger一起使用.

delegate_to_all.rb可从此处获得:https://gist.github.com/TylerRick/4990898

  • 这似乎是一种解决问题的超级优雅方式. (2认同)