Monitoring Rails scheduled jobs (whenever, sidekiq-cron, Solid Queue)
Rails apps schedule recurring work in two ways: system cron (often generated by the whenever gem),
or a job backend's recurring scheduler (sidekiq-cron, sidekiq-scheduler, Solid Queue recurring tasks). Both fail
silently in the same ways. The crontab or scheduler process goes missing after a deploy, the job raises and only
the logs know, or the job hangs.
Jobs scheduled with whenever (system cron)
Wrap the command with cronpup-run. It reports start, success or failure, the exit code and the last lines of output:
# config/schedule.rb
every 1.day, at: "3:00 am" do
command "cronpup-run https://cronpup.com/p/REPORT-UUID bash -lc 'cd /app && bin/rails runner Reports.nightly'"
end
Set the CronPup monitor's schedule to 0 3 * * * in the server's timezone.
Jobs run by Sidekiq, Solid Queue or another queue
Ping from inside the job, so you know the job actually ran, not just that it was enqueued:
# lib/heartbeat.rb
require "net/http"
module Heartbeat
def self.ping(url)
uri = URI(url)
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https",
open_timeout: 5, read_timeout: 5) { |http| http.get(uri.request_uri) }
rescue StandardError => e
Rails.logger.warn("heartbeat ping failed: #{e.message}") # never let monitoring break the job
end
end
# app/jobs/nightly_report_job.rb
class NightlyReportJob < ApplicationJob
def perform
url = ENV.fetch("NIGHTLY_REPORT_PING_URL")
Heartbeat.ping("#{url}/start")
Reports.nightly
Heartbeat.ping(url)
rescue StandardError
Heartbeat.ping("#{url}/fail") if url
raise
end
end
Re-raising keeps the queue's normal retry behaviour. If a retry later succeeds, its success ping marks the monitor up again, and you get a recovery notice.
Pick the schedule and grace period
- Use the same cron expression and timezone as the scheduler entry, so CronPup expects the run at the same time.
- Queued jobs can wait behind other work. Set the grace period to cover normal queue latency plus run time. The monitor page suggests a grace period from your real run durations once a few runs are in.
- Add a one-minute heartbeat job to catch the case where the scheduler process itself isn't running.
Keep ping URLs in environment variables, so staging doesn't ping production monitors.
Monitor your Rails jobs. 10 monitors are free.