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:09

Here's another implementation, inspired by @jonas054's answer.

This uses a pattern similar to Delegator. This way you don't have to list all the methods you want to delegate, since it will delegate all methods that are defined in any of the target objects:

class Tee < DelegateToAllClass(IO)
end

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

You should be able to use this with Logger as well.

delegate_to_all.rb is available from here: https://gist.github.com/TylerRick/4990898

查看更多
爷的心禁止访问
3楼-- · 2019-01-08 05:13

For those who like it simple:

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

source

Or print the message in the Logger formatter:

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

I'm actually using this technique to print to a log file, a cloud logger service (logentries) and if it's dev environment - also print to STDOUT.

查看更多
一夜七次
4楼-- · 2019-01-08 05:13

Are you restricted to the standard logger?

If not you may use 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

One advantage: You could also define different log-levels for stdout and file.

查看更多
我欲成王,谁敢阻挡
5楼-- · 2019-01-08 05:14

I think your STDOUT is used for critical runtime info and errors raised.

So I use

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

to log debug and regular logging, and then wrote a few

  puts "doing stuff..."

where I need to see STDOUT information that my scripts were running at all!

Bah, just my 10 cents :-)

查看更多
来,给爷笑一个
6楼-- · 2019-01-08 05:19

@jonas054's answer above is great, but it pollutes the MultiDelegator class with every new delegate. If you use MultiDelegator several times, it will keep adding methods to the class, which is undesirable. (See bellow for example)

Here is the same implementation, but using anonymous classes so the methods don't pollute the delegator class.

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

Here is an example of the method pollution with the original implementation, contrasted with the modified implementation:

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

All is good above. tee has a write method, but no size method as expected. Now, consider when we create another delegate:

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

Oh no, tee2 responds to size as expected, but it also responds to write because of the first delegate. Even tee now responds to size because of the method pollution.

Contrast this to the anonymous class solution, everything is as expected:

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
查看更多
登录 后发表回答