Logging Sidekiq and Rails
Sidekiq is an efficient way to process background jobs in ruby. Sidekiq makes use of threads to process multiple jobs at the same time in the same process.
We as developer when something goes wrong we relay on application logs to find out what exactly happens, But in a multithreaded server finding log for a particular job is not an easy task unless you tag it with enough information.
By default sidekiq logs have process id and thread id appended at the start of the logs, you can see them when it start and finish a job.e.g.,
171 <190>1 2020-06-03T17:39:23.478290+00:00 app worker.1 - - 4 TID-gpcol38ws YourAwesomeJobWorker JID-70cae479d8084030b4e3a1fe INFO: start
...
181 <190>1 2020-06-03T17:39:23.555938+00:00 app worker.1 - - 4 TID-gpcol38ws YourAwesomeJobWorker JID-70cae479d8084030b4e3a1fe INFO: done: 0.078 sec
here 4
the process id and TID-gpcol38ws
is the thread id.
But all other logs which are logged by Rails.logger instance e.g., ActiveRecord queries log, Mailer logs, etc don’t have any information about job-id or thread-id
To get the job context (JID,TID) in our Rails logs we need to change the formatter for Rails.logger instance. Let’s do this,
Sidekiq.configure_server do |config|
Rails.logger.formatter = Sidekiq::Logging.logger.formatter
end
Here we had set Rails.logger.formatter to Sidekiq::Logging.logger.formatter after doing this our Rails logs will something like this,
343 <190>1 2020-06-03T17:57:03.247017+00:00 app worker.1 - - 4 TID-vf0vw YourAwesomeJobWorker JID-b59552097f12abbe8e618704 DEBUG: User Load (1.9ms)[0m [1m[34mSELECT `users`.* FROM `users` WHERE `users`.`id` = 77670112[0m
Now you can filter all logs belong to a particular job together to debug issues in the job.
We still have one problem, we don’t know what is the job-id for you job with specific parameter with which we have some problem e.g., YourAwesomeJobWorker.perform_async(parameters)
To solve this issue we will log job parameters also at the start of our job. To achive this we need to monkey patch Sidekiq::JobLogger#call method like below,
module Sidekiq
class JobLogger
def call(item, queue)
start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
item_str = "args:#{item["args"]}, queue:#{item["queue"]}, created_at:#{item["created_at"]}, enqueued_at:#{item["enqueued_at"]}"
logger.info("start => #{item_str}")
yield
logger.info("done: #{elapsed(start)} sec => #{item_str}")
rescue Exception
logger.info("fail: #{elapsed(start)} sec => #{item_str}")
raise
end
end
end
After adding it in an initilizer we will get something like this.
294 <190>1 2020-06-03T17:57:03.204317+00:00 app worker.1 - - 4 TID-vf0vw YourAwesomeJobWorker JID-b59552097f12abbe8e618704 INFO: start => args:["parameter1", "parameter2"], queue:default, created_at:1591207023.2046888, enqueued_at:1591207023.2047386
After doing these two steps we will able to find worker logs for any job easily.
Bonus
If you using newrelic you had faced issues when looking for performance problems in backgroud jobs which are enqued with .delay
proxy of ActiveJob,
Since all jobs enqued with .delay
presented as single transaction in new-relic so you don’t get which delayed method taking more time.
To fix this issue we will set custom transaction name based on the Class and method where .delay
method get used.
module DelayedExtention
def perform(yml) #set_newrelic_name for delayed jobs
(target, method_name, args) = YAML.load(yml)
target_name = (target.is_a? ActiveRecord::Base) ? "#{target.class.name}#" : "#{target}/"
NewRelic::Agent.set_transaction_name("#{target_name}#{method_name}")
target.__send__(method_name, *args)
end
end
module MailerDelayedExtention
def perform(yml)
(target, method_name, args) = YAML.load(yml)
NewRelic::Agent.set_transaction_name("#{target}/#{method_name}")
msg = target.public_send(method_name, *args)
if msg
deliver(msg)
else
raise "#{target.name}##{method_name} returned an undeliverable mail object"
end
end
end
module Sidekiq
module Extensions
class DelayedModel
prepend DelayedExtention
end
class DelayedClass
prepend DelayedExtention
end
class DelayedMailer
prepend MailerDelayedExtention
end
end
end
This will replace Sidekiq::DelayedModel#perform with actual Class and method name.