Search by

letflow / laravel-api-status

ignacio.alles

Provide status and health api endpoints

Package info

gitlab.com/letflow/laravel-api-status

Issues

pkg:composer/letflow/laravel-api-status

Statistics

Installs: 11 698

Dependents: 0

Suggesters: 0

Stars: 0

v1.6.0 2026-09-07 18:39 UTC

README

This package creates two routes /status and /health to expose API status and health checks.

Health check results

Each check reports a status, plus time as the measured duration in seconds. Durations are measured with hrtime(), which is monotonic, so an NTP correction during a check cannot distort them.

statusmeaningaffects the HTTP status
okthe check passedno
failedthe check failedyes, /health returns 500
snoozedthe check failed while silencedno
skippedthe check was not run, because it is silenced in skip modeno

A check that took longer than its timeout also carries slow: true. The flag never changes status: a slow check that passed is still ok.

Silencing alarms during a deploy

When a planned change makes a check fail predictably — restarting the FTP server that an FTP check watches, for instance — silence it instead of getting paged:

# Silence every check for the default duration, running them but hiding failures
php artisan api-status:snooze

# Do not even run the ftp check for 15 minutes
php artisan api-status:snooze ftp --skip --minutes=15 --reason="ftp restart"

# Every check whose name starts with ftp, until a given time
php artisan api-status:snooze 'ftp*' --until="2026-09-07 18:00"

# See what would happen, without writing anything
php artisan api-status:snooze ftp --minutes=15 --dry-run

# The deploy is over: remove every silence
php artisan api-status:unsnooze

# Or just one, or a pattern
php artisan api-status:unsnooze ftp
php artisan api-status:unsnooze 'ftp*' --dry-run

Two modes:

  • snooze (the default) runs the check and reports its real result, but a failure comes back as snoozed instead of failed, so /health stays 200.
  • --skip does not run the check at all. Use it when the resource is known to be down and waiting for its timeout is pointless.

While a silence is active, the check also reports snoozed_until, and snooze_reason if one was given.

Silencing something already silenced overwrites it: the mode becomes the new one and the expiry is recomputed from now. The command prints what it replaced.

A silence lives in the cache and expires on its own. Notes on that:

  • A per-check silence wins over the global one.
  • If the cache is unavailable there is no silence, and checks fail exactly as they would otherwise. A check that verifies the cache therefore keeps failing when the cache is broken, which is the point.
  • The commands, unlike the checks, fail loudly (exit code 1) when they cannot reach the cache, so a silence never looks set when it is not.

Configure the defaults in config/api-status.php under snooze: which cache store holds the keys, their prefix, and the default duration in minutes.

Timeouts

Every check accepts timeout, in seconds, defaulting to api-status.timeout (10). On its own it only drives the slow flag.

To make a driver actually honour a timeout, declare the overrides it needs under connection_config. They are merged onto the underlying connection config, and the check runs against an ephemeral connection built from the result:

'checks' => [
    'db' => [
        'driver' => 'db',
        'connection' => 'mysql',
        'timeout' => 3,
        'connection_config' => [
            'options' => [PDO::ATTR_TIMEOUT => 3],
        ],
    ],
    'cache' => [
        'driver' => 'cache',
        'store' => 'redis',
        'timeout' => 3,
        'connection_config' => [
            'read_timeout' => 3,
        ],
    ],
],

There is no uniform API to impose a timeout, because every driver names it differently: phpredis uses timeout and read_timeout, predis timeout and read_write_timeout, memcached Memcached::OPT_CONNECT_TIMEOUT, PDO PDO::ATTR_TIMEOUT. Declaring them per check avoids a mapping table that would guess at other libraries' internals and go stale.

connection_config is opt-in, and worth knowing what it costs:

  • Without it, checks reuse the application's connection, as they always have.
  • With it, each request to /health opens a new connection, with its TCP and authentication handshake. In exchange it exercises the connect path for real, which is what a connection timeout is about.
  • http does not use it: it already honours timeout directly.
  • disk and storage cannot honour a timeout at all. disk_free_space() and is_readable() on a hung NFS mount cannot be interrupted from PHP, so for those two the timeout is only ever informative.
  • On a queue check, the ephemeral connection stays memoised: QueueManager has no method to forget it in Laravel 12 or 13. The name is fixed, so it is one retained object per process, worth knowing under Octane.

One caveat about the db check: Connection::getPdo() returns an already resolved PDO without touching the network. If something opened the connection earlier in the process, the check passes without proving anything. Setting connection_config on it also fixes that, because the ephemeral connection is forced to be new.