Sesuatu seperti fungsi tee di logger.
Sesuatu seperti fungsi tee di logger.
tee --append test.log
untuk mencegah penimpaan.
Jawaban:
Anda dapat menulis IO
kelas semu yang akan menulis ke banyak IO
objek. Sesuatu seperti:
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
Kemudian atur itu sebagai file log Anda:
log_file = File.open("log/debug.log", "a")
Logger.new MultiIO.new(STDOUT, log_file)
Setiap kali Logger
memanggil objek puts
Anda MultiIO
, itu akan menulis ke keduanya STDOUT
dan file log Anda.
Sunting: Saya melanjutkan dan menemukan sisa antarmuka. Perangkat log harus merespons write
dan close
(tidak puts
). Selama MultiIO
menanggapi itu dan memproksinya ke objek IO yang sebenarnya, ini harus berfungsi.
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)
disusutkan.
Solusi @ David sangat bagus. Saya telah membuat kelas delegator generik untuk beberapa target berdasarkan kodenya.
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)
Jika Anda berada di Rails 3 atau 4, seperti yang ditunjukkan oleh posting blog ini , Rails 4 memiliki fungsi bawaan ini . Jadi Anda bisa melakukan:
# config/environment/production.rb
file_logger = Logger.new(Rails.root.join("log/alternative-output.log"))
config.logger.extend(ActiveSupport::Logger.broadcast(file_logger))
Atau jika Anda menggunakan Rails 3, Anda dapat melakukan backport:
# 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
apa pun ActiveSupport::Logger
seperti yang ditunjukkan di atas.
config.logger.extend()
konfigurasi di dalam lingkungan saya. Sebaliknya, saya mengatur config.logger
ke STDOUT
dalam lingkungan saya, kemudian memperpanjang logger di penginisialisasi yang berbeda.
Bagi yang suka sederhana:
log = Logger.new("| tee test.log") # note the pipe ( '|' )
log.info "hi" # will log to both STDOUT and test.log
Atau cetak pesan di pemformat 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
Saya sebenarnya menggunakan teknik ini untuk mencetak ke file log, layanan cloud logger (logentries) dan jika itu lingkungan dev - juga mencetak ke STDOUT.
"| tee test.log"
akan menimpa keluaran lama, mungkin sebagai "| tee -a test.log"
gantinya
Meskipun saya cukup menyukai saran lainnya, saya menemukan bahwa saya memiliki masalah yang sama tetapi menginginkan kemampuan untuk memiliki tingkat logging yang berbeda untuk STDERR dan file.
Saya berakhir dengan strategi perutean yang multipleks di tingkat logger daripada di tingkat IO, sehingga setiap logger kemudian dapat beroperasi di tingkat log independen:
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
suka yang dijelaskan @dsz sangat cocok. Terima kasih telah berbagi!
Anda juga dapat menambahkan fungsionalitas pencatatan beberapa perangkat langsung ke 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
Misalnya:
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')
Berikut implementasi lainnya, terinspirasi oleh jawaban @ jonas054 .
Ini menggunakan pola yang mirip dengan Delegator
. Dengan cara ini Anda tidak perlu membuat daftar semua metode yang ingin Anda delegasikan, karena ini akan mendelegasikan semua metode yang didefinisikan di salah satu objek target:
class Tee < DelegateToAllClass(IO)
end
$stdout = Tee.new(STDOUT, File.open("#{__FILE__}.log", "a"))
Anda harus dapat menggunakan ini dengan Logger juga.
delegate_to_all.rb tersedia dari sini: https://gist.github.com/TylerRick/4990898
Cepat dan kotor (ref: 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')
Jawaban @ jonas054 di atas bagus, tetapi mengotori MultiDelegator
kelas dengan setiap delegasi baru. Jika Anda menggunakan MultiDelegator
beberapa kali, itu akan terus menambahkan metode ke kelas, yang tidak diinginkan. (Lihat di bawah untuk contoh)
Berikut adalah implementasi yang sama, tetapi menggunakan kelas anonim sehingga metode tidak mencemari kelas delegator.
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
Berikut adalah contoh metode pencemaran dengan implementasi asli, berbeda dengan implementasi yang dimodifikasi:
tee = MultiDelegator.delegate(:write).to(STDOUT)
tee.respond_to? :write
# => true
tee.respond_to? :size
# => false
Semuanya bagus di atas. tee
memiliki write
metode, tetapi tidak ada size
metode seperti yang diharapkan. Sekarang, pertimbangkan ketika kita membuat delegasi lain:
tee2 = MultiDelegator.delegate(:size).to("bar")
tee2.respond_to? :size
# => true
tee2.respond_to? :write
# => true !!!!! Bad
tee.respond_to? :size
# => true !!!!! Bad
Oh tidak, tee2
menanggapi size
seperti yang diharapkan, tetapi juga menanggapi write
karena delegasi pertama. Bahkan tee
sekarang menanggapi size
karena metode pencemaran.
Bandingkan ini dengan solusi kelas anonim, semuanya seperti yang diharapkan:
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
Apakah Anda dibatasi untuk logger standar?
Jika tidak, Anda dapat menggunakan 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
Satu keuntungan: Anda juga dapat menentukan tingkat log yang berbeda untuk stdout dan file.
Saya pergi ke ide yang sama tentang "Mendelegasikan semua metode ke sub-elemen" yang sudah dieksplorasi orang lain, tetapi saya mengembalikan untuk masing-masing metode tersebut nilai kembalian dari panggilan terakhir metode tersebut. Jika saya tidak, itu rusak logger-colors
yang mengharapkan Integer
dan peta kembali 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
Ini akan mendelegasikan ulang setiap metode ke semua target, dan hanya mengembalikan nilai kembali dari panggilan terakhir.
Juga, jika Anda ingin warna, STDOUT atau STDERR harus diletakkan terakhir, karena hanya dua warna yang seharusnya menjadi keluaran. Tapi kemudian, itu juga akan menampilkan warna ke file Anda.
logger = Logger.new MultiIO.new(File.open("log/test.log", 'w'), STDOUT)
logger.error "Roses are red"
logger.unknown "Violets are blue"
Saya telah menulis RubyGem kecil yang memungkinkan Anda melakukan beberapa hal ini:
# 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"
Anda dapat menemukan kode di github: teerb
Satu cara lagi. Jika Anda menggunakan logging yang diberi tag dan juga memerlukan tag di file log lain, Anda dapat melakukannya dengan cara ini
# 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))
Setelah ini Anda akan mendapatkan tag uuid di logger alternatif
["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
Harapan yang membantu seseorang.
ActiveSupport::Logger
bekerja di luar kotak dengan ini - Anda hanya perlu menggunakan Rails.logger.extend
dengan ActiveSupport::Logger.broadcast(...)
.
Satu opsi lagi ;-)
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 ...')
Saya suka pendekatan MultiIO . Ini bekerja dengan baik dengan Ruby Logger . Jika Anda menggunakan IO murni, ia berhenti bekerja karena tidak memiliki beberapa metode yang diharapkan dimiliki oleh objek IO. Pipa disebutkan sebelumnya di sini: Bagaimana saya bisa memiliki output log ruby logger ke stdout serta file? . Inilah yang paling cocok untuk saya.
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')
Catatan Saya tahu ini tidak menjawab pertanyaan secara langsung tetapi sangat terkait. Setiap kali saya mencari output ke beberapa IO, saya menemukan utas ini. Jadi, saya harap ini juga berguna bagi Anda.
Ini adalah penyederhanaan dari solusi @ 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
Ini memiliki semua manfaat yang sama seperti miliknya tanpa perlu pembungkus kelas luar. Ini adalah utilitas yang berguna untuk dimiliki dalam file ruby terpisah.
Gunakan sebagai one-liner untuk menghasilkan instance delegator seperti ini:
IO_delegator_instance = delegator(:write, :read).for(STDOUT, STDERR)
IO_delegator_instance.write("blah")
ATAU gunakan sebagai pabrik seperti:
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")
Jika Anda tidak keberatan menggunakannya ActiveSupport
, saya akan sangat menyarankan untuk memeriksa ActiveSupport::Logger.broadcast
, yang merupakan cara yang sangat baik dan ringkas untuk menambahkan tujuan log tambahan ke logger.
Faktanya, jika Anda menggunakan Rails 4+ (seperti dari commit ini ), Anda tidak perlu melakukan apa pun untuk mendapatkan perilaku yang diinginkan - setidaknya jika Anda menggunakan rails console
. Setiap kali Anda menggunakan rails console
, Rails secara otomatis meluas Rails.logger
sedemikian rupa sehingga menghasilkan output ke tujuan file biasanya ( log/production.log
, misalnya) dan 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
Untuk beberapa alasan yang tidak diketahui dan disayangkan, metode ini tidak berdokumen tetapi Anda dapat merujuk ke kode sumber atau posting blog untuk mempelajari cara kerjanya atau melihat contoh.
https://www.joshmcarthur.com/til/2018/08/16/logging-to-multiple-destinations-using-activesupport-4.html memiliki contoh lain:
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"
…
Saya juga memiliki kebutuhan ini baru-baru ini, jadi saya menerapkan perpustakaan yang melakukan ini. Saya baru saja menemukan pertanyaan StackOverflow ini, jadi saya menaruhnya untuk siapa saja yang membutuhkannya: https://github.com/agis/multi_io .
Dibandingkan dengan solusi lain yang disebutkan di sini, ini berusaha untuk menjadi IO
objeknya sendiri, sehingga dapat digunakan sebagai pengganti drop-in untuk objek IO reguler lainnya (file, soket, dll.)
Meskipun demikian, saya belum mengimplementasikan semua metode IO standar, tetapi yang mengikuti semantik IO (misalnya, #write
mengembalikan jumlah dari jumlah byte yang ditulis ke semua target IO yang mendasarinya).
Saya pikir STDOUT Anda digunakan untuk info runtime kritis dan kesalahan yang diangkat.
Jadi saya gunakan
$log = Logger.new('process.log', 'daily')
untuk log debug dan logging biasa, dan kemudian menulis beberapa
puts "doing stuff..."
di mana saya perlu melihat informasi STDOUT bahwa skrip saya berjalan sama sekali!
Bah, hanya 10 sen saya :-)
| tee
sebelum file berhasil untuk saya, jadiLogger.new("| tee test.log")
. Perhatikan pipanya. Ini dari tip di coderwall.com/p/y_b3ra/…