cronradar / php
Dead-simple cron job monitoring with auto-registration. One primary function: monitor(). Advanced: sync(). Self-healing monitors.
Requires
- php: ^8.0
- ext-curl: *
- ext-json: *
Requires (Dev)
- phpunit/phpunit: ^10.0
README
The base SDK for monitoring scheduled jobs in PHP. Wrap a callable, send a heartbeat, or track full lifecycle — CronRadar tracks duration, alerts on missed schedules, and recovers from accidental monitor deletion.
For Laravel scheduler auto-discovery, install the cronradar/laravel extension on top of this base.
Install
composer require cronradar/cronradar
PHP 8.1+ supported.
Setup
The SDK reads its API key from the CRONRADAR_API_KEY environment variable.
export CRONRADAR_API_KEY=ck_app_xxxxxxxxxxxxxxxxxxxx
Or in .env:
CRONRADAR_API_KEY=ck_app_xxxxxxxxxxxxxxxxxxxx
Get the API key from the CronRadar dashboard at app.cronradar.com under your application's settings. API keys have the form ck_app_<random>.
The SDK does not require explicit initialization. The first call reads the env var; if it's missing, the SDK logs a single warning to error_log and silently no-ops every subsequent call (per the "never break a user's job" guarantee).
Quickstart
<?php use CronRadar\CronRadar; // First-time call: pass schedule so the monitor auto-registers. CronRadar::monitor('daily-backup', '0 2 * * *'); // Subsequent calls: just the key. CronRadar::monitor('daily-backup');
That's the minimal integration. CronRadar:
- Creates the
daily-backupmonitor on the first ping (becauseschedulewas provided) - Records each subsequent ping
- Alerts you (email + any other configured channels) if the monitor doesn't ping within
0 2 * * *plus its grace period - Re-creates the monitor if you delete it from the dashboard and the schedule param is still being passed
Manual lifecycle
For finer-grained tracking — duration measurement, explicit failure messages, hung-job detection — use the lifecycle endpoints.
Wrapper (recommended)
<?php use CronRadar\CronRadar; $backupJob = CronRadar::wrap('daily-backup', fn() => runBackup(), '0 2 * * *'); $result = $backupJob();
The wrapper:
- Calls
startJobbefore invoking your callable - Calls
completeJobif it returns - Calls
failJobwith the exception message if it throws - Re-throws the original exception so your error handlers run normally
- Auto-registers the monitor on first run if
scheduleis provided
Manual call style
<?php use CronRadar\CronRadar; CronRadar::startJob('daily-backup'); try { runBackup(); CronRadar::completeJob('daily-backup'); } catch (\Throwable $e) { CronRadar::failJob('daily-backup', $e->getMessage()); throw $e; // always re-throw — monitoring observes, never alters behavior }
Reference
CronRadar::monitor(string $monitorKey, ?string $schedule = null): void
Records a successful execution.
CronRadar::monitor('daily-backup'); CronRadar::monitor('daily-backup', '0 2 * * *');
| Parameter | Type | Required | Notes |
|---|---|---|---|
$monitorKey |
string |
yes | Monitor key. Lowercase, kebab/snake/dot-case. Max 200 chars. |
$schedule |
?string |
optional | Standard 5-field cron expression. Required on the first ping for self-healing registration. |
Throws: never. Network errors and 4xx/5xx responses are caught and logged via error_log.
Grace period is configured per-monitor on CronRadar (default 60 seconds; set via the dashboard or the gracePeriod argument to syncMonitor). It's not a per-call argument.
CronRadar::startJob(string $key): void
Records that a job has begun executing.
CronRadar::startJob('daily-backup');
Used in conjunction with completeJob or failJob to measure duration and detect hung jobs.
CronRadar::completeJob(string $key): void
Records successful completion.
CronRadar::completeJob('daily-backup');
Pair with a prior startJob($key) to record duration in the dashboard.
CronRadar::failJob(string $key, ?string $message = null): void
Records explicit failure.
CronRadar::failJob('daily-backup', 'Database connection refused');
| Parameter | Type | Required | Notes |
|---|---|---|---|
$key |
string |
yes | Monitor key. |
$message |
?string |
optional | Human-readable failure detail, shown in the dashboard. |
Triggers an immediate alert (no grace period). The message appears in the alert payload.
CronRadar::wrap(string $monitorKey, callable $fn, ?string $schedule = null): callable
Wraps a callable with full lifecycle tracking. Returns a callable that, when invoked, calls $fn with start/complete/fail tracking.
$wrapped = CronRadar::wrap('daily-backup', fn() => runBackup(), '0 2 * * *'); $result = $wrapped();
| Parameter | Type | Required | Notes |
|---|---|---|---|
$monitorKey |
string |
yes | Monitor key. |
$fn |
callable |
yes | The callable to instrument. |
$schedule |
?string |
optional | Cron expression for self-healing registration. |
The wrapped callable returns whatever $fn returns. If $fn throws, the wrapper re-throws after recording the failure. Monitoring never swallows your exceptions.
CronRadar::syncMonitor(string $key, string $schedule, ?string $source = null, ?string $name = null): void
Pre-registers a monitor without sending a ping. Used by the cronradar/laravel extension during application startup.
CronRadar::syncMonitor('daily-backup', '0 2 * * *', 'laravel', 'Daily Backup');
| Parameter | Type | Required | Notes |
|---|---|---|---|
$key |
string |
yes | Monitor key. |
$schedule |
string |
yes | Cron expression. |
$source |
?string |
optional | Identifier for the framework or origin. |
$name |
?string |
optional | Human-readable display name. |
Used internally by extensions; rarely needed in application code.
Configuration
| Environment variable | Required | Default | Purpose |
|---|---|---|---|
CRONRADAR_API_KEY |
yes | — | API key from the CronRadar dashboard. Format ck_app_xxxxx. |
CRONRADAR_DEBUG |
no | false |
Set to true to log every request and error via error_log. |
The base URL (https://cron.life), HTTP timeout (5 seconds), and default grace period (60 seconds) are constants in src/Constants.php. To customize them, fork the SDK or open an issue for a configurable hook.
Error handling
The SDK upholds two hard guarantees:
- Never throws to user code. Network errors, timeouts, 4xx/5xx responses, malformed payloads — all caught and logged via
error_log. A failing CronRadar API must not break a user's cron job. - Re-throws user-job exceptions. When using
wrap, the original exception from your callable always propagates. Monitoring observes; it does not change behavior.
What this looks like at runtime:
| Situation | SDK behavior | Your code |
|---|---|---|
| Network unreachable | Logs to error_log; returns | Continues normally |
| 5-second timeout | Logs to error_log; returns | Continues normally |
| 401 Unauthorized | Logs to error_log; returns | Continues normally |
| Wrapped callable throws | Records failJob; re-throws |
Receives the original exception |
CRONRADAR_API_KEY missing |
Logs once per process; subsequent calls no-op | Continues normally |
If you need to know whether a ping succeeded — for example, in tests — every static method returns void either way; success is silent. Set CRONRADAR_DEBUG=true to log every request and error via error_log.
Troubleshooting
401 Unauthorized in error_log
Your CRONRADAR_API_KEY is wrong or missing. Verify:
echo $CRONRADAR_API_KEY # should print ck_app_xxxxx
The key must match the one shown on app.cronradar.com → your application → Settings → API Keys. Keys are not retrievable after creation — if you lost it, create a new one and rotate.
404 Monitor Not Found on first ping
You called monitor($key) without a $schedule for a brand-new key. The first ping must include $schedule so CronRadar can register the monitor:
CronRadar::monitor('daily-backup', '0 2 * * *');
Subsequent pings can omit $schedule. If you delete the monitor from the dashboard and the next ping doesn't include $schedule, you'll see the same 404.
My cron expression is rejected
CronRadar uses the standard 5-field POSIX cron format: minute hour day-of-month month day-of-week. Six-field expressions (with seconds) and Quartz-style 7-field expressions are not accepted. Common pitfalls:
| Wrong | Right |
|---|---|
0 0 2 * * * (6 fields) |
0 2 * * * |
0 2 ? * MON-FRI (Quartz ?) |
0 2 * * 1-5 |
The job runs but I never see a ping in the dashboard
Three things to check, in order:
- API key — see "401 Unauthorized" above.
- Network — outbound HTTPS to
https://cron.lifemay be blocked by firewall. - Process termination — short-lived CLI scripts using fire-and-forget transports may exit before the HTTP request completes. The default transport is synchronous so this is uncommon.
I want to see what the SDK is doing in development
By default the SDK is silent. To trace every request and error:
CRONRADAR_DEBUG=true php my_job.php
If CRONRADAR_API_KEY is unset entirely, the SDK no-ops every call without logging.
Links
- Documentation: docs.cronradar.com
- Agent-friendly index: docs.cronradar.com/llms.txt
- OpenAPI spec: api.cronradar.com/swagger/v1/swagger.json
- Packagist: packagist.org/packages/cronradar/cronradar
- GitHub: github.com/cronradar/cronradar-php
- Laravel extension: packagist.org/packages/cronradar/laravel
- Support: support@cronradar.com