Jak Mogę mieć wyjście dziennika ruby logger na stdout oraz plik?

Coś jak funkcja tee w loggerze.

Author: Manish Sapariya, 2011-06-20

16 answers

Możesz napisać pseudo IO klasę, która będzie pisać do wielu IO obiektów. Coś w stylu:

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)

Za każdym razem, gdy Logger wywoła puts na Twoim obiekcie MultiIO, zapisuje zarówno STDOUT, jak i Twój plik dziennika.

Edit: poszedłem do przodu i rozgryzłem resztę interfejsu. Urządzenie logujące musi odpowiadać na write i close (nie puts). Tak długo, jak MultiIO odpowiada na te i proxuje je do rzeczywistych obiektów IO, to powinno zadziałać.

 109
Author: David,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-20 15:18:25

@ David rozwiązanie jest bardzo dobre. Stworzyłem ogólną klasę delegatora dla wielu celów na podstawie jego kodu.

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)
 45
Author: jonas054,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-20 11:03:17

Jeśli jesteś w Rails 3 lub 4, Jak ten wpis na blogu wskazuje, Rails 4 ma wbudowaną 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 jesteś na Rails 3, możesz go cofnąć:

# 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))
 26
Author: phillbaker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-26 16:29:11

Możesz również dodać wiele funkcji logowania urządzeń bezpośrednio do REJESTRATORA:

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')
 10
Author: Ramon de C Valle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-02 02:27:57

Oto kolejna implementacja, zainspirowana odpowiedzią @jonas054 .

To używa wzoru podobnego do Delegator. W ten sposób nie musisz wyświetlać listy wszystkich metod, które chcesz delegować, ponieważ deleguje ona wszystkie metody zdefiniowane w dowolnym obiekcie docelowym:

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

 9
Author: Tyler Rick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-19 23:39:31

Dla tych, którzy lubią proste:

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

Używam tej techniki do drukowania do pliku dziennika, usługi cloud logger (logentries), a jeśli jest to środowisko dev - również wydrukować na STDOUT.

 9
Author: Igor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-05 13:32:14

Chociaż podobają mi się inne sugestie, odkryłem, że mam ten sam problem, ale chciałem mieć różne poziomy logowania dla STDERR i Pliku (jak mogłem z większymi frameworkami logowania, takimi jak NLog). W rezultacie otrzymałem strategię routingu, która multipleksuje się na poziomie loggera, a nie na poziomie IO, tak aby każdy logger mógł działać na niezależnych poziomach logowania:

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 )
 9
Author: dsz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-22 21:15:13

@jonas054 powyższa odpowiedź jest świetna, ale zanieczyszcza klasę MultiDelegator z każdym nowym delegatem. Jeśli użyjesz MultiDelegator kilka razy, będzie ona dodawać metody do klasy, co jest niepożądane. (Zobacz na przykład poniżej)

Tutaj jest ta sama implementacja, ale używa anonimowych klas, 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 metody z oryginalną implementacją, kontrastującą ze zmodyfikowaną realizacja:

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

Wszystko jest dobre powyżej. tee ma metodę write, ale nie ma metody size zgodnie z oczekiwaniami. Teraz zastanów się, kiedy utworzymy innego delegata:

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

Oh Nie, tee2 odpowiada size zgodnie z oczekiwaniami, ale odpowiada również write z powodu pierwszego delegata. Nawet tee Teraz odpowiada size ze względu na metodę.

W przeciwieństwie do anonimowego rozwiązania klasy, wszystko jest zgodnie 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
 3
Author: Rado,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-16 03:53:36

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Ĺźna rĂłwnieĹź definiowaÄ ‡ róşne poziomy logăłw dla stdout i file.

 2
Author: knut,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-01 20:00:11

Quick and dirty (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')
 2
Author: Jose Alban,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-14 11:04:48

Wpadłem na ten sam pomysł "delegowania wszystkich metod do podelementów", który inni ludzie już zbadali, ale zwracam dla każdego z nich wartość zwracaną przez ostatnie wywołanie metody. Jeśli nie, to pękło logger-colors, które oczekiwały 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 przydzielenie każdej metody do wszystkich celów i zwróci tylko wartość zwracaną przez ostatnie wywołanie.

Ponadto, jeśli chcesz Kolory, STDOUT lub STDERR muszą być umieszczone na końcu, ponieważ są to jedyne dwa były kolory mają być wyjściowe. Ale wtedy również wyświetli kolory do pliku.

logger = Logger.new MultiIO.new(File.open("log/test.log", 'w'), STDOUT)
logger.error "Roses are red"
logger.unknown "Violets are blue"
 1
Author: Jerska,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-06 11:37:08

Napisałem Mały RubyGem, który pozwala 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 GitHubie: teerb

 1
Author: Patrick Hüsler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-08 15:52:17

Jeszcze jeden sposób. Jeśli używasz tagged logging i potrzebujesz tagów 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))

Następnie otrzymasz znaczniki 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.
 0
Author: retgoat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-12 10:05:40

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 ...')
 0
Author: Michael Voigt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-09 14:08:51

Lubię podejście Multio . Działa dobrze z Ruby Logger . Jeśli używasz pure IO przestaje działać, ponieważ brakuje w nim pewnych metod, które obiekty IO powinny mieć. Pipes były już wspomniane tutaj: Jak mogę mieć wyjście dziennika ruby logger na stdout jak również plik?. Oto, co dla mnie najlepsze.

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 wyjścia do wielu IOs natknąłem się na to thread.So mam nadzieję, że to też się Wam przyda.

 0
Author: knugie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:38

Myślę, że Twój STDOUT jest używany do krytycznych informacji o uruchomieniu i błędów.

Więc używam

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

Do logowania debugowania i zwykłego logowania, a następnie napisał kilka

  puts "doing stuff..."

Gdzie muszę zobaczyć STDOUT informacji, że moje Skrypty były uruchomione w ogóle!

Bah, tylko moje 10 centów : -)
 -3
Author: rupweb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-26 10:52:46