De-Interleaving logger in Rails

riscfuture

Tim Morgan

2 years ago · 334 views
class DeinterleavingLogger < ActiveSupport::BufferedLogger
  def initialize(*args)
    super
    @writers = Hash.new
    @writer_guard = Mutex.new
  end

  def add_writer
    @writers[Thread.current] ||= Hash.new
    @writers[Thread.current][Fiber.current] ||= Array.new
  end

  def flush_writer
    return unless current_queue
    @writer_guard.synchronize { current_queue.each { |args, block| add_without_deinterleaving *args, &block } }
    @writers[Thread.current].delete Fiber.current
  end

  def add_with_deinterleaving(*args, &block)
    if current_queue then
      current_queue << [ args, block ]
    else
      add_without_deinterleaving *args, &block
    end
  end
  alias_method_chain :add, :deinterleaving

  private

  def current_queue
    @writers[Thread.current].try(:[], Fiber.current)
  end

  class Middleware
    def initialize(app)
      @app = app
    end

    def call(env)
      Rails.logger.add_writer
      status, headers, response = @app.call(env)
      Rails.logger.flush_writer
      return [ status, headers, response ]
    end
  end
end
Raw

If you use something like Rack::FiberPool, you'll find your server logs interleaved due to parallelization of requests. This middleware and logger replacement will buffer each request's logs until the request is finished, then dump it in an exclusive block.

To use, add the middleware after you've fibered your request, and replace config.logger with the DeinterleavingLogger.

Tagged: fibers logging ruby on rails
Comments are only visible to Forrst members. Log in or Request an invite.
3 new notifications