回答:
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オブジェクトにプロキシする限り、これは機能するはずです。
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を使用している場合、このブログ投稿が指摘するように、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))
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
または、ロガーフォーマッタでメッセージを印刷します。
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に出力します。
"| tee test.log"
古い出力を上書きしますが、"| tee -a test.log"
代わりに可能性があります
他の提案は非常に気に入っていますが、同じ問題があることがわかりましたが、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
よう@dszは素晴らしいフィット感で説明します。共有してくれてありがとう!
複数のデバイスロギング機能をロガーに直接追加することもできます。
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"))
ロガーでもこれを使用できるはずです。
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
標準のロガーに制限されていますか?
そうでない場合は、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
1つの利点:stdoutと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を最後に配置する必要があります。これは、出力されるはずの色が2つしかないためです。しかし、その後、ファイルに色を出力します。
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
それが誰かを助けることを願っています。
ActiveSupport::Logger
あなただけ使用する必要がある-これで箱から出して動作しますRails.logger.extend
とActiveSupport::Logger.broadcast(...)
。
もう1つのオプション;-)
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オブジェクトが持つことが期待されるいくつかのメソッドがないため、動作が停止します。パイプはここで前に言及されました:ファイルだけでなく標準出力へのルビーロガーログ出力をどのように持つことができますか?。これが私にとって最も効果的な方法です。
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")
の使用ActiveSupport
に問題がない場合は、チェックアウトすることを強くお勧めしますActiveSupport::Logger.broadcast
。これは、ログの宛先をロガーに追加するための優れた非常に簡潔な方法です。
実際、Rails 4+を使用している場合(このcommit以降)、少なくともを使用している場合は、目的の動作を得るために何もする必要はありませんrails console
。を使用するとrails console
、Railsは自動的に拡張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
いくつかの未知の不幸な理由のために、この方法は文書化されていませんが、ソースコードまたはブログの投稿を参照して、それがどのように機能するかを確認したり、例を参照したりできます。
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"
…
私も最近この必要性があるので、これを行うライブラリを実装しました。私はこのStackOverflowの質問を見つけたので、それを必要とするすべての人のために公開します:https : //github.com/agis/multi_io。
ここで説明した他のソリューションと比較して、これIO
は独自のオブジェクトになるように努力しているため、他の通常のIOオブジェクト(ファイル、ソケットなど)のドロップイン代替として使用できます。
そうは言っても、私はまだすべての標準IOメソッドを実装していませんが、それらはIOセマンティクスに従ってい#write
ます(たとえば、基になるすべてのIOターゲットに書き込まれたバイト数の合計を返します)。
| tee
ファイルが私のために働く前に追加するので、Logger.new("| tee test.log")
。パイプに注意してください。これは、coderwall.com / p / y_b3ra /…の