Comment puis-je avoir la sortie du journal de l’enregistreur ruby ​​sur stdout ainsi que le fichier?

Quelque chose comme une fonctionnalité de tee dans logger.

Vous pouvez écrire une pseudo classe IO qui écrira sur plusieurs objects IO . Quelque chose comme:

 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 

Ensuite, définissez cela comme votre fichier journal:

 log_file = File.open("log/debug.log", "a") Logger.new MultiIO.new(STDOUT, log_file) 

Chaque fois que les appels de Logger MultiIO votre object MultiIO , celui-ci sera écrit à la fois dans STDOUT et dans votre fichier journal.

Edit: Je suis allé de l’avant et j’ai compris le rest de l’interface. Un périphérique de journalisation doit répondre pour write et close (pas mettre). Tant que MultiIO répond à ceux-ci et les transmet par proxy aux objects IO réels, cela devrait fonctionner.

La solution de @ David est très bonne. J’ai créé une classe de délégation générique pour plusieurs cibles en fonction de son code.

 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 < 

Si vous êtes dans Rails 3 ou 4, comme le souligne cet article de blog , Rails 4 intègre cette fonctionnalité . Donc, vous pouvez faire:

 # config/environment/production.rb file_logger = Logger.new(Rails.root.join("log/alternative-output.log")) config.logger.extend(ActiveSupport::Logger.broadcast(file_logger)) 

Ou si vous êtes sur Rails 3, vous pouvez le sauvegarder:

 # 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)) 

Vous pouvez également append plusieurs fonctionnalités de journalisation de périphériques directement dans le 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 

Par exemple:

 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') 

Voici une autre implémentation inspirée de la réponse de @ jonas054 .

Cela utilise un modèle similaire à Delegator . De cette façon, vous n’avez pas besoin de répertorier toutes les méthodes que vous souhaitez déléguer, car il déléguera toutes les méthodes définies dans l’un des objects cibles:

 class Tee < DelegateToAllClass(IO) end $stdout = Tee.new(STDOUT, File.open("#{__FILE__}.log", "a")) 

Vous devriez aussi pouvoir l'utiliser avec Logger.

delegate_to_all.rb est disponible ici: https://gist.github.com/TylerRick/4990898

Pour ceux qui aiment ça simple:

 log = Logger.new("| tee test.log") # note the pipe ( '|' ) log.info "hi" # will log to both STDOUT and test.log 

la source

Ou imprimez le message dans le formateur 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 

J’utilise en fait cette technique pour imprimer dans un fichier journal, un service de journalisation cloud (logensortinges) et si c’est un environnement de développement – imprimez également sur STDOUT.

Bien que j’aime bien les autres suggestions, j’ai trouvé que j’avais le même problème mais que je souhaitais avoir la possibilité d’avoir différents niveaux de journalisation pour STDERR et le fichier (comme je pouvais le faire avec les grandes structures de consignation comme NLog). J’ai fini avec une stratégie de routage qui multiplexe au niveau de l’enregistreur plutôt qu’au niveau IO, de sorte que chaque enregistreur puisse alors fonctionner à des niveaux de journalisation indépendants:

 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 ) 

La réponse de @ jonas054 ci-dessus est géniale, mais elle pollue la classe MultiDelegator avec chaque nouveau délégué. Si vous utilisez MultiDelegator plusieurs fois, cela appenda des méthodes à la classe, ce qui n’est pas souhaitable. (Voir ci-dessous par exemple)

Voici la même implémentation, mais en utilisant des classes anonymes, les méthodes ne polluent pas la classe du délégué.

 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 < 

Voici un exemple de pollution de la méthode avec l'implémentation d'origine, en contraste avec l'implémentation modifiée:

 tee = MultiDelegator.delegate(:write).to(STDOUT) tee.respond_to? :write # => true tee.respond_to? :size # => false 

Tout est bon ci-dessus. tee a une méthode d' write , mais pas de méthode de size comme prévu. Maintenant, considérez quand nous créons un autre délégué:

 tee2 = MultiDelegator.delegate(:size).to("bar") tee2.respond_to? :size # => true tee2.respond_to? :write # => true !!!!! Bad tee.respond_to? :size # => true !!!!! Bad 

Oh non, tee2 répond à la size comme prévu, mais répond aussi à l' write cause du premier délégué. Même le tee répond à la size cause de la pollution de la méthode.

Contrairement à la solution de classe anonyme, tout se passe comme prévu:

 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 

Êtes-vous limité à l’enregistreur standard?

Sinon, vous pouvez utiliser 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 

Un avantage: vous pouvez également définir différents niveaux de journalisation pour stdout et file.

Rapide et sale (réf: 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') 

J’ai écrit un petit RubyGem qui vous permet de faire plusieurs de ces choses:

 # 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" 

Vous pouvez trouver le code sur github: teerb

Je suis allé à la même idée de “Déléguer toutes les méthodes à des sous-éléments” que d’autres personnes ont déjà explorées, mais je leur renvoie pour chacun la valeur de retour du dernier appel de la méthode. Si je ne l’avais pas fait, il a cassé les logger-colors qui attendaient un Integer et la carte retournait un 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 

Cela va redéléguer chaque méthode à toutes les cibles et renvoyer uniquement la valeur de retour du dernier appel.

De plus, si vous voulez des couleurs, STDOUT ou STDERR doit être mis en dernier, puisque ce sont les deux seules couleurs que l’on est censé produire. Mais alors, il va également sortir des couleurs dans votre fichier.

 logger = Logger.new MultiIO.new(File.open("log/test.log", 'w'), STDOUT) logger.error "Roses are red" logger.unknown "Violets are blue" 

Une autre façon. Si vous utilisez également la journalisation balisée et que vous avez également besoin de balises dans un autre fichier journal, vous pouvez le faire de cette manière.

 # 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)) 

Après cela, vous obtiendrez des tags uuid dans un autre enregistreur

 ["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 

J'espère que ça aide quelqu'un.

Une option de plus 😉

 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 ...') 

J’aime l’approche MultiIO . Cela fonctionne bien avec Ruby Logger . Si vous utilisez IO pure, il cesse de fonctionner car il manque certaines méthodes que les objects IO sont censés avoir. Les tuyaux ont déjà été mentionnés ici: Comment puis-je avoir la sortie du journal de l’enregistreur Ruby dans stdout ainsi que dans le fichier? . Voici ce qui fonctionne le mieux pour moi.

 def watch(cmd) output = SsortingngIO.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') 

Remarque: Je sais que cela ne répond pas directement à la question mais il est fortement lié. Chaque fois que je cherchais une sortie vers plusieurs IO, je suis tombé sur ce sujet. Donc, j'espère que vous trouverez cela utile aussi.

Je pense que votre STDOUT est utilisé pour les informations d’exécution critiques et les erreurs soulevées.

Donc j’utilise

  $log = Logger.new('process.log', 'daily') 

pour enregistrer le débogage et la journalisation régulière, puis écrit quelques

  puts "doing stuff..." 

où j’ai besoin de voir les informations STDOUT que mes scripts étaient en cours d’exécution!

Bah, juste mes 10 centimes 🙂