Как я могу получить вывод журнала журнала рубинов на stdout, а также файл?

Сокет, как функция тройника в журнале.

Ответ 1

Вы можете написать класс pseudo IO, который будет писать несколько объектов IO. Что-то вроде:

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

Затем установите это как файл журнала:

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

Каждый раз, когда Logger вызывает puts на вашем объекте MultiIO, он будет записывать как STDOUT, так и файл журнала.

Изменить: Я пошел дальше и понял остальную часть интерфейса. Устройство регистрации должно отвечать на write и close (not puts). Пока MultiIO отвечает на эти и прокси-серверы на реальные объекты ввода-вывода, это должно работать.

Ответ 2

@Решение David очень хорошее. Я создал общий класс делегирования для нескольких целей на основе его кода.

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)

Ответ 3

Если вы находитесь в Rails 3 или 4, поскольку этот пост в блоге указывает, Rails 4 имеет эту функциональность в. Итак, вы можете сделать:

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

Или, если вы находитесь в Rails 3, вы можете выполнить его резервное копирование:

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

Ответ 4

Хотя мне и нравятся другие предложения, я обнаружил, что у меня возникла та же проблема, но мне хотелось иметь разные уровни ведения журнала для STDERR и файла.

В итоге я разработал стратегию маршрутизации, которая мультиплексируется на уровне регистратора, а не на уровне ввода-вывода, чтобы каждый регистратор мог работать на независимых уровнях журнала:

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)

Ответ 5

Вы также можете добавить несколько функций регистрации устройств непосредственно в 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

Например:

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

Ответ 6

Для тех, кому это нравится:

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

источник

Или распечатайте сообщение в форматировщике 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

Я использую этот метод для печати в файл журнала, службу логгера (logentries) и, если он является средой dev, также печатается в STDOUT.

Ответ 7

Здесь другая реализация, вдохновленная ответом @jonas054.

Здесь используется шаблон, похожий на Delegator. Таким образом, вам не нужно перечислять все методы, которые вы хотите делегировать, поскольку он делегирует все методы, которые определены в любом из целевых объектов:

class Tee < DelegateToAllClass(IO)
end

$stdout = Tee.new(STDOUT, File.open("#{__FILE__}.log", "a"))

Вы также сможете использовать это с Logger.

delegate_to_all.rb доступен здесь: https://gist.github.com/TylerRick/4990898

Ответ 8

@jonas054 Ответ выше - это замечательно, но он загрязняет класс MultiDelegator каждым новым делегатом. Если вы используете MultiDelegator несколько раз, он будет продолжать добавлять методы в класс, что нежелательно. (См. Ниже, например)

Вот такая же реализация, но с использованием анонимных классов, чтобы методы не загрязняли класс делегирования.

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

Вот пример загрязнения метода исходной реализацией, в отличие от модифицированной реализации:

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

Все хорошо выше. tee имеет метод write, но не метод size, как ожидалось. Теперь рассмотрим, когда мы создаем другой делегат:

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

О нет, tee2 отвечает на size, как ожидалось, но также отвечает на write из-за первого делегата. Даже tee теперь реагирует на size из-за загрязнения метода.

Сравните это с решением анонимного класса, все как ожидается:

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

Ответ 9

Вы ограничены стандартным регистратором?

Если вы не можете использовать 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

Одно преимущество: вы также можете определить разные уровни регистрации для stdout и файла.

Ответ 11

Я пошел к той же идее "Делегирование всех методов к подэлементам", которые другие исследователи уже изучили, но возвращаю для каждого из них возвращаемое значение последнего вызова метода. Если я этого не сделал, он сломал logger-colors, которые ожидали Integer, а карта возвращала 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

Это приведет к переопределению каждого метода ко всем целям и возврату только возвращаемого значения последнего вызова.

Кроме того, если вы хотите цвета, STDOUT или STDERR должны быть помещены последними, так как они должны были выводиться только два цвета. Но тогда он также выдаст цвета вашему файлу.

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

Ответ 12

Я написал немного RubyGem, который позволяет вам сделать несколько из этих вещей:

# Pipe calls to an instance of Ruby 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"

Вы можете найти код на github: teerb

Ответ 13

Еще один способ. Если вы используете тегированные теги и нужны теги в другом файле журнала, вы можете сделать это таким образом.

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

После этого вы получите теги uuid в альтернативном журнале

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

Надеюсь, что это поможет кому-то.

Ответ 14

Еще один вариант: -)

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

Ответ 15

Мне нравится подход MultiIO. Он хорошо работает с Ruby Logger. Если вы используете чистый IO, он перестает работать, потому что ему не хватает некоторых методов, которые, как ожидается, будут иметь объекты IO. Трубы были упомянуты здесь: Как я могу получить вывод журнала журнала рубинов в stdout, а также файл?. Вот что лучше всего подходит для меня.

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

Примечание Я знаю, что это не отвечает на вопрос напрямую, но это сильно связано. Всякий раз, когда я искал вывод для нескольких IO, я сталкивался с этой нитью. Так что, надеюсь, вы тоже найдете это полезным.

Ответ 16

Это упрощение решения @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

Он имеет все те же преимущества, что и его, без необходимости использования внешнего класса-оболочки. Это полезная утилита в отдельном рубиновом файле.

Используйте его как однострочник для генерации экземпляров делегатора, например:

IO_delegator_instance = delegator(:write, :read).for(STDOUT, STDERR)
IO_delegator_instance.write("blah")

ИЛИ используйте его как фабрику, например так:

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

Ответ 17

Вы можете использовать объект Loog::Tee из loog драгоценного камня:

require 'loog'
logger = Loog::Tee.new(first, second)

Именно то, что вы ищете.

Ответ 18

Я думаю, что ваш STDOUT используется для критической информации о запуске и ошибок.

Поэтому я использую

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

для регистрации отладки и регулярного ведения журнала, а затем написал несколько

  puts "doing stuff..."

где мне нужно увидеть информацию STDOUT о том, что мои скрипты запущены вообще!

Ба, только мои 10 центов: -)