I have a Logger instance:
require 'logger'
logger = Logger.new( 'foo.log', 'weekly' )
I want to redirect runtime errors (stderr output) into the log as well. I found this forum thread 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?
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
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With