romalytar/yammi-jobs-monitoring-laravel
Real-time queue monitoring & observability for Laravel. Dashboard for job runs, retries, failures, DLQ, stats, worker heartbeat, scheduled task outcomes, duration anomalies, and alerts (Slack/email/webhooks). Works with Redis, SQS, database, or sync—no extra infra.
Real-time monitoring and observability for Laravel queues. See exactly what happens during job execution — retries, failed jobs, a dead-letter queue, recurring failure patterns, scheduled-task outcomes, worker health, duration anomalies and alerts — across any queue driver: Redis, Amazon SQS, database or sync.
Install, run the migration, open /jobs-monitor. No extra infrastructure, sensible defaults, optional config.
| Dark | Light |
|---|---|
![]() |
![]() |
composer require romalytar/yammi-jobs-monitoring-laravel
php artisan migrate
Open /jobs-monitor. Config is optional — defaults are sensible.
^8.1^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0Summary cards (total / processing / processed / failed / retry rate) and a time-series chart that refresh automatically — no page reload needed.
Period pills, search by job class, and four dropdowns (Status, Queue, Connection, Failure category) combine freely. Queue and connection values are populated from real data; active filters appear as chips you can dismiss one at a time or clear all at once.
Click any row to expand the full attempt timeline — every retry, its status, exception, failure tag, and duration in one place.

Top failing classes, slowest jobs, retry rate, and a per-class breakdown on one page.

Jobs that exhausted retries or hit a permanent / critical failure
land here. Each row has a three-dots menu:
Bulk operations: select rows (or "select all N matching" across every page), then retry or delete in one click. Requests are chunked client-side so batches of thousands finish without timeouts.

| Category | Meaning | Examples |
|---|---|---|
transient |
Retry likely helps | timeout, deadlock, 429 |
permanent |
Retry won't help | validation, type error |
critical |
Code is broken | class not found, parse error |
unknown |
No pattern matched | anything else |
Bring your own classifier:
// config/jobs-monitor.php
'failure_classifier' => \App\Monitoring\MyClassifier::class,
Any class implementing
Yammi\JobsMonitor\Domain\Job\Contract\FailureClassifier works.
Exceptions are automatically grouped by a stable fingerprint (exception class + normalized stack trace). Each group shows occurrence count, affected job classes, first/last seen, and a sample stack trace.
Per-group actions from the same three-dots menu: retry all jobs in the group, delete all, or inspect the sample trace. Alert rules can target a fingerprint directly so you get notified when the same error recurs.

Every scheduled task is tracked — start time, outcome, duration, and any output or exception. The package detects:
Outcome reports land in the Alerts channels so on-call knows which tasks succeeded and which need attention.

Workers send a heartbeat on each polling cycle. The dashboard shows:
A watchdog command (jobs-monitor:worker-watchdog) fires alerts when
workers disappear.

Statistical baselines are built per job class from historical duration data. When a job runs significantly shorter or longer than its baseline the package flags it:
Baselines refresh automatically on a configurable cron. Anomalies appear in the dedicated UI page and trigger alerts.
| Anomalies overview | Detail |
|---|---|
![]() |
![]() |
Five delivery channels — Slack, Email, PagerDuty, Opsgenie, Webhook — all fire together for every trigger. Alerts include deep links back to the exact page and row that triggered them. Resolve semantics: when the condition clears, a recovery message is sent automatically.
Built-in triggers (four ship enabled by default):
| Trigger | What fires it |
|---|---|
| Failure rate | % failed jobs in a window exceeds threshold |
| Failure category | permanent / critical count exceeds threshold |
| DLQ size | Dead-letter count exceeds threshold |
| Worker silent | A worker hasn't heartbeated within tolerance |
| Scheduled silent | A scheduled task produced no outcome |
| Duration anomaly | Job duration deviates from baseline |
Minimum setup:
JOBS_MONITOR_ALERTS_ENABLED=true
JOBS_MONITOR_SLACK_WEBHOOK=https://hooks.slack.com/services/...
JOBS_MONITOR_ALERT_MAIL_TO=ops@acme.com
JOBS_MONITOR_PAGERDUTY_KEY=...
JOBS_MONITOR_OPSGENIE_KEY=...
JOBS_MONITOR_WEBHOOK_URL=https://your-endpoint.example.com/hook
JOBS_MONITOR_WEBHOOK_SECRET=... # HMAC-SHA256 signature

Operational config lives in the database so operators can tune the package without a redeploy. 23 settings across 7 groups:
| Group | Settings |
|---|---|
| General | store_payload, retention_days, max_tries |
| Bulk operations | max_ids_per_request, candidate_limit |
| Scheduler monitoring | enabled, watchdog, watchdog tolerance |
| Duration anomaly | enabled, min_samples, short/long factor |
| Outcome reports | enabled |
| Worker heartbeat | enabled, interval, silent threshold, retention, cron |
| Alerts schedule | enabled, cron, queue |
Resolution order: DB row → config value → package default. Each setting shows its source badge. Reset to defaults clears all DB overrides in one click.
Secrets and boot-time config (webhook URLs, API keys, middleware) stay
in .env / config forever.
| General settings | Database connection |
|---|---|
![]() |
![]() |

Three facades — YammiJobs (reads), YammiJobsManage (retries/deletes),
YammiJobsSettings (settings & rules) — let you drive the entire
dashboard programmatically. Every available method is browsable and
executable from /jobs-monitor/settings/playground: pick a method,
fill the auto-generated form, press Run, see the JSON result.

php artisan jobs-monitor:prune --days=30
$schedule->command('jobs-monitor:prune')->daily();
// config/jobs-monitor.php
return [
'enabled' => env('JOBS_MONITOR_ENABLED', true),
// Store raw job payload (required for DLQ edit & retry).
// Sensitive keys are auto-masked: password, token, secret, api_key,
// authorization, credit_card, cvv, ssn.
'store_payload' => env('JOBS_MONITOR_STORE_PAYLOAD', false),
'failure_classifier' => null, // FQCN or null for built-in
'retention_days' => env('JOBS_MONITOR_RETENTION_DAYS', 30),
'max_tries' => env('JOBS_MONITOR_MAX_TRIES', 3),
'dlq' => [
// Gate ability consulted before retry / delete.
'authorization' => env('JOBS_MONITOR_DLQ_GATE'),
],
'ui' => [
'enabled' => env('JOBS_MONITOR_UI_ENABLED', true),
'path' => env('JOBS_MONITOR_UI_PATH', 'jobs-monitor'),
'middleware' => ['web'],
],
'alerts' => [
'enabled' => env('JOBS_MONITOR_ALERTS_ENABLED', false),
],
];
'ui' => [
'middleware' => ['web', 'auth', 'can:viewJobsMonitor'],
],
// AppServiceProvider
Gate::define('manage-jobs-monitor', function ($user, string $action) {
// $action is 'retry' or 'delete'
return $user->hasRole('admin');
});
JOBS_MONITOR_DLQ_GATE=manage-jobs-monitor
php artisan vendor:publish --tag=jobs-monitor-config
php artisan vendor:publish --tag=jobs-monitor-views
php artisan vendor:publish --tag=jobs-monitor-migrations
store_payload explicitly.JOBS_MONITOR_DLQ_GATE before running.auth or a Gate; anonymous
access is blocked by default.MIT
How can I help you explore Laravel packages today?