Sidekiq Request ID Tracking
Published: 2022-07-08 15:47:38

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.

```ruby
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`.

```ruby
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.

```ruby
# 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`.

```ruby
# 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.

```ruby
# 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.

```ruby
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?