114 lines
3.6 KiB
PHP
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);
|
|
}
|
|
}
|