Rails and sidekiq-cron
Cron lives in Redis and runs inside Sidekiq, so a lost schedule key is silent.
With sidekiq-cron the schedule lives in Redis and the jobs run inside Sidekiq. That makes two quiet ways to stop: a Redis instance that was flushed or replaced loses the cron keys, and a job disabled in the Sidekiq web UI stays disabled with no trace in your code. Neither raises anything, so Sidekiq's death handlers and your error reporter see nothing at all.
Report from the job
require "net/http"
class NightlyReportJob
include Sidekiq::Job
PING = "https://vivere.dev/p/<monitor-id>".freeze
def perform
ping("/start")
summary = Report.generate!
ping("", summary.to_s)
rescue => e
ping("/fail", e.message)
raise
end
private
def ping(suffix = "", body = nil)
Net::HTTP.post(URI("#{PING}#{suffix}"), body.to_s, "Content-Type" => "text/plain")
rescue StandardError => e
Sidekiq.logger.warn("vivere ping failed: #{e.message}")
end
end
The raise after the failure ping matters: it keeps Sidekiq's retry behaviour exactly as it was. The monitor goes Down on the first failure rather than after the retries, which is usually what you want from a nightly job. To hear about it only once the retries are exhausted, move the ping into sidekiq_retries_exhausted:
sidekiq_retries_exhausted do |msg, ex|
Net::HTTP.post(URI("#{PING}/fail"), ex.message, "Content-Type" => "text/plain")
end
Active Job
If the job goes through Active Job, the same code works in perform, and rescue_from covers the failure ping for every job in a base class.
The whenever gem
If your schedule is a crontab written by whenever, the job runs in its own rails runner process and the cron guide applies instead: wrap the line, or append the ping with &&.
Monitor settings
Use the cron expression from sidekiq.yml or the Sidekiq::Cron::Job definition, with the timezone the schedule was written in. sidekiq-cron enqueues on its poll, and a busy queue then adds its own delay, so the grace should cover the queue wait plus the job's worst normal run rather than its usual one.