ongr/monitoring-bundle
A Symfony bundle that adds health-check endpoints to your application, letting external monitoring tools query the status of services and dependencies. Useful for wiring up availability checks and exposing readiness information to uptime systems and dashboards.
Read this first: it is a Symfony bundle, not a Laravel package. There is no ServiceProvider, no config/monitoring.php, no facades, no artisan command. The bundle registers a Bundle subclass into the Symfony kernel, ships a controller plus a route, and aggregates a set of "check" services into a single HTTP health/status response. With 1 star, 0 dependents, and a score of ~1.75, treat it as a reference implementation to read, not a dependency to composer require into a Laravel app — its PHP 7 / Symfony 2–4 constraints will collide with a modern Laravel dependency graph.
If you are evaluating it inside its native habitat (an ONGR/Symfony app), the minimal path is:
composer require ongr/monitoring-bundle (pin the version; expect old Symfony constraints).new Ongr\Bundle\MonitoringBundle\OngrMonitoringBundle() in AppKernel.phpOngr\Bundle\MonitoringBundle\OngrMonitoringBundle::class => ['all' => true] in config/bundles.php# config/routes/ongr_monitoring.yaml
ongr_monitoring:
resource: "@OngrMonitoringBundle/Resources/config/routing.yml"
prefix: /_monitor
/_monitor — expect 200 when all registered checks are healthy, non-200 otherwise.Where to look first. The package is small; read vendor/ongr/monitoring-bundle/ end to end in an hour. Three files carry the whole contract: the check/monitor interface (whatever it is named in your version), the aggregator service that collects tagged checks, and the controller that renders the aggregate. Everything else is DI plumbing.
First use case. A single liveness/readiness URL for a load balancer or uptime pinger, replacing GET / with GET /_monitor. That is the entire day-to-day value: one cheap endpoint that fans out to dependency checks (DB, Elasticsearch cluster health, cache, queue depth) and collapses them into a status code.
If you are on Laravel — which is where this assessment applies — skip steps 1–2 entirely. The equivalent first move is php artisan pulse:check / a /up route backed by Illuminate\Foundation\Configuration\Application::configure()->withRouting(health: '/up'), and read this bundle's aggregator + check interface purely as a design sketch before writing your own ~100-line version.
The check interface is the real product. Whether you install it or port it, the pattern you take away is: one small interface, one method, a structured result (status + message), and a tag so the container collects implementations automatically. Reproduce that shape and the rest is trivial.
// Sketch of a check, regardless of framework
class ElasticsearchCheck
{
public function __construct(private Client $client) {}
public function check(): array
{
try {
$health = $this->client->cluster()->health();
return [
'status' => $health['status'] === 'red' ? 1 : 0,
'message' => $health['status'],
];
} catch (\Throwable $e) {
return ['status' => 1, 'message' => $e->getMessage()];
}
}
}
Wiring pattern (Symfony). Register each check as a service and apply the bundle's tag (or the !tagged / !tagged_iterator argument in services.yaml for newer Symfony). The aggregator injects the tagged iterable and runs every check; the controller maps aggregate → HTTP status.
Wiring pattern (Laravel port). If you decide the check set is worth keeping, re-express it natively rather than importing the bundle:
// app/Providers/MonitoringServiceProvider.php
public function register(): void
{
$this->app->singleton(HealthAggregator::class, function ($app) {
return new HealthAggregator([
new Checks\DatabaseCheck(),
new Checks\CacheCheck(),
new Checks\QueueCheck(),
new Checks\ElasticsearchCheck($app->make(Client::class)),
]);
});
}
// routes/web.php
Route::get('/_monitor', function (HealthAggregator $agg) {
$results = $agg->run(); // never throws
$healthy = collect($results)->every(fn ($r) => $r['status'] === 0);
return response()->json($results, $healthy ? 200 : 503);
})->middleware('throttle:60,1');
Workflow patterns that actually pay off day to day:
/_monitor shallow (process is alive, config loaded) for the LB, and expose a ?deep=1 variant with the expensive dependency checks for humans and on-call dashboards. A health endpoint that queries Elasticsearch on every LB poll will page you for a slow cluster and amplify the slowdown.critical (DB unreachable → 503, pull from rotation) from degraded (queue backlog → still 200, but log/alert). Collapsing everything into one boolean is the most common reason these endpoints get ignored after the first false alarm.readinessProbe, or a plain curl --fail in CI smoke tests, with no body parsing.try/catch, return an unhealthy result rather than propagating, and put explicit timeouts on every network call inside a check. An exception inside a health check that 500s the endpoint is a self-inflicted outage.parameters.yml has to become config/monitoring.php + env() in a Laravel port. Nothing transfers automatically.EventDispatcherInterface contract, so listener classes may partially work — but tagged registration, priorities, and the bundle's event names won't. Don't count on drop-in listener reuse.composer.json pinned to PHP 7.x and Symfony 2/3/4-era constraints. If you find yourself reaching for --ignore-platform-reqs or constraint relaxation, stop — that's the signal to port instead of install./up health route cover the same ground with zero ownership cost. Read this bundle's check/aggregator split, copy the idea in ~100 lines you can test, and move on.How can I help you explore Laravel packages today?