ruby-on-railsrubyruby-on-rails-4logging

How to log an entire request (headers, body, etc) for a certain url?


I need to log all the requests including HTTP headers, bodies, etc to a certain url. I've tried this code:

def index
  global_request_logging
end

private

def global_request_logging 
    http_request_header_keys = request.headers.keys.select{|header_name| header_name.match("^HTTP.*")}
    http_request_headers = request.headers.select{|header_name, header_value| http_request_header_keys.index(header_name)}
    logger.info "Received #{request.method.inspect} to #{request.url.inspect} from #{request.remote_ip.inspect}.  Processing with headers #{http_request_headers.inspect} and params #{params.inspect}"
    begin 
      yield 
    ensure 
      logger.info "Responding with #{response.status.inspect} => #{response.body.inspect}"
    end 
  end 

But it said request.headers doesn't contain a method named keys. Also I figure there should be an easier way or standard to do it. Preferably, not to use a gem.


Solution

  • It looks like request.headers returns a hash, but in fact, it returns an instance of Http::Headers that doesn't have a keys method defined.

    But a Http::Headers responds to env which returns the original env hash. Therefore, the following works:

    http_request_header_keys = request.headers.env.keys.select do |header_name| 
      header_name.match("^HTTP.*")
    end
    

    Or you can just iterate over all key-value-pairs and copy them into another hash:

    http_envs = {}.tap do |envs|
      request.headers.each do |key, value|
        envs[key] = value if key.downcase.starts_with?('http')
      end
    end
    
    logger.info <<-LOG.squish
      Received     #{request.method.inspect} 
      to           #{request.url.inspect} 
      from         #{request.remote_ip.inspect}.  
      Processing 
      with headers #{http_envs.inspect} 
      and params   #{params.inspect}"
    LOG
    

    To wrap this up:

    around_action :log_everything, only: :index
    
    def index
      # ...
    end
    
    private
    def log_everything
      log_headers
      yield
    ensure
      log_response
    end
    
    def log_headers
      http_envs = {}.tap do |envs|
        request.headers.each do |key, value|
          envs[key] = value if key.downcase.starts_with?('http')
        end
      end
    
      logger.info "Received #{request.method.inspect} to #{request.url.inspect} from #{request.remote_ip.inspect}. Processing with headers #{http_envs.inspect} and params #{params.inspect}"
    end
    
    def log_response
      logger.info "Responding with #{response.status.inspect} => #{response.body.inspect}"
    end