Redirigir stderr a la instancia de Logger

Tengo un Logger ejemplo:

require 'logger'
logger = Logger.new( 'foo.log', 'weekly' )

I want to redirect runtime errors (stderr output) into the log as well. I found este hilo del foro which has the advice:

new_fd = logger.get_logger_file_descriptor
$stderr.reopen new_fd

However, Logger does not have an instance method get_logger_file_descriptor, nor can I find any exposed methods around gaining access to the log device or file.

How can I cause all $stderr output to go into the log?

preguntado el 09 de marzo de 12 a las 15:03

2 Respuestas

If you're creating the logger yourself, you can create the File object first, then use it to create the logger and assign it to $stderr:

log_file = File.new('foo.log', 'a')
logger = Logger.new(log_file, 'weekly')
$stderr = log_file   #usually no need to use reopen

Note that this will result in the log output being mixed up with the output of $stderr, which may cause problems if you're parsing the log file expecting it to be in a certain format (this will happen with your solution too).

If you don't have the underlying file but just receive the logger from somewhere else, it's a bit more tricky. What is needed is an IO like object that can be assigned to $stderr and passes anything written to it to the logger. The IO class in Ruby is unfortunately fairly closely tied to the underlying i/o system (file descriptors and the like), and there's no general interface that can be used to create input and output streams. (StringIO being the notable exception).

However most, if not all, of the output methods on IO ultimately go through #write, so by overriding this one method you can get close to what you're after:

class IOToLog < IO

  def initialize(logger)
    @logger = logger
  end

  def write(string)
    #assume anything written to stderr is an error
    @logger.error(message)
  end

end

logger = get_logger_from_somewhere

$stderr = IOToLog.new(logger)

Now anything written to $stderr will end up going to the log file. The formatting however will be a bit odd. Anytime any of the writing methods calls #write a new entry will be made in the logfile. For example, #puts called with an array will call #write for each entry of the array, and again with a newline character between each entry, resulting in 2n - 1 log entries, n - 1 of which will be blank.

You could make the overridden #write method more complex to handle this, perhaps using an internal buffer, and only call the logger when you think you have a full message. Alternatively you could override the individual methods to write to the logger themselves. If you did this the IOToLog class wouldn't necessarily have to inherit from IO.

Your best solution will depend on how you want to standard error output to appear in the logfile, how your program uses $stderr, and how much work you want to do implementing methods from IO.

respondido 09 mar '12, 22:03

Great answer, thanks! In my case I am creating the logger so your first solution is clean and elegant. I particularly like that you've included the workaround for the other case. (Though in such a case I might advocate my hack of reaching in and grabbing the file directly.) - Phrogz

The second method doesn't work for me on jruby. I get the following error: (Errno::EBADF) Bad file descriptor - Richard Nienaber

The best I've been able to come up with is this reach-around hack:

$stderr.reopen logger.instance_variable_get(:@logdev).dev

It works, but of course breaks the encapsulation of Logger which I assume was there for a reason.

respondido 09 mar '12, 16:03

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.