Redirecting $stdout to Logger with Ruby on Rails

posted in: rails, ruby | 0

Ruby on Rails on FastCGI on Apache is a pretty fast and robust combination. Unfortunately FastCGI do doesn’t like output on “standard out”, i.e. $stdout in Ruby. Brian Pontarelli has an article concerning the problem. His suggestion is to simply have no puts in your code. The standard lib of Ruby however sometimes prints exceptions to the console – REXML is one popular example.

Brian also mentions the idea to redirect anything printed to $stdout to a logger. That means however, that all output given by web servers like Webrick would also end up in some log file – which isn’t the best thing for your development and debugging sessions.

Eric Hodel on the other hand found a pretty nice way to redirect $stdout for only some Ruby threads.

Using those two techniques I came up with a solution that turns on $stdout redirection with the beginning of an HTTP request and turns it off once the Rails controller finished its work. Problem solved 😉 – everything printed to $stdout in your controllers will end up in your development/production/test.log.

Here’s the code:

# in app/controllers/application.rb
class ApplicationController < ActionController::Base

  # ...

  class StdOutLogger
    def write(s)
      if Thread.current[:stdout_to_logger]
        RAILS_DEFAULT_LOGGER.info s
      else
        $stdout_default.write s
      end
    end
  end
  
  before_filter do
    unless $stdout_logger
      $stdout_default = $stdout.clone
      $stdout_logger = StdOutLogger.new
    end
    $stdout = $stderr = $stdout_logger
    Thread.current[:stdout_to_logger] = true
  end
  
  after_filter { !(Thread.current[:stdout_to_logger] = false) }

  # ...

end

One thing that may be a problem: I don't know on which semaphore to synchronize - the beast may not be thread safe. This depends on your FastCGI stack. I didn't experience any problems up to now.