Skip to content

renuo/rails_api_logger

Repository files navigation

Rails API Logger

The simplest way to log API requests of your Rails application in your database.

The Rails API logger gem introduces a set of tools to log and debug API requests. It works on two sides:

  • Inbound requests: API exposed by your application
  • Outbound requests: API invoked by your application

This gem has been extracted from various Renuo projects, where we implemented this technique multiple times successfully.

This gem creates two database tables to log the following information:

  • path the path/url invoked
  • method the method used to invoke the API (get, post, put, etc...)
  • request_body what was included in the request body
  • response_body what was included in the response body
  • response_code the HTTP response code of the request
  • started_at when the request started
  • ended_at when the request finished

Installation

Add this line to your application's Gemfile:

gem 'rails_api_logger'

And then execute:

bundle install
spring stop # if it's running. otherwise it does not see the new generator 
bundle exec rails generate rails_api_logger:install
bundle exec rails db:migrate

This will generate two tables inbound_request_logs and outbound_request_logs. These tables will contain the logs.

Log Outbound Requests

Given an outbound request in the following format:

uri = URI('http://example.com/some_path?query=string')
http = Net::HTTP.start(uri.host, uri.port)
request = Net::HTTP::Get.new(uri)
response = http.request(request)

you can log it by doing the following:

uri = URI('http://example.com/some_path?query=string')
http = Net::HTTP.start(uri.host, uri.port)
request = Net::HTTP::Get.new(uri)

log = OutboundRequestLog.from_request(request)

response = http.request(request)

log.response_body = response.body
log.response_code = response.code
log.save!

You can also use the provided logger class to do that in a simpler and safer manner:

uri = URI('https://example.com/some_path')
request = Net::HTTP::Post.new(uri)
request.body = { answer: 42 }.to_json
request.content_type = 'application/json'

response = RailsApiLogger.new.call(nil, request) do
  Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
end

This will guarantee that the log is always persisted, even in case of errors.

Database Transactions Caveats

If you log your outbound requests inside of parent app transactions (which you probably shouldn't), your logs will not be persisted if the transaction is rolled-back. You can circumvent that by opening another database connection to the same (or another database) when logging.

# config/initializers/outbound_request_log_patch.rb

module OutboundRequestLogTransactionPatch
  extend ActiveSupport::Concern

  included do
    connects_to database: { writing: :primary, reading: :primary }
  end
end

OutboundRequestLog.include(OutboundRequestLogTransactionPatch)

You can also log the request in a separate thread to provoke the checkout of a separate database connection. Have a look at this example here.

Log Inbound Requests

If you are exposing some API you might be interested in logging the requests you receive. You can do so by adding this middleware in config/application.rb

config.middleware.insert_before Rails::Rack::Logger, InboundRequestsLoggerMiddleware

this will by default only log requests that have an impact in your system (POST, PUT, and PATCH calls). If you want to log all requests (also GET ones) use

config.middleware.insert_before Rails::Rack::Logger, InboundRequestsLoggerMiddleware, only_state_change: false

If you want to log only requests on a certain path, you can pass a regular expression:

config.middleware.insert_before Rails::Rack::Logger, InboundRequestsLoggerMiddleware, path_regexp: /api/

If you want to skip logging the response or request body of certain requests, you can pass a regular expression:

config.middleware.insert_before Rails::Rack::Logger, InboundRequestsLoggerMiddleware,
                                skip_request_body_regexp: /api/books/,
                                skip_response_body_regexp: /api/letters/

In the implementation of your API, you can call any time attach_inbound_request_loggable(model) to attach an already persisted model to the log record.

For example:

def create
  @user = User.new(user_params)
  if @user.save
    attach_inbound_request_loggable(@user)
    render json: { id: @user.id }, status: :created
  else
    render json: @user.errors.details, status: :unprocessable_entity
  end
end

in the User model you can define:

has_many :inbound_request_logs, inverse_of: :loggable, dependent: :destroy, as: :loggable

to be able to access the logs attached to the model.

RailsAdmin integration

We provide here some code samples to integrate the models in RailsAdmin.

This configuration will give you some nice views, and searches to work with the logs efficiently.

%w[InboundRequestLog OutboundRequestLog].each do |logging_model|
  config.model logging_model do
    list do
      filters %i[method path response_code request_body response_body created_at]
      scopes [nil, :failed]

      include_fields :method, :path, :response_code, :created_at

      field :request_body, :string do
        visible false
        searchable true
        filterable true
      end

      field :response_body, :string do
        visible false
        searchable true
        filterable true
      end
    end

    show do
      include_fields :loggable, :method, :path, :response_code
      field(:created_at)
      field(:request_body) do
        formatted_value { "<pre>#{JSON.pretty_generate(bindings[:object].request_body)}</pre>".html_safe }
      end
      field(:response_body) do
        formatted_value { "<pre>#{JSON.pretty_generate(bindings[:object].response_body)}</pre>".html_safe }
      end
    end
  end
end

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/renuo/rails_api_logger. This project is intended to be a safe, welcoming space for collaboration.

Try to be a decent human being while interacting with other people.

License

The gem is available as open source under the terms of the MIT License.