Skip to content

feat(apscheduler): Add cron monitoring for scheduled jobs - #7840

Open
wedamija wants to merge 33 commits into
danf/crons-monitor-markerfrom
danf/apscheduler-crons
Open

wedamija wants to merge 33 commits into
danf/crons-monitor-markerfrom
danf/apscheduler-crons

Conversation

@wedamija

@wedamija wedamija commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

Description

Stacked on #8198.

Adds an APScheduler integration (3.3+) that sends cron check-ins for scheduled jobs. It isn't auto-enabled; adding it turns monitoring on:

sentry_sdk.init(integrations=[APSchedulerIntegration()])

Each run sends check-ins with a monitor config taken from the job's trigger, so monitors are created on the first run and stay in sync with the schedule. Runs that never start report as missed.

Jobs we can't describe exactly (some triggers, non-IANA time zones, one-off DateTrigger jobs) aren't monitored, since a wrong schedule causes false missed alerts. Jobs that already use @sentry_sdk.monitor are skipped, so they don't report twice. exclude_jobs skips jobs by regex.

Motivation

Today APScheduler users have to create every monitor by hand and add check-in calls to each job. The schedule is already on the trigger, so the SDK can do both.

Docs: getsentry/sentry-docs#19786

Add an opt-in APSchedulerIntegration(monitor_jobs=True) for APScheduler 3.x.
When a job with a cron or interval trigger is submitted to its executor, send
an in_progress check-in with a monitor config derived from the trigger, so the
monitor is created on first run. A scheduler listener sends the ok/error
check-in when the run finishes or is missed.

The integration is not auto-enabled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Coverage Results 📊

✅ 137666 passed | ⏭️ 7503 skipped | Total: 145169 | Pass Rate: 94.83% | Execution Time: 449m 22s

📊 Comparison with Base Branch

Metric Change
Total Tests 📈 +499
Passed Tests 📈 +452
Failed Tests —
Skipped Tests 📈 +47

All tests are passing successfully.

✅ Patch coverage is 96.02% (193 of 201 changed executable lines covered; target 80%).
Project statement coverage is 90.34% (up 0.01 percentage points from base (b05563b) to head (2963513)).

Changed files with executable lines (1)
File Patch coverage Changed executable lines
sentry_sdk/integrations/apscheduler.py 96.02% 193/201 covered; missed: 177, 219, 324, 340, 342, 343, 344, 345; partial branches: 125, 172, 218, 323, 336
Coverage diff
@@            Coverage Diff             @@
##        master     #7840       +/-##
==========================================
+ Coverage    90.33%    90.34%    +0.01%
==========================================
  Files          203       204        +1
  Tracked lines     26922     27134      +212
  Branches     10056     10130       +74
==========================================
+ Hits         24319     24514      +195
- Misses        2603      2620       +17
- Partials      1516      1523        +7

Generated by Coverage Action

wedamija and others added 2 commits October 2, 2026 15:53
Regenerating locally dropped cached releases of other packages. Restore them
and keep only the APScheduler entries.

Co-Authored-By: Claude <noreply@anthropic.com>
Move the APScheduler interval-to-schedule conversion into a private
helper in sentry_sdk.crons so other integrations can reuse it.

Co-Authored-By: Claude <noreply@anthropic.com>
wedamija and others added 3 commits October 2, 2026 17:40
Co-Authored-By: Claude <noreply@anthropic.com>
- Handle the Optional[str] check-in id that capture_checkin now returns.
- Send the monitor config only when the trigger's timezone is an IANA
  name. Sentry rejects names like "UTC+02:00", and without a timezone it
  would read the crontab as UTC.
- Open the check-ins only once the executor accepts the job, so runs
  skipped because of max_instances send none, instead of in_progress
  followed by error.
- Warn once per job when it has no explicit id and its name is used as
  the monitor slug.
- Say that only APScheduler 3.x is supported when the import fails.

Co-Authored-By: Claude <noreply@anthropic.com>
@linear-code

linear-code Bot commented Oct 21, 2026

Copy link
Copy Markdown

PY-3033

