Sidekiq is the go to job system for most Ruby and Rails applications. It's blazing fast, well supported, trusted by thousands, and best of all it's free! However, I wish that it included a default method for tracking the Rails request_id
that caused the job to be enqueued. This is useful if we want to see which controller action kicked off the job. No worries though, Sidekiq has the concept of middleware - similar to Rack - that allows us to implement functionality on the Sidekiq client and/or server.
Let's Get Started
Before we can implement the Sidekiq middleware we need a place to store request_id
for the current request. In our case we use the Current
model. Rails has some magic that resets the attributes defined on Current
before and after each request - this makes it a great place to store thread-isolated data. That being said, let's go ahead and add the request_id
attribute to our Current
model.
class Current < ActiveSupport::CurrentAttributes
attribute :request_id
end
We can use a before_action
in our ApplicationController
so that every request will store it's request_id
.
class ApplicationController < ActionController::Base
before_action do
Current.request_id = request.request_id
end
end
Now that we have global access to the request_id
we need to create some client and server middleware for Sidekiq.
Client Middleware
Client middleware runs before the job is pushed to Redis - allowing us to modify the job. Knowing this, the middleware is pretty easy.
# You can put this in lib/request_id/sidekiq/client.rb
module RequestId
module Sidekiq
class Client
def call(worker_klass, job, queue, redis_pool)
# Stash the request_id in the job's metadata
job['request_id'] = Current.request_id
yield
end
end
end
end
Server Middleware
The server middleware runs around the execution the job, allowing us to do something before and after the job's execution. Once again it's a class that responds to call
.
# You can put this in lib/request_id/sidekiq/server.rb
module RequestId
module Sidekiq
class Server
def call(worker, job, queue)
Current.request_id = job['request_id']
yield
ensure
Current.reset
end
end
end
end
On the server we pull the request_id
from the job's metadata and set the value on Current
. We'll want to use ensure
and call Current.reset
so that all attributes are reset when the job completes - whether it's successful or not.
Hooking it Up
Now that we have our middleware let's tell Sidekiq to use it.
# config/initializers/sidekiq.rb
require 'lib/request_id/sidekiq/client'
require 'lib/request_id/sidekiq/server'
Sidekiq.configure_server do |config|
if Sidekiq.server?
config.server_middleware do |chain|
chain.add RequestId::Sidekiq::Server
end
end
end
Sidekiq.configure_client do |config|
config.client_middleware do |chain|
chain.add RequestId::Sidekiq::Client
end
end
Now in our job we can include the request_id
that triggered the job in the logs.
class ImportUsersFromCsvWorker
include Sidekiq::Worker
def perform(csv)
# …do stuff
Rails.logger.info({ message: 'Imported Users', request_id: Current.request_id })
end
end
Summary
Knowing what action triggered a job can be quite powerful while debugging. In a few lines of code we can implement middleware for Sidekiq allowing us to use the request_id
of the request that caused our job to be enqueued. We could even go a step further and add the request_id
to Sidekiq's context and include it in the default logs if we wanted to.
What Sidekiq middleware have you implemented or found useful in your app?
Top comments (0)