query('SELECT 1')->fetchColumn(); $checks['database'] = [ 'ok' => true, 'latency_ms' => (int)round((microtime(true) - $started) * 1000), ]; } catch (Throwable $e) { Logger::error('Health-Check: Datenbank nicht erreichbar', ['error' => $e->getMessage()]); $checks['database'] = ['ok' => false, 'error' => 'nicht erreichbar']; $healthy = false; } // --- Schreibbarkeit des Log-Verzeichnisses --- $logDir = DC_VAR . '/log'; $checks['log_writable'] = ['ok' => is_dir($logDir) ? is_writable($logDir) : is_writable(DC_ROOT)]; $response = [ 'healthy' => $healthy, 'app' => Config::get('app.name', 'Deploymentcenter'), 'version' => Config::get('app.version', 'unknown'), 'time_utc' => gmdate('c'), 'checks' => $checks, ]; // --- Detailinformationen nur fuer Authentifizierte --- if ($db !== null && ApiAuth::resolve($db, 'bugtracker:read') !== null) { try { $status = Migrator::status($db); $response['schema'] = [ 'applied_count' => count($status['applied']), 'pending' => $status['pending'], ]; if ($status['pending'] !== []) { $response['healthy'] = false; $response['checks']['migrations'] = [ 'ok' => false, 'message' => 'Ausstehende Migrationen: ' . implode(', ', $status['pending']), ]; } else { $response['checks']['migrations'] = ['ok' => true]; } $repo = new BugRepo($db); $response['bugtracker'] = $repo->getStats(); $monitors = $db->query(' SELECT state, COUNT(*) AS total FROM watchdog_monitors GROUP BY state ')->fetchAll() ?: []; $byState = []; foreach ($monitors as $row) { $byState[(string)$row['state']] = (int)$row['total']; } $response['watchdog'] = ['monitors_by_state' => $byState]; $lastRun = $db->query(" SELECT last_run_utc FROM watchdog_cron_jobs WHERE name = 'evaluator' ")->fetchColumn(); $response['watchdog']['evaluator_last_run_utc'] = $lastRun !== false ? $lastRun : null; // Laeuft der Evaluator nicht, sind alle Monitor-Zustaende wertlos. if ($lastRun === false || $lastRun === null) { $response['checks']['evaluator'] = [ 'ok' => false, 'message' => 'Der Evaluator lief noch nie. Cron-Job auf /api/watchdog/v1/evaluate einrichten.', ]; } else { $age = time() - (int)strtotime((string)$lastRun . ' UTC'); $response['checks']['evaluator'] = [ 'ok' => $age < 900, 'age_seconds' => $age, 'message' => $age < 900 ? null : 'Letzter Lauf liegt zu lange zurueck.', ]; } } catch (Throwable $e) { Logger::warning('Health-Check: Detailabfrage fehlgeschlagen', ['error' => $e->getMessage()]); $response['detail_error'] = 'Detailinformationen konnten nicht ermittelt werden.'; } } Http::ok($response, $response['healthy'] ? 200 : 503);