Skip to content

Commit 9c0d870

Browse files
authored
Add /ping and /health endpoints for monitoring (#443)
1 parent 551c3f3 commit 9c0d870

2 files changed

Lines changed: 103 additions & 0 deletions

File tree

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use Illuminate\Http\JsonResponse;
6+
use Illuminate\Http\Response;
7+
use Illuminate\Support\Facades\DB;
8+
use Illuminate\Support\Facades\Redis;
9+
10+
class HealthController extends Controller
11+
{
12+
public function ping(): Response
13+
{
14+
return response('pong!', 200)
15+
->header('Content-Type', 'text/plain');
16+
}
17+
18+
public function health(): JsonResponse
19+
{
20+
$checks = [
21+
'database' => $this->checkDatabase(),
22+
'redis' => $this->checkRedis(),
23+
'disk' => $this->checkDisk(),
24+
];
25+
26+
$healthy = collect($checks)->every(fn ($check) => $check['status'] === 'ok');
27+
28+
return response()->json([
29+
'status' => $healthy ? 'healthy' : 'unhealthy',
30+
'checks' => $checks,
31+
'timestamp' => now()->toIso8601String(),
32+
], $healthy ? 200 : 503);
33+
}
34+
35+
private function checkDatabase(): array
36+
{
37+
try {
38+
DB::connection()->getPdo();
39+
DB::select('SELECT 1');
40+
41+
return ['status' => 'ok'];
42+
} catch (\Exception $e) {
43+
return [
44+
'status' => 'error',
45+
'message' => $e->getMessage(),
46+
];
47+
}
48+
}
49+
50+
private function checkRedis(): array
51+
{
52+
try {
53+
$response = Redis::ping();
54+
if ($response === true || $response == 'PONG' || (is_object($response) && method_exists($response, 'getPayload') && $response->getPayload() === 'PONG')) {
55+
return ['status' => 'ok'];
56+
}
57+
58+
return [
59+
'status' => 'error',
60+
'message' => 'Unexpected response from Redis',
61+
];
62+
} catch (\Exception $e) {
63+
return [
64+
'status' => 'error',
65+
'message' => $e->getMessage(),
66+
];
67+
}
68+
}
69+
70+
private function checkDisk(): array
71+
{
72+
try {
73+
$path = storage_path();
74+
$freeBytes = disk_free_space($path);
75+
$totalBytes = disk_total_space($path);
76+
77+
if ($freeBytes === false || $totalBytes === false) {
78+
return [
79+
'status' => 'error',
80+
'message' => 'Unable to read disk space',
81+
];
82+
}
83+
84+
$usedPercent = round((($totalBytes - $freeBytes) / $totalBytes) * 100, 1);
85+
$status = $usedPercent > 95 ? 'error' : ($usedPercent > 85 ? 'warning' : 'ok');
86+
87+
return [
88+
'status' => $status,
89+
'used_percent' => $usedPercent,
90+
];
91+
} catch (\Exception $e) {
92+
return [
93+
'status' => 'error',
94+
'message' => $e->getMessage(),
95+
];
96+
}
97+
}
98+
}

routes/api.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use App\Http\Controllers\AuthController;
2323
use App\Http\Controllers\EmailChangeController;
2424
use App\Http\Controllers\EmailVerificationController;
25+
use App\Http\Controllers\HealthController;
2526
use App\Http\Controllers\InboxController;
2627
use App\Http\Controllers\InstanceActorController;
2728
use App\Http\Controllers\NodeInfoController;
@@ -37,6 +38,10 @@
3738
use App\Http\Middleware\FederationEnabled;
3839
use Illuminate\Support\Facades\Route;
3940

41+
// Health check endpoints
42+
Route::get('/ping', [HealthController::class, 'ping'])->name('health.ping');
43+
Route::get('/health', [HealthController::class, 'health'])->name('health.check');
44+
4045
// NodeInfo endpoints
4146
Route::group(['prefix' => 'nodeinfo'], function () {
4247
Route::get('2.0', [NodeInfoController::class, 'nodeInfo20'])

0 commit comments

Comments
 (0)