How can I have ruby logger log output to stdout as

2019-01-08 04:37发布

Someting like a tee functionality in logger.

标签: ruby logging
17条回答
做个烂人
2楼-- · 2019-01-08 05:05

One more option ;-)

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 ...')
查看更多
家丑人穷心不美
3楼-- · 2019-01-08 05:05

@David's solution is very good. I've made a generic delegator class for multiple targets based on his 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 <<self
    alias to new
  end
end

log_file = File.open("debug.log", "a")
log = Logger.new MultiDelegator.delegate(:write, :close).to(STDOUT, log_file)
查看更多
We Are One
4楼-- · 2019-01-08 05:06

This is a simplification of @rado's solution.

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

It has all the same benefits as his without the need of the outer class wrapper. Its a useful utility to have in a separate ruby file.

Use it as a one-liner to generate delegator instances like so:

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

OR use it as a factory like so:

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")
查看更多
孤傲高冷的网名
5楼-- · 2019-01-08 05:06

If you're in Rails 3 or 4, as this blog post points out, Rails 4 has this functionality built in. So you can do:

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

Or if you're on Rails 3, you can backport it:

# 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))
查看更多
爷、活的狠高调
6楼-- · 2019-01-08 05:08

You can also add multiple device logging functionality directly into the 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

For instance:

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')
查看更多
Viruses.
7楼-- · 2019-01-08 05:09

I have written a little RubyGem that allows you to do several of these things:

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

You can find the code on github: teerb

查看更多
登录 后发表回答