-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Middleware
Sidekiq has a similar notion of middleware to Rack: these are small bits of code that can implement functionality. Sidekiq breaks middleware into client-side and server-side.
- Client middleware runs before the pushing of the job to Redis and allows you to modify/stop the job before it gets pushed.
- Server middleware runs 'around' job processing.
If you call perform_inline
both client and server middleware will run.
Sidekiq 5+ ships with no middleware out of the box. Various gems and services will install middleware to track jobs or provide additional features. You may also add your own (note that as of Sidekiq 7, you must include Sidekiq::ClientMiddleware
or Sidekiq::ServerMiddleware
in your client or server middleware, respectively)
You can use client middleware to add job metadata to the job before pushing it to Redis. The same job data will be available to the server middleware before the job is executed, if you need to set up some global state, e.g. current locale, current tenant in a multi-tenant app, etc.
Client middleware may receive the class argument as a Class object or a String containing the name of the class.
Not calling yield
or returning anything other than job
will result in no further middleware being called and the job will not be pushed to the queue. (Note that yield
will return an equivalent value to job
)
module MyMiddleware
module Client
class CustomerJobAttribute
include Sidekiq::ClientMiddleware
# @param [String, Class] job_class_or_string the class or string representation
# of the class of the job being queued
# @param [Hash] job the full job payload
# * @see https://github.com/sidekiq/sidekiq/wiki/Job-Format
# @param [String] queue the name of the queue the job was pulled from
# @param [ConnectionPool] redis_pool the redis pool
# @return [Hash, FalseClass, nil] if false or nil is returned,
# the job is not to be enqueued into redis, otherwise the block's
# return value is returned
# @yield the next middleware in the chain or the enqueuing of the job
def call(job_class_or_string, job, queue, redis_pool)
# return false/nil to stop the job from going to redis
return false if queue != 'default'
job['customer'] = Customer.current_id
yield
end
end
end
end
You can use server middleware to do something around the execution of any job. The example below logs any exception from any job.
Not calling yield
will result in no further middleware being called and the job's perform
method will not be called.
class MyMiddleware::Server::ErrorLogger
include Sidekiq::ServerMiddleware
# @param [Object] job_instance the instance of the job that was queued
# @param [Hash] job_payload the full job payload
# * @see https://github.com/sidekiq/sidekiq/wiki/Job-Format
# @param [String] queue the name of the queue the job was pulled from
# @yield the next middleware in the chain or worker `perform` method
# @return [Void]
def call(job_instance, job_payload, queue)
begin
yield
rescue => ex
puts ex.message
end
end
end
You may also pass options to the Middleware initializer when you register the middleware:
class MyMiddleware::Server::ErrorLogger
def initialize(options=nil)
# options == { :foo => 1, :bar => 2 }
end
def call(worker, job, queue)
begin
yield
rescue => ex
puts ex.message
end
end
end
Register your middleware as part of the chain:
Sidekiq.configure_client do |config|
config.client_middleware do |chain|
chain.add MyMiddleware::Client::CustomerJobAttribute
end
end
Sidekiq.configure_server do |config|
config.server_middleware do |chain|
chain.add MyMiddleware::Server::ErrorLogger, :foo => 1, :bar => 2
end
end
I'd suggest putting this code in config/initializers/sidekiq.rb
in your Rails app.
The jobs running in the Sidekiq server can themselves push new jobs to Sidekiq, thus acting as clients. You must configure your client middleware within the configure_server
block also in that case:
Sidekiq.configure_client do |config|
config.client_middleware do |chain|
chain.add MyMiddleware::Client::CustomerJobAttribute
end
end
Sidekiq.configure_server do |config|
config.client_middleware do |chain|
chain.add MyMiddleware::Client::CustomerJobAttribute
end
config.server_middleware do |chain|
chain.add MyMiddleware::Server::ErrorLogger, :foo => 1, :bar => 2
end
end
If you need to remove a middleware for some reason, you can do this in your configuration:
Sidekiq.configure_server do |config|
config.server_middleware do |chain|
chain.remove Some::Middleware
end
end
Sidekiq 6.3 adds support for serializing Rails' ActiveSupport::CurrentAttributes
which ensures a job will have access to the same request-specific context as the HTTP request which created the job. See my blog post for details:
require "sidekiq/middleware/current_attributes"
Sidekiq::CurrentAttributes.persist(Myapp::Current) # Your AS::CurrentAttributes singleton, or...
Sidekiq::CurrentAttributes.persist("Myapp::Current") # initializers shouldn't load classes so we use the class name instead, or...
Sidekiq::CurrentAttributes.persist(["Myapp::Current", "Another::Current"]) # if you have multiple globals...
Sidekiq will print out the configured client and server middleware
chains when started with -v
. Ordering can be critical if multiple
middleware can conflict somehow. The chain class has the various APIs allowing you to adjust middleware ordering in your initializer.