Co czas Loggerpołączenia putsna swoim MultiIOobiekcie, będzie pisać do obu STDOUTi pliku dziennika.
Edycja: poszedłem dalej i rozgryzłem resztę interfejsu. Urządzenie rejestrujące musi odpowiadać na writei close(nie puts). MultiIOPowinno to działać, o ile odpowiada na nie i przekazuje je do rzeczywistych obiektów we / wy.
jeśli spojrzysz na ktora loggera, zobaczysz, że zakłóci to rotację logów. 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
JeffCharter
3
Uwaga w Rubim 2.2 @targets.each(&:close)jest amortyzowana.
xis
Pracowało dla mnie, dopóki nie zdałem sobie sprawy, że muszę okresowo wywoływać: close na log_file, aby uzyskać plik log_file w celu zaktualizowania tego, co zarejestrował rejestrator (zasadniczo "zapisz"). STDOUT nie lubił: bliskie bycie na nim wezwanym, coś w rodzaju pokonania idei MultoIO. Dodano hack do pominięcia: blisko, z wyjątkiem klasy Plik, ale żałuję, że nie mam bardziej eleganckiego rozwiązania.
Kim Miller
48
Rozwiązanie @ Davida jest bardzo dobre. Stworzyłem ogólną klasę delegatora dla wielu celów w oparciu o jego kod.
Czy mógłbyś wyjaśnić, w jaki sposób jest to lepsze lub jakie są ulepszone narzędzia tego podejścia niż proste sugerowane przez Davida
Manish Sapariya
5
To oddzielenie obaw. MultiDelegator wie tylko o delegowaniu wywołań do wielu celów. Fakt, że urządzenie rejestrujące wymaga metody zapisu i zamknięcia, jest zaimplementowany w programie wywołującym. To sprawia, że MultiDelegator jest użyteczny w innych sytuacjach niż logowanie.
jonas054
Niezłe rozwiązanie. Próbowałem użyć tego do przeniesienia danych wyjściowych z moich zadań rake do pliku dziennika. Aby jednak działał z putsami (aby móc wywołać $ stdout.puts bez otrzymywania „metody prywatnej„ puts ”o nazwie”), musiałem dodać kilka innych metod: log_file = File.open ("tmp / rake.log "," a ") $ stdout = MultiDelegator.delegate (: write,: close,: puts,: print) .to (STDOUT, log_file) Byłoby miło, gdyby można było stworzyć klasę Tee, która dziedziczy z MultiDelegator, tak jak można to zrobić z klasą Delegator w stdlib ...
Tyler Rick
Wymyśliłem implementację podobną do Delegatora, którą nazwałem DelegatorToAll. W ten sposób nie musisz wymieniać wszystkich metod, które chcesz delegować, ponieważ deleguje wszystkie metody zdefiniowane w klasie delegata (IO): class Tee <DelegateToAllClass (IO) end $ stdout = Tee.new (STDOUT , File.open ("# { PLIK } .log", "a")) Więcej informacji znajdziesz na gist.github.com/TylerRick/4990898 .
Tyler Rick,
1
Naprawdę podoba mi się twoje rozwiązanie, ale nie jest dobre jako ogólny delegator, którego można używać wiele razy, ponieważ każda delegacja zanieczyszcza wszystkie wystąpienia nowymi metodami. Poniżej zamieściłem odpowiedź ( stackoverflow.com/a/36659911/123376 ), która rozwiązuje ten problem. Opublikowałem odpowiedź, a nie edycję, ponieważ zobaczenie różnicy między dwoma implementacjami może być pouczające, ponieważ zamieściłem również przykłady.
Lub jeśli korzystasz z Rails 3, możesz to przenieść:
# config/initializers/alternative_output_log.rb# backported from rails4moduleActiveSupportclassLogger < ::Logger# Broadcasts logs to multiple loggers. Returns a module to be# `extended`'ed into other logger instances.defself.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)
endendendendend
file_logger = Logger.new(Rails.root.join("log/alternative-output.log"))
Rails.logger.extend(ActiveSupport::Logger.broadcast(file_logger))
czy ma to zastosowanie poza szynami, czy tylko po szynach?
Ed Sykes,
Jest oparty na ActiveSupport, więc jeśli masz już tę zależność, możesz extenddowolną ActiveSupport::Loggerinstancję, jak pokazano powyżej.
phillbaker
Dzięki, to było pomocne.
Lucas
Myślę, że to najprostsza i najskuteczniejsza odpowiedź, chociaż miałem trochę dziwności, używając config.logger.extend()wewnętrznej konfiguracji mojego środowiska. Zamiast ustawić config.loggersię STDOUTw moim otoczeniu, a następnie rozszerzyła rejestratora w różnych inicjalizatorów.
mattsch,
14
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
fangxing
13
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:
Najbardziej podoba mi się to rozwiązanie, ponieważ jest (1) proste i (2) zachęca do ponownego użycia klas Loggera zamiast zakładać, że wszystko trafia do pliku. W moim przypadku chciałbym zalogować się do STDOUT i appendera GELF dla Graylog. Posiadanie MultiLoggerpolubienia, które opisuje @dsz, to świetne dopasowanie. Dzięki za udostępnienie!
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:
Odpowiedź @ jonas054 powyżej jest świetna, ale zanieczyszcza MultiDelegatorklasę każdym nowym delegatem. Jeśli użyjesz MultiDelegatorkilka 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.
classBetterMultiDelegatordefself.delegate(*methods)
Class.new dodefinitialize(*targets)
@targets = targets
end
methods.each do|m|
define_method(m) do|*args|
@targets.map { |t| t.send(m, *args) }
endendclass <<selfalias to new
endend# new classend# delegateend
Oto przykład zanieczyszczenia metody oryginalną implementacją, w przeciwieństwie do zmodyfikowanej implementacji:
Wszystko jest dobrze powyżej. teema writemetodę, ale nie ma sizemetody 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, tee2odpowiada sizezgodnie z oczekiwaniami, ale reaguje również z writepowodu pierwszego delegata. Nawet teeteraz reaguje z sizepowodu zanieczyszczenia metody.
W porównaniu z rozwiązaniem klasy anonimowej, wszystko jest zgodne z oczekiwaniami:
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-colorsco oczekiwałem, Integera mapa zwracała Array.
classMultiIOdefself.delegate_all
IO.methods.each do|m|
define_method(m) do|*args|
ret = nil
@targets.each { |t| ret = t.send(m, *args) }
ret
endendenddefinitialize(*targets)
@targets = targets
MultiIO.delegate_all
endend
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"
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.rbmoduleActiveSupportclassLogger < ::Logger# Broadcasts logs to multiple loggers. Returns a module to be# `extended`'ed into other logger instances.defself.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)
endend# Module.newend# broadcastdefinitialize(*args)super
@formatter = SimpleFormatter.new
end# Simple formatter which only displays the message.classSimpleFormatter < ::Logger::Formatter# This method is invoked when a log event occursdefcall(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"endendend# class Loggerend# 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-1216: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
Prosty, niezawodny i działa genialnie. Dzięki! Zwróć uwagę, że ActiveSupport::Loggerdziała to po wyjęciu z pudełka - wystarczy użyć Rails.logger.extendz ActiveSupport::Logger.broadcast(...).
defwatch(cmd)
output = StringIO.new
IO.popen(cmd) do|fd|until fd.eof?
bit = fd.getc
output << bit
$stdout.putc bit
endend
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.
Jeśli nie masz nic ActiveSupportprzeciwko 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.loggertak, że wysyła zarówno do swojego zwykłego miejsca docelowego pliku ( log/production.logna przykład), jak i STDERR:
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.
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ć IOwł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. #writeZwraca sumę liczby bajtów zapisanych do wszystkich bazowych celów IO).
| tee
zanim plik zadziałał dla mnie, więcLogger.new("| tee test.log")
. Zwróć uwagę na rurę. To pochodzi z poradytee --append test.log
aby zapobiec nadpisywaniu.Odpowiedzi:
Możesz napisać pseudoklasę,
IO
która będzie zapisywać do wieluIO
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łączeniaputs
na swoimMultiIO
obiekcie, będzie pisać do obuSTDOUT
i pliku dziennika.Edycja: poszedłem dalej i rozgryzłem resztę interfejsu. Urządzenie rejestrujące musi odpowiadać na
write
iclose
(nieputs
).MultiIO
Powinno to działać, o ile odpowiada na nie i przekazuje je do rzeczywistych obiektów we / wy.źródło
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)
źródło
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))
źródło
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
źródło
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.
źródło
"| tee test.log"
nadpisze stare wyjścia, może"| tee -a test.log"
zamiast tegoChociaż 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)
źródło
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')
źródło
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
źródło
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')
źródło
Odpowiedź @ jonas054 powyżej jest świetna, ale zanieczyszcza
MultiDelegator
klasę każdym nowym delegatem. Jeśli użyjeszMultiDelegator
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
mawrite
metodę, ale nie masize
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
odpowiadasize
zgodnie z oczekiwaniami, ale reaguje również zwrite
powodu pierwszego delegata. Nawettee
teraz reaguje zsize
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
źródło
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.
źródło
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łaArray
.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"
źródło
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
źródło
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.
źródło
ActiveSupport::Logger
działa to po wyjęciu z pudełka - wystarczy użyćRails.logger.extend
zActiveSupport::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 ...')
źródło
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.
źródło
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")
źródło
Możesz użyć
Loog::Tee
obiektu zloog
klejnotu:require 'loog' logger = Loog::Tee.new(first, second)
Dokładnie to, czego szukasz.
źródło
Jeśli nie masz nic
ActiveSupport
przeciwko używaniu , gorąco polecam sprawdzenieActiveSupport::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żywaszrails console
, Railsy automatycznie rozszerzają zakresRails.logger
tak, że wysyła zarówno do swojego zwykłego miejsca docelowego pliku (log/production.log
na przykład), jak iSTDERR
: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" …
źródło
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).źródło
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 :-)
źródło