【发布时间】:2011-09-18 10:13:47
【问题描述】:
类似于记录器中的 tee 功能。
【问题讨论】:
-
在文件对我有用之前添加
| tee,所以Logger.new("| tee test.log")。 注意管道。这是来自coderwall.com/p/y_b3ra/…的提示 -
@mjwatts 使用
tee --append test.log防止覆盖。
类似于记录器中的 tee 功能。
【问题讨论】:
| tee,所以Logger.new("| tee test.log")。 注意管道。这是来自coderwall.com/p/y_b3ra/…的提示
tee --append test.log 防止覆盖。
您可以编写一个伪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 在您的MultiIO 对象上调用puts,它都会同时写入STDOUT 和您的日志文件。
编辑:我继续弄清楚界面的其余部分。日志设备必须响应write 和close(而不是puts)。只要MultiIO 响应这些并将它们代理到真正的 IO 对象,这应该可以工作。
【讨论】:
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
@targets.each(&:close) 已贬值。
@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,正如this blog post 指出的那样,Rails 4 has this functionality built in。所以你可以这样做:
# 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))
【讨论】:
extend 任何ActiveSupport::Logger 实例,如上所示。
config.logger.extend() 时有些奇怪。相反,我在我的环境中将config.logger 设置为STDOUT,然后在不同的初始化程序中扩展了记录器。
对于喜欢简单的人:
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
我实际上是在使用这种技术打印到日志文件、云记录器服务 (logentries),如果它是开发环境 - 也打印到 STDOUT。
【讨论】:
"| tee test.log" 将覆盖旧输出,可能是"| tee -a test.log"
No such file or directory @ rb_sysopen
您还可以将多个设备的日志记录功能直接添加到 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')
【讨论】:
虽然我非常喜欢其他建议,但我发现我遇到了同样的问题,但希望能够为 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)
【讨论】:
MultiLogger 非常合适。感谢分享!
这是另一个实现,灵感来自 @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
【讨论】:
又快又脏(参考: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')
【讨论】:
@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
【讨论】:
我编写了一个小 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
【讨论】:
您是否仅限于标准记录器?
如果没有,你可以使用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
一个优点:您还可以为标准输出和文件定义不同的日志级别。
【讨论】:
我采用了其他人已经探索过的“将所有方法委托给子元素”的相同想法,但我为每个人返回了最后一次调用该方法的返回值。
如果我不这样做,它会破坏 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"
【讨论】:
另一种方式。 如果您正在使用标记日志记录并且还需要另一个日志文件中的标记,您可以这样做
# 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(...) 一起使用。
如果您可以使用ActiveSupport,那么我强烈建议您查看ActiveSupport::Logger.broadcast,这是一种向记录器添加额外日志目标的绝佳且非常简洁的方法。
事实上,如果您使用的是 Rails 4+(截至this commit),您不需要做任何事情来获得所需的行为 - 至少如果您使用的是rails console。每当您使用 rails console 时,Rails automatically 都会扩展 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
由于一些未知且不幸的原因,此方法为 undocumented,但您可以参考 source code 或博客文章了解其工作原理或查看示例。
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"
…
【讨论】:
还有一个选项 ;-)
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。如果你使用 pure IO 它会停止工作,因为它缺少一些 IO 对象应该有的方法。 管道在此之前提到过:How can I have ruby logger log output to stdout as well as file?。以下是最适合我的方法。
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 的输出时,我都会遇到这个线程。所以,我希望你也觉得这很有用。
【讨论】:
这是@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
不需要外部类包装器,它具有与他相同的所有优点。它是一个有用的实用程序,可以放在单独的 ruby 文件中。
使用它作为单行来生成委托实例,如下所示:
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")
【讨论】:
【讨论】:
我最近也有这个需求,所以我实现了一个库来做这个。我刚刚发现了这个 StackOverflow 问题,因此我将其发布给任何需要它的人:https://github.com/agis/multi_io。
与此处提到的其他解决方案相比,这努力成为自己的IO 对象,因此它可以用作其他常规 IO 对象(文件、套接字等)的直接替代品
也就是说,我还没有实现所有标准 IO 方法,但那些遵循 IO 语义的方法(例如,#write 返回写入所有底层 IO 目标的字节数之和)。
【讨论】:
您可以继承Logger 并覆盖write 方法:
class LoggerWithStdout < Logger
def initialize(*)
super
def @logdev.write(msg)
super
puts msg
end
end
end
logger = LoggerWithStdout.new('path/to/log_file.log')
【讨论】:
我认为您的 STDOUT 用于重要的运行时信息和引发的错误。
所以我用
$log = Logger.new('process.log', 'daily')
去log debug和定期记录,然后写了几个
puts "doing stuff..."
我需要在哪里查看我的脚本正在运行的 STDOUT 信息!
呸,只是我的 10 美分 :-)
【讨论】: