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


답변:


124

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 객체에 대한 프록시를이 작동합니다.


로거의 ctor를 보면 이것이 로그 회전을 엉망으로 만드는 것을 볼 수 있습니다. def initialize(log = nil, opt = {}) @dev = @filename = @shift_age = @shift_size = nil @mutex = LogDeviceMutex.new if log.respond_to?(:write) and log.respond_to?(:close) @dev = log else @dev = open_logfile(log) @dev.sync = true @filename = log @shift_age = opt[:shift_age] || 7 @shift_size = opt[:shift_size] || 1048576 end end
JeffCharter 2014 년

3
Ruby 2.2의 참고 사항 @targets.each(&:close)은 감가 상각됩니다.
xis

로거가 기록한 내용을 업데이트하기 위해 log_file을 얻기 위해 log_file에서 주기적으로 : close를 호출해야한다는 것을 깨달을 때까지 저를 위해 일했습니다 (본질적으로 "저장"). STDOUT은 : close가 그것에 대해 불려지는 것을 좋아하지 않았습니다. 파일 클래스를 제외하고 : close를 건너 뛰는 해킹을 추가했지만 더 우아한 솔루션이 있었으면합니다.
Kim Miller

48

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

당신이 더 나은 또는 무슨 일이 일반보다이 방법의 유틸리티를 강화 데이비드 제안하는 방법을 설명해 주시겠습니까
마니 Sapariya

5
우려의 분리입니다. MultiDelegator는 여러 대상에 대한 호출 위임에 대해서만 알고 있습니다. 로깅 장치에 쓰기 및 닫기 메서드가 필요하다는 사실은 호출자에서 구현됩니다. 이렇게하면 MultiDelegator를 로깅 이외의 상황에서 사용할 수 있습니다.
jonas054

좋은 솔루션입니다. 나는 이것을 사용하여 내 레이크 작업의 출력을 로그 파일로 보내려고했습니다. ( "private method`puts 'called"를받지 않고 $ stdout.puts를 호출 할 수 있기 위해) 풋과 함께 작동하게하려면 몇 가지 메서드를 추가해야했습니다. log_file = File.open ( "tmp / rake.log ","a ") $ stdout = MultiDelegator.delegate (: write, : close, : puts, : print) .to (STDOUT, log_file) 상속 된 Tee 클래스를 생성 할 수 있다면 좋을 것입니다. MultiDelegator, stdlib에서 Delegator 클래스로 할 수있는 것처럼 ...
Tyler Rick

DelegatorToAll이라고 부르는 Delegator와 같은 구현을 생각해 냈습니다. 이렇게하면 위임 클래스 (IO)에 정의 된 모든 메서드를 위임하므로 위임하려는 모든 메서드를 나열 할 필요가 없습니다. class Tee <DelegateToAllClass (IO) end $ stdout = Tee.new (STDOUT , File.open ( "# { FILE } .log", "a")) 자세한 내용은 gist.github.com/TylerRick/4990898 을 참조하세요.
Tyler Rick

1
나는 당신의 솔루션을 정말 좋아하지만 모든 위임이 모든 인스턴스를 새로운 방법으로 오염시키기 때문에 여러 번 사용할 수있는 일반 위임자로서 좋지 않습니다. 이 문제를 해결 하는 다음 답변 ( stackoverflow.com/a/36659911/123376 )을 게시했습니다 . 나는 예제를 게시하면서 두 구현의 차이점을 확인하는 것이 교육적 일 수 있기 때문에 편집보다는 답변을 게시했습니다.
왜 그래

35

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

레일 외부에만 적용됩니까? 아니면 레일에만 적용됩니까?
Ed Sykes

ActiveSupport를 기반으로하므로 이미 해당 종속성이 extend있는 ActiveSupport::Logger경우 위에 표시된대로 모든 인스턴스 를 사용할 수 있습니다 .
phillbaker

감사합니다. 도움이되었습니다.
Lucas

config.logger.extend()내 환경 구성 내부를 사용하는 데 약간의 이상 함이 있었지만 이것이 가장 간단하고 효과적인 대답이라고 생각 합니다. 대신, 설정 config.loggerSTDOUT한 후, 내 환경에서 서로 다른 이니셜 로거를 확장했다.
mattsch

14

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

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에도 인쇄합니다.


2
"| tee test.log"이전 출력을 덮어 씁니다. "| tee -a test.log"대신 있을 수 있습니다.
fangxing

13

다른 제안이 마음에 들지만 동일한 문제가 있지만 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)

1
나는이 솔루션이 (1) 간단하고 (2) 모든 것이 파일에 있다고 가정하는 대신 Logger 클래스를 재사용하도록 권장하기 때문에이 솔루션을 가장 좋아합니다. 제 경우에는 STDOUT에 로그인하고 Graylog에 대한 GELF appender를 사용하고 싶습니다. MultiLogger@dsz가 설명 하는 것과 같은 것을 갖는 것은 매우 적합합니다. 공유해 주셔서 감사합니다!
Eric Kramer

핸들 pseudovariables (세터 / 게터)에 추가 된 섹션
에릭 크레이머

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

예를 들면 :

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

Logger에서도 이것을 사용할 수 있어야합니다.

