[ruby] 루비 로거 로그 출력을 파일뿐만 아니라 stdout에 어떻게 할 수 있습니까?

로거의 티 기능과 같은 것.



답변

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 및 로그 파일에 기록됩니다.

편집 : 계속해서 나머지 인터페이스를 알아 냈습니다. 로그 장치는 writeclose(아님 puts)에 응답해야합니다 . 만큼 MultiIO사람들에게 응답과 실제 IO 객체에 대한 프록시를이 작동합니다.


답변

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


답변

이 블로그 게시물에서 지적했듯이 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))

또는 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))


답변

간단한 것을 좋아하는 사람들을 위해 :

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

출처

또는 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

실제로이 기술을 사용하여 로그 파일, 클라우드 로거 서비스 (로그 항목)에 인쇄하고 개발 환경 인 경우 STDOUT에도 인쇄합니다.


답변

다른 제안이 마음에 들지만 동일한 문제가 있지만 STDERR 및 파일에 대해 서로 다른 로깅 수준을 가질 수있는 기능을 원했습니다.

결국 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)


답변

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

예를 들면 :

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