DEV Community

Chad Wilken for CompanyCam

Posted on • Edited on

Sidekiq Request ID Tracking

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)