On this page
Module health checks
Report each module's status and combine them into one view of system health. Use it for /health HTTP endpoints,
container orchestrators and the doctor CLI.
Quick start
1. Implement ModuleHealthCheckInterface
use Gacela\Framework\Health\HealthStatus;
use Gacela\Framework\Health\ModuleHealthCheckInterface;
final class DatabaseHealthCheck implements ModuleHealthCheckInterface
{
public function __construct(private readonly PDO $pdo) {}
public function checkHealth(): HealthStatus
{
$this->pdo->query('SELECT 1');
return HealthStatus::healthy('Database operational');
}
public function getModuleName(): string
{
return 'Database';
}
}2. Register the check
Register it in gacela.php. The doctor command then runs it, next to its cache-staleness, suffix-mismatch, and
filename-mismatch checks:
<?php # gacela.php
return function (GacelaConfig $config) {
$config->addHealthCheck(DatabaseHealthCheck::class);
$config->addHealthCheck(new CacheHealthCheck($redis));
};3. Run the checks
use Gacela\Framework\Health\HealthChecker;
$checker = new HealthChecker([
new DatabaseHealthCheck($pdo),
new CacheHealthCheck($redis),
]);
$report = $checker->checkAll();Or run the CLI:
vendor/bin/gacela doctorPass an optional namespace filter to limit the module checks. In CI, use vendor/bin/gacela doctor --strict so
warnings also produce a failing exit code.
Several checks per module Since 2.1
Several checks may report under the same getModuleName(). Gacela combines them into one module result with the
worst level reported, and keeps each individual status under that result's health_checks metadata. A later healthy
check cannot hide an earlier degraded or unhealthy one. Before 2.1 it could: results were keyed by module name, so the
last check to run overwrote the ones before it.
Status levels
| Level | When to use |
|---|---|
healthy |
Everything works as expected |
degraded |
Works but slow or using fallbacks |
unhealthy |
Critical failure |
HealthStatus::healthy('API responding in 50ms');
HealthStatus::degraded('High latency', ['avg_ms' => 500]);
HealthStatus::unhealthy('Unreachable', ['retries' => 3]);HTTP endpoint
public function healthCheck(): Response
{
$report = $this->healthChecker->checkAll();
$status = match ($report->getOverallLevel()) {
HealthLevel::HEALTHY, HealthLevel::DEGRADED => 200,
HealthLevel::UNHEALTHY => 503,
};
return new JsonResponse($report->toArray(), $status);
}$report->toArray():
[
'overall' => 'degraded',
'modules' => [
'Database' => ['level' => 'healthy', 'message' => '...', 'metadata' => [...]],
'PaymentAPI' => ['level' => 'degraded', 'message' => '...', 'metadata' => [...]],
],
]Report API
$report->isHealthy(); // bool
$report->hasUnhealthyModules(); // bool
$report->getOverallLevel(); // HealthLevel
$report->getResults(); // array<string, HealthStatus>
$report->getResultsByLevel(HealthLevel::UNHEALTHY);
$report->toArray();Best practices
- Be fast: finish each check in under a second. Prefer a quick ping (
SELECT 1) over a full query. - Include metadata: latency, error codes and retry counts help diagnose problems.
- Let exceptions propagate:
HealthCheckerconverts anyThrowableinto anunhealthyresult with exception, file, and line metadata. - Pick the right level: keep
unhealthyfor real outages. Usedegradedfor slow but working.
API reference
ModuleHealthCheckInterface
public function checkHealth(): HealthStatus;
public function getModuleName(): string;HealthStatus
HealthStatus::healthy(string $message = 'Module is healthy', array $metadata = []): self
HealthStatus::degraded(string $message, array $metadata = []): self
HealthStatus::unhealthy(string $message, array $metadata = []): self
$status->level; // HealthLevel
$status->message; // string
$status->metadata; // array
$status->isHealthy(): bool
$status->isDegraded(): bool
$status->isUnhealthy(): bool
$status->toArray(): arrayHealthChecker
$checker->checkAll(): HealthCheckReport
$checker->count(): int