The microservices 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 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
module in rails makes the uuid
method available on the request
object. We can use this in our controllers:
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.
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
middleware.
We should also set the application level tagged logging to make use of these request uuids:
# 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:
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.
Top comments (0)