# Conflicts:
#	scripts/populate_tox/package_dependencies.jsonl
#	scripts/populate_tox/releases.jsonl
Comment thread sentry_sdk/integrations/apscheduler.py Resolved
Sentry monitor schedules don't end, so a finite cron or interval trigger
would raise missed check-ins after its last run.
Move arq's timezone name logic into sentry_sdk.crons.utils and use it for
APScheduler too, so fixed-offset timezones with whole-hour offsets are
monitored with Etc/GMT names. tzlocal's "local" zone is rejected.
Jitter delays runs by up to that many seconds, so add it on top of Sentry's
default 1 minute check-in margin.
@wedamija
wedamija marked this pull request as ready for review October 22, 2026 18:58
@wedamija
wedamija requested a review from a team as a code owner October 22, 2026 18:58
@wedamija
wedamija requested a review from sentrivana October 22, 2026 18:58
# Conflicts:
#	scripts/populate_tox/releases.jsonl
A run that dies inside the executor, like a crashed process pool worker,
goes through BaseExecutor._run_job_error, which dispatches no event. Close
that job's oldest pending check-in as an error there.
A job function wrapped in @sentry_sdk.monitor sends its own check-ins, so
monitoring it again reported every run twice. The decorator marks its
wrapper with _sentry_monitored, and the integration skips marked jobs.
With coalesce=False, a submit can carry several overdue run times, which
sent a burst of in_progress/ok pairs. Open one check-in per submit and
close it on the last run time's event.
With TZ set to a file path, tzlocal names the zone after the file's
basename, like "localtime", which Sentry rejects as an invalid monitor
config. Return a zone's name only when zoneinfo or pytz knows it.
APScheduler 4 fails the 3.x imports, which already raises DidNotEnable.
Comment thread sentry_sdk/integrations/apscheduler.py Resolved
Comment thread sentry_sdk/integrations/apscheduler.py Resolved
A cron coroutine wrapped in @sentry_sdk.monitor sends its own check-ins,
so monitoring it again reported every run twice. The arq wrapper keeps the
coroutine's attributes, so the _sentry_monitored marker stays visible.
Sentry's missed-run detection reports the slot, so a run APScheduler skips
for being past its misfire grace time gets no check-in, and its pending
state is dropped.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit fcdeaca. Configure here.

Comment thread sentry_sdk/integrations/apscheduler.py Resolved
A job that waits in a busy pool past its misfire grace time has already
sent in_progress when APScheduler reports it missed. Close that check-in
as error instead of leaving it to time out.
# Conflicts:
#	scripts/populate_tox/releases.jsonl
Comment thread sentry_sdk/integrations/apscheduler.py Resolved
With coalesce=False a backlog runs in one submit and sends one check-in,
closed by the last run's event. An earlier failed run now makes it an error.

@sentry-junior sentry-junior Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cron translation holds up. I checked the weekday mapping, */2 → 1-7/2, and the bail-outs for last, 1st mon, seconds, and day + day_of_week against cronsim, which Sentry uses to parse schedules. But this PR is doing too much and has picked up a lot of edge-case handling. Two asks overall:

  • Please split it. The arq changes (@wraps, skipping jobs that already use @monitor, stricter timezone names), the shared crons/utils helper, and the decorator marker can go in their own PR, with APScheduler stacked on top.
  • Please trim the listener down to the common case. Details are inline.

Comment thread sentry_sdk/integrations/apscheduler.py Resolved
Comment thread sentry_sdk/integrations/apscheduler.py Resolved
Comment thread sentry_sdk/integrations/apscheduler.py Outdated
Comment on lines +144 to +152
def finish_missed(self, job_id: str, run_time: "Any") -> None:
with self.lock:
if self.earlier.pop((job_id, run_time), None) is not None:
return
pending = self._pop((job_id, run_time))
# Send nothing, so Sentry reports the slot as missed, unless a run that
# waited in a busy pool already sent `in_progress`.
if pending is not None and pending.opened:
self._close(pending, MonitorStatus.ERROR)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A run that is missed after in_progress was sent is rare. Sentry already times out an open in_progress, so I'd drop this path.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to keep this one. Without it the user gets a timeout half an hour later, which looks like the job hung when it never actually ran. It's only a few lines once the rest of the listener state is gone.

Comment thread sentry_sdk/integrations/apscheduler.py Resolved
Comment thread sentry_sdk/integrations/apscheduler.py Resolved
Comment thread sentry_sdk/integrations/apscheduler.py Resolved
Comment thread sentry_sdk/integrations/apscheduler.py Resolved
Comment thread sentry_sdk/crons/utils.py Resolved
Comment thread tests/test_crons.py Resolved
@sentrivana

sentrivana commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

Hey @wedamija, we had Junior do a first pass on the PR, please have a look. We'll have a human review afterwards, probably next week 🙏🏻

@wedamija
wedamija changed the base branch from master to danf/crons-monitor-marker November 5, 2026 15:56
# Conflicts:
#	sentry_sdk/crons/utils.py
#	sentry_sdk/integrations/arq.py
Adding APSchedulerIntegration turns monitoring on, without a separate flag.
Pending check-ins are tracked by the last run time of each submit, and the
patch for crashed process pool workers is gone, since Sentry times out an open
in_progress check-in. Tests assert on captured check-ins only.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants