Files
biiproject-kit-v1/app/Http/Controllers/Api/HealthController.php
T

114 lines
3.6 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Queue;
use OpenApi\Attributes as OA;
class HealthController extends Controller
{
#[OA\Get(
path: '/health',
operationId: 'healthCheck',
tags: ['System'],
summary: 'Application health check',
description: 'Returns status of database, Redis, storage, and queue. Returns 503 if any check fails.',
responses: [
new OA\Response(
response: 200,
description: 'All systems healthy',
content: new OA\JsonContent(properties: [
new OA\Property(property: 'status', type: 'string', example: 'healthy'),
new OA\Property(property: 'timestamp', type: 'string', format: 'date-time'),
new OA\Property(property: 'checks', type: 'object'),
])
),
new OA\Response(response: 503, description: 'One or more checks degraded'),
]
)]
public function check()
{
$checks = [
'database' => $this->checkDatabase(),
'redis' => $this->checkRedis(),
'storage' => $this->checkStorage(),
'queue' => $this->checkQueue(),
];
$hasFailure = collect($checks)->contains(fn ($c) => $c['status'] === 'fail');
$allOk = collect($checks)->every(fn ($c) => $c['status'] === 'ok');
return response()->json([
'status' => $allOk ? 'healthy' : ($hasFailure ? 'degraded' : 'warn'),
'timestamp' => now()->toIso8601String(),
'checks' => $checks,
], $hasFailure ? 503 : 200);
}
private function checkDatabase(): array
{
try {
DB::connection()->getPdo();
return ['status' => 'ok', 'latency_ms' => $this->measure(fn () => DB::select('SELECT 1'))];
} catch (\Throwable $e) {
return ['status' => 'fail', 'error' => $e->getMessage()];
}
}
private function checkRedis(): array
{
try {
$latency = $this->measure(fn () => Cache::store('redis')->put('health_check', true, 5));
return ['status' => 'ok', 'latency_ms' => $latency];
} catch (\Throwable $e) {
return ['status' => 'fail', 'error' => $e->getMessage()];
}
}
private function checkStorage(): array
{
try {
$free = disk_free_space(storage_path());
$total = disk_total_space(storage_path());
if ($free === false || $total === false || $total === 0.0) {
return ['status' => 'fail', 'error' => 'Unable to read disk space.'];
}
$usedPct = round((($total - $free) / $total) * 100, 1);
return [
'status' => $usedPct < 90 ? 'ok' : 'warn',
'used_pct' => $usedPct,
'free_gb' => round($free / 1073741824, 2),
];
} catch (\Throwable $e) {
return ['status' => 'fail', 'error' => $e->getMessage()];
}
}
private function checkQueue(): array
{
try {
$size = Queue::size('default');
return ['status' => 'ok', 'pending_jobs' => $size];
} catch (\Throwable $e) {
return ['status' => 'unknown', 'error' => $e->getMessage()];
}
}
private function measure(callable $fn): int
{
$start = hrtime(true);
$fn();
return (int) round((hrtime(true) - $start) / 1_000_000);
}
}