Configuration
The published config/alerts.php, without its comment blocks:
<?php
declare(strict_types=1);
use RoundlyConsulting\Alerts\Alert;
use RoundlyConsulting\Alerts\AlertSilence;
use RoundlyConsulting\Alerts\HealthCheck;
use RoundlyConsulting\Alerts\HealthCheckRun;
use RoundlyConsulting\Alerts\Jobs\HealthCheckJob;
return [
'health-check' => HealthCheck::class,
// Check classes (or instances) registered globally on boot.
'checks' => [
// RoundlyConsulting\Alerts\Checks\DatabaseCheck::class,
],
'alert' => Alert::class,
// Key type of your notifiable (owner) models: bigint | uuid | ulid
'key_type' => env('ALERTS_KEY_TYPE', 'bigint'),
'job' => HealthCheckJob::class,
// Auto-register the perform command on the scheduler.
'schedule' => [
'enabled' => env('ALERTS_SCHEDULE', true),
'frequency' => env('ALERTS_SCHEDULE_FREQUENCY', 'everyMinute'),
],
// Opt-in JSON status endpoint registered via Health::routes().
'route' => [
'uri' => env('ALERTS_ROUTE_URI', 'health'),
'name' => 'alerts.health',
'details' => env('ALERTS_ROUTE_DETAILS', false),
],
// Master switch for maintenance-window muting + the silence model.
'silence' => env('ALERTS_SILENCE', true),
'silence-model' => AlertSilence::class,
'silence-retention' => env('ALERTS_SILENCE_RETENTION', 7),
// Run history + latency tracking, retention, and the run model.
'history' => [
'enabled' => env('ALERTS_HISTORY', true),
'retention_days' => env('ALERTS_HISTORY_RETENTION', 30),
'model' => HealthCheckRun::class,
],
// Store (and mail) a thrown exception's message and frames — off by default.
'store_exception_details' => env('ALERTS_STORE_EXCEPTION_DETAILS', false),
// Optional global default escalation policy (data-only).
'escalation' => [
// 1 => 'owner', 3 => 'team', 5 => 'oncall',
],
];Every key
| Key | Default | Env | Purpose |
|---|---|---|---|
health-check | HealthCheck::class | — | Model that stores scheduled checks. |
checks | [] | — | Check classes (or instances) registered on boot. |
alert | Alert::class | — | Model that records alerts. |
key_type | bigint | ALERTS_KEY_TYPE | Key type of the notifiable_id columns on health_checks, alerts and alert_silences — bigint, uuid or ulid (case-insensitive; anything else throws InvalidConfigurationException). Read when the migrations run, so set it first. |
job | HealthCheckJob::class | — | Queued job dispatched for each due check. A blank value is not set and uses the packaged job. |
schedule.enabled | true | ALERTS_SCHEDULE | Auto-register alerts:perform-health-checks on the scheduler, onOneServer() with a 5-minute overlap lock. Pruning is scheduled either way while history is enabled. |
schedule.frequency | everyMinute | ALERTS_SCHEDULE_FREQUENCY | Scheduler method used for the command: everyMinute, everyTwoMinutes, everyThreeMinutes, everyFourMinutes, everyFiveMinutes, everyTenMinutes, everyFifteenMinutes, everyThirtyMinutes, hourly, everyOddHour, everyTwoHours, everyThreeHours, everyFourHours, everySixHours, daily, twiceDaily, weekly, monthly, quarterly or yearly. A blank value is not set (everyMinute); any other name throws when the scheduler is resolved. A coarser cadence still runs every check: each tick queues the checks that came due since the previous one. |
route.uri | health | ALERTS_ROUTE_URI | URI of the opt-in JSON status endpoint. A blank value is not set and uses health — never the site root; a non-string value throws. |
route.name | alerts.health | — | Route name of the status endpoint. A blank value is not set and uses the default; a non-string value throws. |
route.details | false | ALERTS_ROUTE_DETAILS | Also render message, tags, last_alert_at and muted for each check on the endpoint — an explicit allow-list: the raw alert meta never renders. Off by default: the endpoint is unauthenticated unless you add middleware, and with store_exception_details on a stored message can carry raw exception text. |
silence | true | ALERTS_SILENCE | Master switch for maintenance-window muting; false ignores every silence. While on, alerts:prune-silences is scheduled daily. |
silence-model | AlertSilence::class | — | Model that stores mute records. |
silence-retention | 7 | ALERTS_SILENCE_RETENTION | Days an ended silence is kept before alerts:prune-silences deletes it. Must be a whole number of at least 1; blank is not set (7); anything else throws, naming the key. |
history.enabled | true | ALERTS_HISTORY | Record every run (status + latency) and schedule alerts:prune-runs daily — also when schedule.enabled is off. |
history.retention_days | 30 | ALERTS_HISTORY_RETENTION | Days of run history kept before alerts:prune-runs deletes them. Must be a whole number of at least 1; blank is not set (30); anything else (five, 5.5, 0) throws instead of pruning, so a typo can never wipe the run history. |
history.model | HealthCheckRun::class | — | Model that records runs. |
store_exception_details | false | ALERTS_STORE_EXCEPTION_DETAILS | Store — and mail — a thrown exception’s message and stack frames, and the error a built-in check caught, on runs and alerts. Off by default: they can carry hosts, SQL and credentials, so the stored result names only the exception’s class and the throwable goes to your exception handler instead. |
escalation | [] | — | Global default escalation policy (threshold ⇒ group), applied to any check that declares none of its own. |
Environment
ALERTS_KEY_TYPE=bigint
ALERTS_SCHEDULE=true
ALERTS_SCHEDULE_FREQUENCY=everyMinute
ALERTS_ROUTE_URI=health
ALERTS_ROUTE_DETAILS=false
ALERTS_SILENCE=true
ALERTS_SILENCE_RETENTION=7
ALERTS_HISTORY=true
ALERTS_HISTORY_RETENTION=30
ALERTS_STORE_EXCEPTION_DETAILS=falseThe switches — schedule.enabled, route.details, silence, history.enabled and store_exception_details — accept booleans and the usual env strings: 1, true, on and yes turn them on; 0, false, off and no turn them off. A blank value (ALERTS_SCHEDULE=) is not set, so the switch keeps its default (off for route.details and store_exception_details, on for the rest). Any other value throws InvalidConfigurationException naming the key and the value, so a typo never silently flips a switch.
Every non-boolean setting is read strictly too: a key that is not set — absent, null or blank (a host’s KEY=) — takes its default, and any other invalid value throws InvalidConfigurationException naming the key. Nothing falls back silently.
Keep the checks array to class names. Configured instances such as DatabaseCheck::make('mysql') are better registered from a service provider — objects in a config file stop php artisan config:cache from serializing it.
Swapping models
Every model key may point at your own subclass of the packaged model. Each is resolved through a single seam, so a swapped model is honoured everywhere — relations, actions, commands and the report alike:
namespace App\Models;
use RoundlyConsulting\Alerts\HealthCheck as BaseHealthCheck;
class HealthCheck extends BaseHealthCheck
{
// your own scopes, relations, observers…
}// config/alerts.php
'health-check' => App\Models\HealthCheck::class,
'alert' => App\Models\Alert::class,
'silence-model' => App\Models\AlertSilence::class,
'history' => [
// …
'model' => App\Models\HealthCheckRun::class,
],A key that is not set — absent, null or blank — resolves the packaged model. Anything else must be the packaged model or a subclass of it — the package relies on its methods and scopes — otherwise it throws InvalidConfigurationException naming the key. A foreign class is never silently replaced.
Swapping the job
The job key sets the queued job dispatched for each due check. It is dispatched with the HealthCheck row as its only argument; the packaged HealthCheckJob is final, so write your own and run the row with Health::for($row->notifiable)->run($row) — for example to run monitoring on a dedicated queue. The pipeline behind it, RunHealthCheckAction, is internal; don’t call it directly:
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use RoundlyConsulting\Alerts\Facades\Health;
use RoundlyConsulting\Alerts\HealthCheck;
class MonitoringJob implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
public function __construct(public HealthCheck $healthCheck)
{
$this->onQueue('monitoring');
}
public function handle(): void
{
// Runs this scheduled row as-is, through the same pipeline as HealthCheckJob.
Health::for($this->healthCheck->notifiable)->run($this->healthCheck);
}
}
// config/alerts.php
'job' => App\Jobs\MonitoringJob::class,Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.