delegate_to_all.rb는 여기에서 사용할 수 있습니다 : https://gist.github.com/TylerRick/4990898



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 

위의 모든 것이 좋습니다. teewrite방법,하지만 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

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 및 file에 대해 다른 로그 수준을 정의 할 수도 있습니다.


1

다른 사람들이 이미 살펴본 "모든 메서드를 하위 요소에 위임"이라는 동일한 아이디어로 갔지만 각각에 대해 메서드의 마지막 호출의 반환 값을 반환하고 있습니다. 그렇지 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

한 가지 더. 태그가 지정된 로깅을 사용 중이고 다른 로그 파일에도 태그가 필요한 경우 다음과 같이 할 수 있습니다.

# 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

누군가에게 도움이되기를 바랍니다.


간단하고 안정적이며 훌륭하게 작동합니다. 감사! 참고 ActiveSupport::Logger그냥 사용할 필요 -이와 함께 상자 밖으로 작동 Rails.logger.extend으로 ActiveSupport::Logger.broadcast(...).
XtraSimplicity

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 개체가 가질 것으로 예상되는 일부 메서드가 없기 때문에 작동이 중지됩니다. 파이프 는 여기에 앞서 언급되었습니다. 루비 로거 로그 출력을 파일뿐만 아니라 stdout에 어떻게 할 수 있습니까? . 여기에 가장 적합한 것이 있습니다.

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에 대한 출력을 검색 할 때마다이 스레드를 발견 했으므로이 스레드도 유용하다고 생각합니다.


0

이것은 @rado의 솔루션을 단순화 한 것입니다.

def delegator(*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 for new
    end
  end # new class
end # delegate

외부 클래스 래퍼가 필요하지 않은 경우와 동일한 이점이 있습니다. 별도의 루비 파일에있는 유용한 유틸리티입니다.

다음과 같이 위임자 인스턴스를 생성하려면 한 줄로 사용하십시오.

IO_delegator_instance = delegator(:write, :read).for(STDOUT, STDERR)
IO_delegator_instance.write("blah")

또는 다음과 같이 공장으로 사용하십시오.

logger_delegator_class = delegator(:log, :warn, :error)
secret_delegator = logger_delegator_class(main_logger, secret_logger)
secret_delegator.warn("secret")

general_delegator = logger_delegator_class(main_logger, debug_logger, other_logger) 
general_delegator.log("message")

0

gem의 Loog::Tee객체를 사용할 수 있습니다 loog.

require 'loog'
logger = Loog::Tee.new(first, second)

정확히 당신이 찾고있는 것.


0

을 사용 ActiveSupport하는 것이 괜찮다면 ActiveSupport::Logger.broadcast로거에 로그 대상을 추가하는 훌륭하고 간결한 방법 인을 확인하는 것이 좋습니다 .

사실, Rails 4+를 사용하고 있다면 ( 이 커밋 현재 ), 원하는 동작을 얻기 위해 아무것도 할 필요가 없습니다 . 적어도 rails console. 를 사용할 때마다 rails consoleRails는 자동으로 확장 Rails.logger되어 일반적인 파일 대상 ( log/production.log예 :) 과 다음을 모두 출력합니다 STDERR.

    console do |app|
      
      unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDERR, STDOUT)
        console = ActiveSupport::Logger.new(STDERR)
        Rails.logger.extend ActiveSupport::Logger.broadcast console
      end
      ActiveRecord::Base.verbose_query_logs = false
    end

알 수없고 안타깝게도이 방법은 문서화되어 있지 않지만 소스 코드 또는 블로그 게시물을 참조하여 작동 방식을 배우거나 예제를 볼 수 있습니다.

https://www.joshmcarthur.com/til/2018/08/16/logging-to-multiple-destinations-using-activesupport-4.html 에는 또 다른 예가 있습니다.

require "active_support/logger"
console_logger = ActiveSupport::Logger.new(STDOUT)
file_logger = ActiveSupport::Logger.new("my_log.log")
combined_logger = console_logger.extend(ActiveSupport::Logger.broadcast(file_logger))

combined_logger.debug "Debug level"

0

나는 또한 최근에 이러한 필요성이 있으므로 이것을 수행하는 라이브러리를 구현했습니다. 방금이 StackOverflow 질문을 발견 했으므로 필요한 모든 사람을 위해 https://github.com/agis/multi_io를 제공하고 있습니다.

여기에 언급 된 다른 솔루션과 비교할 때 이것은 IO자체 객체 가되기 위해 노력 하므로 다른 일반 IO 객체 (파일, 소켓 등)에 대한 드롭 인 교체로 사용할 수 있습니다.

즉, 아직 모든 표준 IO 메서드를 구현하지는 않았지만 IO 의미 체계를 따르는 메서드는 구현했습니다 (예 : #write모든 기본 IO 대상에 기록 된 바이트 수의 합계를 반환합니다).


-3

STDOUT은 중요한 런타임 정보 및 오류 발생에 사용되는 것 같습니다.

그래서 나는

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

디버그 및 일반 로깅을 기록한 다음 몇 가지

  puts "doing stuff..."

내 스크립트가 전혀 실행되고 있다는 STDOUT 정보를 볼 필요가 있습니다!

Bah, 내 10 센트 :-)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.