Skip to main content

cronkit

A local tracker for batch cron jobs, like a small Sentry. cronkit handles locking and run tracking, records each Item your job processes and every error it hits, and reports success ratios, recurring errors, Stuck Items and Resolved failures from a SQLite database on the host.

cronkit sends nothing itself. It gives you Alerts and Digests as data, and you deliver them however you like (SMTP, HTTP, Slack, …).

Terms are defined in CONTEXT.md.

Installation

pip install -e /path/to/cronkit

Python 3.11+, Linux, no runtime dependencies.

Quickstart

#!/usr/bin/env python3
import sys
from cronkit import cron_runner, item, record_error
from cronkit.errors import Skip, SoftFail, HardFail

JOB = "sync_orders"


def worker(run_id: int) -> None:
    orders = fetch_pending_orders()
    if not orders:
        raise Skip("nothing to process")          # whole Run skipped

    for order in orders:
        with item(order.id):                      # one Item per retryable unit
            if not order.ready:
                raise SoftFail("upstream not ready")  # expected to clear later
            if order.corrupt:
                raise HardFail("bad payload")         # needs a human
            process(order)                        # any other exception → hard


if __name__ == "__main__":
    sys.exit(cron_runner(JOB, worker, schedule="*/5 * * * *"))

item() records the outcome and swallows the exception, so the loop continues with the next order.

Several errors on one Item

When one Item contains nested work (e.g. several sessions in one record), record each problem as its own Error so each one gets its own Fingerprint:

with item(record.id):
    for session in record.sessions:
        try:
            handle(session)
        except Exception as e:
            record_error(e)          # attached to the current Item
    ...                              # then raise SoftFail/HardFail for the Item's outcome

Outcomes

Raised Item outcome Counts as failure Alerted
nothing ok — —
Skip skip no no
SoftFail soft yes no (unless Stuck and escalated)
HardFail hard yes yes
other hard yes yes

If an exception escapes outside any Item, the Run is Crashed, which is always alerted. If the previous Run still holds the lock, the new Run is recorded as Locked out and exits 0.

Alerts

Pass a callback. It runs once at the end of any Run that Crashed or had hard failures:

import smtplib
from email.message import EmailMessage


def send_alert(alert) -> None:
    msg = EmailMessage()
    msg["Subject"] = f"[{alert.env}] {alert.job}: {len(alert.items)} hard failure(s)"
    msg["From"] = "cron@example.com"
    msg["To"] = "ops@example.com"
    msg.set_content(alert.render())
    with smtplib.SMTP("localhost") as s:
        s.send_message(msg)


sys.exit(cron_runner(JOB, worker, on_alert=send_alert))

Digests

A Digest is data (build_digest) plus a template (render_digest). To email one daily or weekly, run it as an ordinary cronkit Job:

from cronkit import build_digest, cron_runner, render_digest


def worker(run_id: int) -> None:
    send_mail("Weekly cron digest", render_digest(build_digest(days=7)))


sys.exit(cron_runner("weekly_digest", worker, schedule="0 9 * * 1"))

Retries

from cronkit import with_retry

with_retry(lambda: call_external_api(), retries=5, backoff=1.5)

Each retried attempt is logged and stored against the current Item; the Item only fails if the last attempt does.

CLI

cronkit list                         # known Jobs
cronkit summary [job] [--days N]     # Item counts and success ratio
cronkit failures [job] [--days N]
cronkit dangling [job] [--days N]     # errors recorded outside any Item
cronkit errors [job] [--show FP]     # recurring errors by Fingerprint
cronkit retries [job]                # retried attempts, rescued vs exhausted
cronkit latest [job]
cronkit stuck [job]
cronkit digest [job] [--days N]      # print the Digest

Every command accepts --json.

Configuration

Variable Default Description
APP_ENV dev Included in Alerts/Digests
CRONKIT_TZ UTC Display timezone
CRON_TRACKER_DB /var/log/cronkit/cron_tracker.db SQLite tracker path
CRON_LOG_DIR /var/log/cronkit Log file directory
CRON_LOCK_DIR /var/lock/cronkit Lock file directory
CRON_LOG_SOURCE 1 0 hides the logger name (Job / cronkit.* / library) in log lines

The default paths require root. Create them in advance and chown them to the cron user:

sudo mkdir -p /var/log/cronkit /var/lock/cronkit
sudo chown $USER:$USER /var/log/cronkit /var/lock/cronkit

Logging

cronkit sets up logging automatically. Logs go to stderr and to one file per ISO week, {CRON_LOG_DIR}/{job}.{YYYY}-W{WW}.log (e.g. sync_orders.2026-W39.log). Timestamps and week boundaries are UTC. Each process picks its file when it starts and never renames it, so a Run's lines always stay in one file. cronkit never deletes log files; clean them up with your host's usual policy (logrotate, find -mtime, …). In your job:

import logging
logger = logging.getLogger(JOB)

Metadata

Release files for cronkit 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cronkit 0.2.0
File Size Uploaded
cronkit-0.2.0.tar.gz 30.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for cronkit 0.2.0
File Interpreter ABI Platform
cronkit-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 57.6 kB

Release files / cronkit-0.2.0.tar.gz

Download URL cronkit-0.2.0.tar.gz
Size 30.4 kB
Tags Source
SHA-256 checksum
How to use checksums
ca9bef0fc74d0486600066c8103f062e28d689c7de69f08cb6f1e78f7e1e5b03
BLAKE2b-256 checksum
How to use checksums
1f15c6c4efe58f8ee25477bdce7825315b58ee7dc026c09722f248b9390c26de
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release files / cronkit-0.2.0-py3-none-any.whl

Download URL cronkit-0.2.0-py3-none-any.whl
Size 27.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
65171968037a568c7ce9f7dbaafa0debdef8e980462af5abdb4a0e2a500d3eb7
BLAKE2b-256 checksum
How to use checksums
38e9ac6c174323b4015197fd4bd64442059de8a0515bf53bc8182a0e2df59148
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page