루비 로거 로그 출력을 파일뿐만 아니라 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
편집 : 계속해서 나머지 인터페이스를 알아 냈습니다. 로그 장치는 write
및 close
(아님 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))
다른 제안이 마음에 들지만 동일한 문제가 있지만 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)
간단한 것을 좋아하는 사람들을 위해 :
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에도 인쇄합니다.
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
위의 @ 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
표준 로거로 제한됩니까?
그렇지 않은 경우 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에 대해 다른 로그 수준을 정의 할 수도 있습니다.
빠르고 더럽다 (참조 : 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"
다음과 같은 몇 가지 작업을 수행 할 수있는 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
한 가지 더. 태그가 지정된 로깅을 사용 중이고 다른 로그 파일에도 태그가 필요한 경우 다음과 같이 할 수 있습니다.
# 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
누군가에게 도움이되기를 바랍니다.
하나 더 옵션 ;-)
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 를 사용하는 경우 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')
Note I know this doesn't answer the question directly but it is strongly related. Whenever I searched for output to multiple IOs I came across this thread.So, I hope you find this useful too.
This is a simplification of @rado's solution.
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
It has all the same benefits as his without the need of the outer class wrapper. Its a useful utility to have in a separate ruby file.
Use it as a one-liner to generate delegator instances like so:
IO_delegator_instance = delegator(:write, :read).for(STDOUT, STDERR)
IO_delegator_instance.write("blah")
OR use it as a factory like so:
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")
You can use Loog::Tee
object from loog
gem:
require 'loog'
logger = Loog::Tee.new(first, second)
Exactly what you are looking for.
I think your STDOUT is used for critical runtime info and errors raised.
So I use
$log = Logger.new('process.log', 'daily')
to log debug and regular logging, and then wrote a few
puts "doing stuff..."
where I need to see STDOUT information that my scripts were running at all!
Bah, just my 10 cents :-)
'Programing' 카테고리의 다른 글
컨테이너의 로그가있는 로그 파일은 어디에 있습니까? (0) | 2020.09.08 |
---|---|
Java에서 폴더 삭제 (0) | 2020.09.08 |
TCPServer 오류 : 주소가 이미 사용 중입니다. bind (2) (0) | 2020.09.08 |
두 목록의 공통 요소 (0) | 2020.09.08 |
자바 논리 연산자 단락 (0) | 2020.09.08 |