feat: add app and database modules
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user