Logger'da bir tee işlevi gibi.
Logger'da bir tee işlevi gibi.
tee --append test.log
önlemek için kullanın .
Yanıtlar:
IO
Birden çok IO
nesneye yazacak sözde bir sınıf yazabilirsiniz . Gibi bir şey:
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
Sonra bunu günlük dosyanız olarak ayarlayın:
log_file = File.open("log/debug.log", "a")
Logger.new MultiIO.new(STDOUT, log_file)
Nesnenizi her Logger
çağırdığınızda puts
, MultiIO
hem STDOUT
günlük dosyanıza hem de günlük dosyanıza yazacaktır .
Düzenleme: Devam ettim ve arayüzün geri kalanını çözdüm. Bir günlük cihazı yanıt vermeli write
ve close
( yanıtlamamalıdır puts
). MultiIO
Bunlara yanıt verdiği ve onları gerçek IO nesnelerine temsil ettiği sürece , bu işe yaramalıdır.
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)
, amortismana tabi tutulmuştur.
@ David'in çözümü çok iyi. Koduna göre birden çok hedef için genel bir temsilci sınıfı oluşturdum.
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 veya 4'teyseniz, bu blog gönderisinin de işaret ettiği gibi, Rails 4 bu işlevselliğe sahiptir . Böylece şunları yapabilirsiniz:
# config/environment/production.rb
file_logger = Logger.new(Rails.root.join("log/alternative-output.log"))
config.logger.extend(ActiveSupport::Logger.broadcast(file_logger))
Veya Rails 3'teyseniz, onu arka plana atabilirsiniz:
# 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
herhangi bir ActiveSupport::Logger
örneği yapabilirsiniz .
config.logger.extend()
İç ortam yapılandırmamı kullanırken bazı tuhaflıklar yaşasa da, bunun en basit ve en etkili cevap olduğunu düşünüyorum . Bunun yerine, set config.logger
için STDOUT
o sırada, çevrede farklı başlatıcılar logger uzatıldı.
Basitten hoşlananlar için:
log = Logger.new("| tee test.log") # note the pipe ( '|' )
log.info "hi" # will log to both STDOUT and test.log
Veya mesajı Logger formatlayıcıda yazdırın:
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
Aslında bu tekniği bir günlük dosyasına, bir bulut günlükçü hizmetine (log girişleri) ve geliştirme ortamı ise - STDOUT'a yazdırmak için kullanıyorum.
"| tee test.log"
eski çıktıların üzerine "| tee -a test.log"
Diğer önerileri oldukça beğenmeme rağmen, aynı sorunu yaşadığımı fark ettim ancak STDERR ve dosya için farklı kayıt seviyelerine sahip olmak istedim.
Sonunda, her bir kaydedicinin bağımsız günlük seviyelerinde çalışabilmesi için, GÇ düzeyi yerine günlükçü düzeyinde çoğullayan bir yönlendirme stratejisi buldum:
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'nin tanımladığı gibi olması mükemmel bir uyumdur. Paylaşım için teşekkürler!
Ayrıca, doğrudan Logger'a birden fazla cihaz günlüğü işlevi ekleyebilirsiniz:
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
Örneğin:
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')
İşte @ jonas054'ün cevabından esinlenen başka bir uygulama .
Bu, benzer bir kalıp kullanır Delegator
. Bu şekilde, herhangi bir hedef nesnede tanımlanan tüm yöntemleri delege edeceğinden, yetkilendirmek istediğiniz tüm yöntemleri listelemeniz gerekmez:
class Tee < DelegateToAllClass(IO)
end
$stdout = Tee.new(STDOUT, File.open("#{__FILE__}.log", "a"))
Bunu Logger ile de kullanabilmelisiniz.
delegate_to_all.rb'ye buradan ulaşılabilir: https://gist.github.com/TylerRick/4990898
Hızlı ve kirli (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')
@ jonas054'ün yukarıdaki cevabı harika, ancak MultiDelegator
her yeni delege ile sınıfı kirletiyor . MultiDelegator
Birkaç kez kullanırsanız , istenmeyen bir durum olan sınıfa yöntemler eklemeye devam edecektir. (Örneğin aşağıya bakınız)
İşte aynı uygulama, ancak anonim sınıflar kullanmak, böylece yöntemler temsilci sınıfını kirletmez.
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
Aşağıda, değiştirilmiş uygulamaya zıt olarak orijinal uygulama ile yöntem kirliliğinin bir örneği verilmiştir:
tee = MultiDelegator.delegate(:write).to(STDOUT)
tee.respond_to? :write
# => true
tee.respond_to? :size
# => false
Yukarıda her şey güzel. tee
bir write
yöntemi var, ancak size
beklendiği gibi bir yöntemi yok . Şimdi, başka bir temsilci oluşturduğumuzda düşünün:
tee2 = MultiDelegator.delegate(:size).to("bar")
tee2.respond_to? :size
# => true
tee2.respond_to? :write
# => true !!!!! Bad
tee.respond_to? :size
# => true !!!!! Bad
Oh hayır, beklendiği gibi tee2
yanıt veriyor size
, ama aynı zamanda write
ilk delege yüzünden de yanıt veriyor . tee
Şimdi bile size
yöntem kirliliği nedeniyle yanıt veriyor .
Bunu anonim sınıf çözümüyle karşılaştırın, her şey beklendiği gibi:
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
Standart kaydedici ile sınırlı mısınız?
Değilse log4r kullanabilirsiniz :
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
Bir avantaj: Standart çıkış ve dosya için farklı günlük düzeyleri de tanımlayabilirsiniz.
Diğer insanların zaten keşfettiği "Tüm yöntemleri alt öğelere devretme" fikrine gittim, ancak her biri için yöntemin son çağrısının dönüş değerini döndürüyorum. Ben yapmadım, o kırdı logger-colors
bir bekliyorduk hangi Integer
ve harita bir dönüyordu 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
Bu, her yöntemi tüm hedeflere yeniden atar ve yalnızca son çağrının dönüş değerini döndürür.
Ayrıca, renk istiyorsanız, STDOUT veya STDERR en son yerleştirilmelidir, çünkü yalnızca iki renk çıktısı olması gerekirdi. Ancak daha sonra, dosyanıza renkler de çıkarır.
logger = Logger.new MultiIO.new(File.open("log/test.log", 'w'), STDOUT)
logger.error "Roses are red"
logger.unknown "Violets are blue"
Aşağıdakilerden birkaçını yapmanıza izin veren küçük bir RubyGem yazdım:
# 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"
Bir yol daha. Etiketli günlük kaydı kullanıyorsanız ve başka bir günlük dosyasında da etiketlere ihtiyacınız varsa, bunu bu şekilde yapabilirsiniz.
# 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))
Bundan sonra, alternatif kaydedicide uuid etiketleri alacaksınız
["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
Umarım bu birine yardımcı olur.
ActiveSupport::Logger
sadece kullanmak gerekir - bu kutunun dışında çalışır Rails.logger.extend
ile ActiveSupport::Logger.broadcast(...)
.
Bir seçenek daha ;-)
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 ...')
Sevdiğim MultiIO yaklaşım. Ruby Logger ile iyi çalışır . Saf GÇ kullanırsanız , GÇ nesnelerinin sahip olması beklenen bazı yöntemlerden yoksun olduğu için çalışmayı durdurur. Borulardan daha önce burada bahsedilmişti: Ruby logger log çıktısını stdout'a ve dosyaya nasıl alabilirim? . İşte benim için en iyi olan şey.
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')
Not Bunun soruyu doğrudan yanıtlamadığını biliyorum, ancak büyük ölçüde ilişkili. Ne zaman çoklu IO'lara çıktı aradığımda bu konuya rastladım ve umarım bunu da faydalı bulursunuz.
Bu, @ rado'nun çözümünün basitleştirilmesidir.
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
Dış sınıf paketleyiciye ihtiyaç duymadan onun ile aynı faydalara sahiptir. Ayrı bir Ruby dosyasında bulunması yararlı bir yardımcı programdır.
Aşağıdaki gibi temsilci örnekleri oluşturmak için tek satırlık bir uygulama olarak kullanın:
IO_delegator_instance = delegator(:write, :read).for(STDOUT, STDERR)
IO_delegator_instance.write("blah")
VEYA bunu bir fabrika olarak kullanın:
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")
Kullanmakta sorun yaşamıyorsanız, bir günlükçüye ek günlük hedefleri eklemenin mükemmel ve çok kısa bir yolu olan ActiveSupport
kontrol etmenizi şiddetle tavsiye ederim ActiveSupport::Logger.broadcast
.
Aslında, Rails 4+ kullanıyorsanız ( bu işlemden itibaren ), istenen davranışı elde etmek için hiçbir şey yapmanıza gerek yoktur - en azından rails console
. Eğer her kullandığınızda rails console
, Raylar otomatik uzanan Rails.logger
o (olağan dosya hedefine hem çıkışı böyle log/production.log
örneğin) ve 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
Bilinmeyen ve talihsiz bir nedenden ötürü, bu yöntem belgelenmemiştir ancak nasıl çalıştığını öğrenmek veya örnekleri görmek için kaynak koduna veya blog yazılarına başvurabilirsiniz .
https://www.joshmcarthur.com/til/2018/08/16/logging-to-multiple-destinations-using-activesupport-4.html'de başka bir örnek var:
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"
…
Ayrıca son zamanlarda bu ihtiyacım var, bu yüzden bunu yapan bir kütüphane uyguladım. Bu StackOverflow sorusunu yeni keşfettim, bu yüzden ihtiyacı olan herkes için oraya koyuyorum: https://github.com/agis/multi_io .
Burada bahsedilen diğer çözümlerle karşılaştırıldığında, bu IO
kendi başına bir nesne olmaya çalışır, bu nedenle diğer normal IO nesneleri (dosyalar, soketler vb.) İçin bir drop-in yerine kullanılabilir.
Bununla birlikte, henüz tüm standart GÇ yöntemlerini uygulamadım, ancak GÇ anlamını takip edenler (örneğin, #write
tüm temel GÇ hedeflerine yazılan bayt sayısının toplamını döndürür).
STDOUT'unuzun kritik çalışma zamanı bilgileri ve oluşan hatalar için kullanıldığını düşünüyorum.
Bu yüzden kullanıyorum
$log = Logger.new('process.log', 'daily')
hata ayıklama ve düzenli günlüğe kaydetme günlüğünü ve ardından birkaç
puts "doing stuff..."
komut dosyalarımın çalıştığına dair STDOUT bilgilerini görmem gereken yer!
Bah, sadece 10 sentim :-)
| tee
Dosyanın benim için çalışmasından önce eklemek , yaniLogger.new("| tee test.log")
. Boruyu not edin. Bu, coderwall.com/p/y_b3ra/… adresindeki