Coś w rodzaju tee w loggerze.
Coś w rodzaju tee w loggerze.
tee --append test.log
aby zapobiec nadpisywaniu.
Odpowiedzi:
Możesz napisać pseudoklasę, IO
która będzie zapisywać do wielu IO
obiektów. Coś jak:
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
Następnie ustaw to jako plik dziennika:
log_file = File.open("log/debug.log", "a")
Logger.new MultiIO.new(STDOUT, log_file)
Co czas Logger
połączenia puts
na swoim MultiIO
obiekcie, będzie pisać do obu STDOUT
i pliku dziennika.
Edycja: poszedłem dalej i rozgryzłem resztę interfejsu. Urządzenie rejestrujące musi odpowiadać na write
i close
(nie puts
). MultiIO
Powinno to działać, o ile odpowiada na nie i przekazuje je do rzeczywistych obiektów we / wy.
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)
jest amortyzowana.
Rozwiązanie @ Davida jest bardzo dobre. Stworzyłem ogólną klasę delegatora dla wielu celów w oparciu o jego kod.
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)
Jeśli jesteś w Railsach 3 lub 4, jak wskazuje ten wpis na blogu , Rails 4 ma wbudowaną tę funkcjonalność . Więc możesz zrobić:
# config/environment/production.rb
file_logger = Logger.new(Rails.root.join("log/alternative-output.log"))
config.logger.extend(ActiveSupport::Logger.broadcast(file_logger))
Lub jeśli korzystasz z Rails 3, możesz to przenieść:
# 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
dowolną ActiveSupport::Logger
instancję, jak pokazano powyżej.
config.logger.extend()
wewnętrznej konfiguracji mojego środowiska. Zamiast ustawić config.logger
się STDOUT
w moim otoczeniu, a następnie rozszerzyła rejestratora w różnych inicjalizatorów.
Dla tych, którzy lubią prostotę:
log = Logger.new("| tee test.log") # note the pipe ( '|' )
log.info "hi" # will log to both STDOUT and test.log
Lub wydrukuj wiadomość w programie formatującym 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
W rzeczywistości używam tej techniki do drukowania do pliku dziennika, usługi rejestratora w chmurze (logentries), a jeśli jest to środowisko programistyczne - również do drukowania na STDOUT.
"| tee test.log"
nadpisze stare wyjścia, może "| tee -a test.log"
zamiast tego
Chociaż podobają mi się inne sugestie, stwierdziłem, że mam ten sam problem, ale chciałem mieć możliwość posiadania różnych poziomów rejestrowania dla STDERR i pliku.
Skończyło się na strategii routingu, która multipleksuje na poziomie rejestratora, a nie na poziomie IO, tak aby każdy rejestrator mógł następnie działać na niezależnych poziomach dziennika:
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
polubienia, które opisuje @dsz, to świetne dopasowanie. Dzięki za udostępnienie!
Możesz również dodać funkcję rejestrowania wielu urządzeń bezpośrednio do Loggera:
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
Na przykład:
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')
Oto kolejna implementacja, zainspirowana odpowiedzią @ jonas054 .
Używa wzorca podobnego do Delegator
. W ten sposób nie musisz wymieniać wszystkich metod, które chcesz delegować, ponieważ deleguje wszystkie metody zdefiniowane w dowolnym z obiektów docelowych:
class Tee < DelegateToAllClass(IO)
end
$stdout = Tee.new(STDOUT, File.open("#{__FILE__}.log", "a"))
Powinieneś być w stanie używać tego również z Loggerem.
delegate_to_all.rb jest dostępny tutaj: https://gist.github.com/TylerRick/4990898
Szybko i brudno (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')
Odpowiedź @ jonas054 powyżej jest świetna, ale zanieczyszcza MultiDelegator
klasę każdym nowym delegatem. Jeśli użyjesz MultiDelegator
kilka razy, będzie nadal dodawać metody do klasy, co jest niepożądane. (Zobacz na przykład poniżej)
Oto ta sama implementacja, ale przy użyciu klas anonimowych, aby metody nie zanieczyszczały klasy delegatora.
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
Oto przykład zanieczyszczenia metody oryginalną implementacją, w przeciwieństwie do zmodyfikowanej implementacji:
tee = MultiDelegator.delegate(:write).to(STDOUT)
tee.respond_to? :write
# => true
tee.respond_to? :size
# => false
Wszystko jest dobrze powyżej. tee
ma write
metodę, ale nie ma size
metody zgodnie z oczekiwaniami. Teraz zastanów się, kiedy tworzymy kolejnego delegata:
tee2 = MultiDelegator.delegate(:size).to("bar")
tee2.respond_to? :size
# => true
tee2.respond_to? :write
# => true !!!!! Bad
tee.respond_to? :size
# => true !!!!! Bad
O nie, tee2
odpowiada size
zgodnie z oczekiwaniami, ale reaguje również z write
powodu pierwszego delegata. Nawet tee
teraz reaguje z size
powodu zanieczyszczenia metody.
W porównaniu z rozwiązaniem klasy anonimowej, wszystko jest zgodne z oczekiwaniami:
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
Czy jesteś ograniczony do standardowego rejestratora?
Jeśli nie, możesz użyć 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
Jedna zaleta: możesz także zdefiniować różne poziomy logowania dla standardowego wyjścia i pliku.
Poszedłem do tego samego pomysłu "Delegowania wszystkich metod do elementów podrzędnych", który inni ludzie już odkryli, ale zwracam dla każdego z nich wartość zwracaną przez ostatnie wywołanie metody. Jeśli tego nie zrobiłem, zepsuło się, logger-colors
co oczekiwałem, Integer
a mapa zwracała 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
Spowoduje to ponowne delegowanie każdej metody do wszystkich celów i zwrócenie tylko wartości zwracanej przez ostatnie wywołanie.
Ponadto, jeśli chcesz, aby kolory, STDOUT lub STDERR muszą być umieszczone na końcu, ponieważ to jedyne dwa, na których mają być wyświetlane kolory. Ale wtedy również wyprowadzi kolory do twojego pliku.
logger = Logger.new MultiIO.new(File.open("log/test.log", 'w'), STDOUT)
logger.error "Roses are red"
logger.unknown "Violets are blue"
Napisałem mały RubyGem, który pozwala ci zrobić kilka z tych rzeczy:
# 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"
Możesz znaleźć kod na github: teerb
Jeszcze jeden sposób. Jeśli używasz tagowanego rejestrowania i potrzebujesz tagów również w innym pliku dziennika, możesz to zrobić w ten sposób
# 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))
Po tym otrzymasz tagi uuid w alternatywnym loggerze
["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
Mam nadzieję, że to komuś pomoże.
ActiveSupport::Logger
działa to po wyjęciu z pudełka - wystarczy użyć Rails.logger.extend
z ActiveSupport::Logger.broadcast(...)
.
Jeszcze jedna opcja ;-)
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 ...')
Podoba mi się podejście MultiIO . Działa dobrze z Ruby Logger . Jeśli używasz czystego IO , przestaje on działać, ponieważ brakuje mu niektórych metod, które powinny mieć obiekty IO. Potoki zostały tutaj wspomniane wcześniej: Jak mogę mieć wyjście dziennika rejestratora ruby na standardowe wyjście, a także do pliku? . Oto, co działa najlepiej dla mnie.
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')
Uwaga Wiem, że to nie odpowiada bezpośrednio na pytanie, ale jest silnie powiązane. Ilekroć szukałem danych wyjściowych do wielu operacji we / wy, natknąłem się na ten wątek, więc mam nadzieję, że to również okaże się przydatne.
To jest uproszczenie rozwiązania @ 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
Ma wszystkie te same zalety, co jego, bez potrzeby zewnętrznego opakowania klasy. Jest to przydatne narzędzie w osobnym pliku ruby.
Użyj go jako jednowierszowego, aby wygenerować wystąpienia delegatora, takie jak:
IO_delegator_instance = delegator(:write, :read).for(STDOUT, STDERR)
IO_delegator_instance.write("blah")
LUB użyj go jako fabryki:
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")
Jeśli nie masz nic ActiveSupport
przeciwko używaniu , gorąco polecam sprawdzenie ActiveSupport::Logger.broadcast
, co jest doskonałym i bardzo zwięzłym sposobem dodawania dodatkowych miejsc docelowych dziennika do rejestratora.
W rzeczywistości, jeśli używasz Rails 4+ (od tego zatwierdzenia ), nie musisz nic robić , aby uzyskać pożądane zachowanie - przynajmniej jeśli używasz rails console
. Za każdym razem, gdy używasz rails console
, Railsy automatycznie rozszerzają zakres Rails.logger
tak, że wysyła zarówno do swojego zwykłego miejsca docelowego pliku ( log/production.log
na przykład), jak i 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
Z jakiegoś nieznanego i niefortunnego powodu ta metoda jest nieudokumentowana, ale możesz odwołać się do kodu źródłowego lub postów na blogu, aby dowiedzieć się, jak to działa lub zobaczyć przykłady.
https://www.joshmcarthur.com/til/2018/08/16/logging-to-multiple-destinations-using-activesupport-4.html ma inny przykład:
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"
…
Ostatnio też mam tę potrzebę, więc zaimplementowałem bibliotekę, która to robi. Właśnie odkryłem to pytanie StackOverflow, więc umieszczam je dla każdego, kto go potrzebuje: https://github.com/agis/multi_io .
W porównaniu z innymi wymienionymi tutaj rozwiązaniami, to stara się być IO
własnym obiektem, więc może być używany jako zamiennik dla innych zwykłych obiektów IO (plików, gniazd itp.)
To powiedziawszy, nie zaimplementowałem jeszcze wszystkich standardowych metod IO, ale te, które są, są zgodne z semantyką IO (np. #write
Zwraca sumę liczby bajtów zapisanych do wszystkich bazowych celów IO).
Myślę, że twój STDOUT jest używany do krytycznych informacji o czasie wykonywania i podniesionych błędów.
Więc używam
$log = Logger.new('process.log', 'daily')
rejestrować debugowanie i regularne rejestrowanie, a następnie napisałem kilka
puts "doing stuff..."
gdzie muszę zobaczyć informację STDOUT, że moje skrypty w ogóle działały!
Tak, tylko moje 10 centów :-)
| tee
zanim plik zadziałał dla mnie, więcLogger.new("| tee test.log")
. Zwróć uwagę na rurę. To pochodzi z porady