Distributed request tracing in Rails
Tags: productivity, webdev, beginners, devops
Published: 2020-09-07 17:31:23

---
title: Distributed request tracing in Rails
published: true
description: Distributed Request Tracing in a microservices based Rails setup
tags: productivity,webdev,beginners,devops
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/3bcwie1g080sfnay265q.png
---

The [microservices](https://microservices.io/patterns/microservices.html) pattern is a highly debated topic. The pros and cons are heatedly discussed over forums, blogs, podcasts, social media, and literally everywhere else. We'll skip that argument for another day. Let's dive into how we can enable better request tracing in a microservices architecture in a pure Ruby on Rails world. Distributed tracing / debugging is one of the biggest challenges in a microservice architecture.

The [X-Request-ID](https://doc.scalingo.com/platform/app/x-request-id) is a standard HTTP header. The header, as defined in the blog post, is :

> A unique request ID, represented by a UUID, is generated at each HTTP request received by the platform routing servers. It is added to the request which is passed to your application containers.
> If the X-Request-ID header is already defined by the client, it won’t be overridden except if it doesn’t respect the following format:
> 20-128 alphanumerical characters and the symbols +, =, / and -.

The key point to focus here is:
> If the X-Request-ID header is already defined by the client, it won’t be overridden

We will use the same header to our advantage when making calls to all our external microservices.

The [`ActionDispatch::Request`](https://github.com/rails/rails/blob/451c3c0a1b1fedd2611278522435f01be4172ef3/actionpack/lib/action_dispatch/http/request.rb#L307) module in rails makes the `uuid` method available on the `request` object. We can use this in our controllers:

```ruby
class ApplicationController < ActionController::Base
  before_action :set_thread_data

  private
  def set_thread_data
    Thread.current[:uuid] = request.uuid
  end
end
```
We can then leverage this `Thread` context from the Proxy classes making requests to our microservices.

```ruby
class ServiceProxy
  attr_reader :headers, :params, :method, :url, :handler

  def initialize(headers:, params:, method:, url:, handler:)
    @headers = headers
    @params = params
    @method = method
    @url = url
    @handler = handler
  end

  def make_request
    circuit.run do
      RestClient::Request.execute(
        method: method, url: url, payload: params, 
        headers: headers, read_timeout: CircuitConstants[handler][:read_timeout],
        open_timeout: CircuitConstants[handler][:open_timeout]
      )
    end
  end

  private
  def circuit
    Circuitbox.circuit(handler, CircuitConstants[handler])
  end

  def headers
    @headers_with_request_id ||= begin
      return @headers unless @headers.is_a?(Hash)
      @headers['X-Request-Id'] = Thread.current[:uuid]
      @headers  
    end
  end
end
```
All modern web frameworks will respect this header and use it to set the request level UUID. In Rails, this is handled by the [`ActionDispatch::RequestId`](https://api.rubyonrails.org/classes/ActionDispatch/RequestId.html) middleware.

We should also set the application level tagged logging to make use of these request uuids:
```ruby
# config/application.rb
config.log_tags = [ :uuid ]
```
After implementing the above, logs will be tagged to the request uuid and will start looking like the log snippet below:

![logs](https://dev-to-uploads.s3.amazonaws.com/i/upiox3phqv87blqi31v6.png)

With the above setup, all requests flowing through all the microservices will have the same `request-id` set, enabling easy request tracing and in-turn, all application issues, easily debuggable.