Systemstatus im Sysadmin-Bereich und /health fuer externe Ueberwachung
- HealthProbe: Datenbank, Cache, Queue (sync / database / Horizon), fehlgeschlagene Jobs (24 h), Scheduler-Herzschlag, Plattenplatz - /health auf jeder Domain, ohne web-Gruppe, nur mit MONITORING_HEALTH_TOKEN - monitoring:heartbeat minuetlich im Scheduler - Sysadmin -> Systemstatus (my.mivita.care/sysadmin/status) mit Version, Umgebung und zuletzt fehlgeschlagenen Jobs - Anleitung Abschnitt G Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
22c85fc746
commit
fec88ee265
17 changed files with 783 additions and 3 deletions
39
app/Http/Controllers/HealthController.php
Normal file
39
app/Http/Controllers/HealthController.php
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\Monitoring\HealthProbe;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* Tiefer Health-Check für den externen Monitor.
|
||||
*
|
||||
* `/up` zeigt nur, dass die App bootet. `/health` prüft zusätzlich DB,
|
||||
* Cache, Queue, fehlgeschlagene Jobs, Scheduler-Herzschlag und Plattenplatz: 200 solange
|
||||
* nichts kritisch ist, sonst 503. Zugang nur mit MONITORING_HEALTH_TOKEN
|
||||
* (Bearer oder `?token=`); ohne konfiguriertes Token gibt es den Endpunkt nicht.
|
||||
*/
|
||||
class HealthController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request, HealthProbe $probe): JsonResponse
|
||||
{
|
||||
$token = (string) config('monitoring.health.token');
|
||||
$given = (string) ($request->bearerToken() ?? $request->query('token', ''));
|
||||
|
||||
abort_if($token === '' || ! hash_equals($token, $given), 404);
|
||||
|
||||
$checks = $probe->checks();
|
||||
$status = HealthProbe::worstStatus($checks);
|
||||
|
||||
return response()->json([
|
||||
'status' => $status,
|
||||
'version' => config('app.version'),
|
||||
'checked_at' => now()->toIso8601String(),
|
||||
'checks' => collect($checks)->mapWithKeys(fn (array $check): array => [
|
||||
$check['key'] => ['status' => $check['status'], 'detail' => $check['detail']],
|
||||
]),
|
||||
], $status === HealthProbe::StatusCritical ? 503 : 200)
|
||||
->header('Cache-Control', 'no-store');
|
||||
}
|
||||
}
|
||||
59
app/Http/Controllers/SyS/SystemStatusController.php
Normal file
59
app/Http/Controllers/SyS/SystemStatusController.php
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\SyS;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Monitoring\HealthProbe;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Systemstatus im Sysadmin-Bereich: dieselben Prüfungen wie `/health`,
|
||||
* dazu Umgebung und die zuletzt fehlgeschlagenen Jobs.
|
||||
*/
|
||||
class SystemStatusController extends Controller
|
||||
{
|
||||
public function index(HealthProbe $probe): View
|
||||
{
|
||||
$checks = $probe->checks();
|
||||
|
||||
return view('sys.status.index', [
|
||||
'checks' => $checks,
|
||||
'overall' => HealthProbe::worstStatus($checks),
|
||||
'environment' => [
|
||||
'Version' => config('app.version') ?? 'unbekannt',
|
||||
'Umgebung' => app()->environment(),
|
||||
'PHP' => PHP_VERSION,
|
||||
'Laravel' => app()->version(),
|
||||
'Queue-Treiber' => config('queue.default'),
|
||||
'Cache-Treiber' => config('cache.default'),
|
||||
],
|
||||
'healthTokenConfigured' => filled(config('monitoring.health.token')),
|
||||
'horizonAvailable' => config('queue.default') === 'redis',
|
||||
'failedJobs' => $this->recentFailedJobs(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, array{uuid: ?string, job: string, queue: string, failed_at: string, error: string}>
|
||||
*/
|
||||
private function recentFailedJobs(): Collection
|
||||
{
|
||||
try {
|
||||
$rows = DB::table('failed_jobs')->latest('failed_at')->limit(10)->get();
|
||||
} catch (Throwable) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return $rows->map(fn (object $row): array => [
|
||||
'uuid' => $row->uuid ?? null,
|
||||
'job' => json_decode($row->payload, true)['displayName'] ?? 'unbekannt',
|
||||
'queue' => $row->connection.' / '.$row->queue,
|
||||
'failed_at' => (string) $row->failed_at,
|
||||
'error' => Str::limit(Str::before((string) $row->exception, "\n"), 200),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue