- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
295 lines
9.4 KiB
PHP
295 lines
9.4 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace App\Framework\Logging\Commands;
|
||
|
||
use App\Framework\Console\ConsoleCommand;
|
||
use App\Framework\Console\ConsoleInput;
|
||
use App\Framework\Console\ExitCode;
|
||
use App\Framework\Core\ValueObjects\Byte;
|
||
|
||
/**
|
||
* Console Command zur Überwachung der Log-Infrastruktur
|
||
*
|
||
* Prüft:
|
||
* - Log-Verzeichnisse und Berechtigungen
|
||
* - Log-Dateien und deren Größen
|
||
* - Rotierte Log-Dateien
|
||
* - Disk-Space-Verfügbarkeit
|
||
* - Schreibrechte
|
||
*
|
||
* Usage:
|
||
* ```bash
|
||
* php console.php log:health-check
|
||
* php console.php log:health-check --detailed
|
||
* php console.php log:health-check --fix-permissions
|
||
* ```
|
||
*/
|
||
final readonly class LogHealthCheckCommand
|
||
{
|
||
#[ConsoleCommand(name: 'logs:health-check', description: 'Check log infrastructure health and identify issues')]
|
||
public function execute(ConsoleInput $input): ExitCode
|
||
{
|
||
$detailed = $input->hasOption('detailed');
|
||
$fixPermissions = $input->hasOption('fix-permissions');
|
||
|
||
$basePath = getcwd() ?: '/var/www/html';
|
||
$logsPath = $basePath . '/storage/logs';
|
||
|
||
echo "═══════════════════════════════════════════════════\n";
|
||
echo " Log Infrastructure Health Check \n";
|
||
echo "═══════════════════════════════════════════════════\n\n";
|
||
|
||
$checks = [
|
||
'directories' => $this->checkDirectories($logsPath, $basePath, $fixPermissions),
|
||
'permissions' => $this->checkPermissions($logsPath, $basePath, $fixPermissions),
|
||
'disk_space' => $this->checkDiskSpace($logsPath, $basePath),
|
||
'log_files' => $this->checkLogFiles($logsPath, $basePath, $detailed),
|
||
];
|
||
|
||
// Summary
|
||
echo "\n═══════════════════════════════════════════════════\n";
|
||
echo " Summary \n";
|
||
echo "═══════════════════════════════════════════════════\n";
|
||
|
||
$allPassed = true;
|
||
foreach ($checks as $checkName => $passed) {
|
||
$status = $passed ? '✓ PASS' : '✗ FAIL';
|
||
$color = $passed ? "\033[32m" : "\033[31m";
|
||
echo $color . $status . "\033[0m " . str_pad(ucfirst(str_replace('_', ' ', $checkName)), 30) . "\n";
|
||
|
||
if (!$passed) {
|
||
$allPassed = false;
|
||
}
|
||
}
|
||
|
||
echo "\n";
|
||
|
||
if ($allPassed) {
|
||
echo "\033[32m✓ All checks passed! Log infrastructure is healthy.\033[0m\n";
|
||
return ExitCode::SUCCESS;
|
||
}
|
||
|
||
echo "\033[31m✗ Some checks failed. Please review above output.\033[0m\n";
|
||
|
||
if (!$fixPermissions) {
|
||
echo "\033[33mTip: Use --fix-permissions to automatically fix permission issues.\033[0m\n";
|
||
}
|
||
|
||
return ExitCode::GENERAL_ERROR;
|
||
}
|
||
|
||
/**
|
||
* Prüft ob Log-Verzeichnisse existieren
|
||
*/
|
||
private function checkDirectories(string $logsPath, string $basePath, bool $fix): bool
|
||
{
|
||
echo "📁 Checking log directories...\n";
|
||
|
||
$directories = [
|
||
$logsPath,
|
||
$logsPath . '/app',
|
||
$logsPath . '/debug',
|
||
$logsPath . '/security',
|
||
];
|
||
|
||
$allExist = true;
|
||
|
||
foreach ($directories as $dir) {
|
||
$exists = is_dir($dir);
|
||
|
||
if ($exists) {
|
||
echo " ✓ " . $this->relativePath($dir, $basePath) . "\n";
|
||
} else {
|
||
echo " ✗ " . $this->relativePath($dir, $basePath) . " (missing)\n";
|
||
$allExist = false;
|
||
|
||
if ($fix) {
|
||
echo " → Creating directory...\n";
|
||
mkdir($dir, 0777, true);
|
||
echo " ✓ Directory created\n";
|
||
$allExist = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
echo "\n";
|
||
|
||
return $allExist;
|
||
}
|
||
|
||
/**
|
||
* Prüft Schreibberechtigungen
|
||
*/
|
||
private function checkPermissions(string $logsPath, string $basePath, bool $fix): bool
|
||
{
|
||
echo "🔐 Checking write permissions...\n";
|
||
|
||
$testFile = $logsPath . '/.health-check-' . uniqid() . '.tmp';
|
||
|
||
try {
|
||
// Versuche Test-Datei zu schreiben
|
||
$result = @file_put_contents($testFile, 'health check test');
|
||
|
||
if ($result === false) {
|
||
echo " ✗ Cannot write to " . $this->relativePath($logsPath, $basePath) . "\n";
|
||
|
||
if ($fix) {
|
||
echo " → Attempting to fix permissions...\n";
|
||
@chmod($logsPath, 0777);
|
||
@chmod(dirname($logsPath), 0777);
|
||
|
||
// Retry
|
||
$result = @file_put_contents($testFile, 'health check test');
|
||
|
||
if ($result !== false) {
|
||
echo " ✓ Permissions fixed\n";
|
||
@unlink($testFile);
|
||
echo "\n";
|
||
return true;
|
||
}
|
||
}
|
||
|
||
echo "\n";
|
||
return false;
|
||
}
|
||
|
||
// Cleanup
|
||
@unlink($testFile);
|
||
|
||
$perms = substr(sprintf('%o', fileperms($logsPath)), -4);
|
||
echo " ✓ Write permissions OK (permissions: " . $perms . ")\n";
|
||
echo "\n";
|
||
|
||
return true;
|
||
} catch (\Throwable $e) {
|
||
echo " ✗ Permission check failed: " . $e->getMessage() . "\n";
|
||
echo "\n";
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Prüft verfügbaren Disk-Space
|
||
*/
|
||
private function checkDiskSpace(string $logsPath, string $basePath): bool
|
||
{
|
||
echo "💾 Checking disk space...\n";
|
||
|
||
$freeSpace = disk_free_space($logsPath);
|
||
$totalSpace = disk_total_space($logsPath);
|
||
|
||
if ($freeSpace === false || $totalSpace === false) {
|
||
echo " ✗ Could not determine disk space\n";
|
||
echo "\n";
|
||
return false;
|
||
}
|
||
|
||
$freeBytes = Byte::fromBytes((int) $freeSpace);
|
||
$totalBytes = Byte::fromBytes((int) $totalSpace);
|
||
$usedBytes = Byte::fromBytes((int) ($totalSpace - $freeSpace));
|
||
|
||
$percentUsed = ($totalSpace > 0) ? round(($totalSpace - $freeSpace) / $totalSpace * 100, 2) : 0;
|
||
|
||
echo " Total: " . $totalBytes->toHumanReadable() . "\n";
|
||
echo " Used: " . $usedBytes->toHumanReadable() . " (" . $percentUsed . "%)\n";
|
||
echo " Free: " . $freeBytes->toHumanReadable() . "\n";
|
||
|
||
// Warn if less than 100MB free or more than 90% used
|
||
if ($freeSpace < 100 * 1024 * 1024 || $percentUsed > 90) {
|
||
echo " \033[33m⚠ Warning: Low disk space!\033[0m\n";
|
||
echo "\n";
|
||
return false;
|
||
}
|
||
|
||
echo " ✓ Disk space OK\n";
|
||
echo "\n";
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Prüft Log-Dateien
|
||
*/
|
||
private function checkLogFiles(string $logsPath, string $basePath, bool $detailed): bool
|
||
{
|
||
echo "📄 Checking log files...\n";
|
||
|
||
$logFiles = $this->findLogFiles($logsPath);
|
||
|
||
if (empty($logFiles)) {
|
||
echo " ℹ No log files found (this is OK for a fresh install)\n";
|
||
echo "\n";
|
||
return true;
|
||
}
|
||
|
||
echo " Found " . count($logFiles) . " log file(s)\n\n";
|
||
|
||
$totalSize = 0;
|
||
$warnings = [];
|
||
|
||
foreach ($logFiles as $logFile) {
|
||
$size = filesize($logFile);
|
||
$totalSize += $size;
|
||
|
||
$sizeBytes = Byte::fromBytes($size);
|
||
$modified = date('Y-m-d H:i:s', filemtime($logFile));
|
||
|
||
if ($detailed) {
|
||
echo " " . $this->relativePath($logFile, $basePath) . "\n";
|
||
echo " Size: " . $sizeBytes->toHumanReadable() . "\n";
|
||
echo " Modified: " . $modified . "\n";
|
||
|
||
// Check if file is very large
|
||
if ($size > 50 * 1024 * 1024) { // 50MB
|
||
echo " \033[33m⚠ Large file (consider rotation)\033[0m\n";
|
||
$warnings[] = $logFile;
|
||
}
|
||
|
||
echo "\n";
|
||
}
|
||
}
|
||
|
||
$totalBytes = Byte::fromBytes($totalSize);
|
||
echo " Total log size: " . $totalBytes->toHumanReadable() . "\n";
|
||
|
||
if (!empty($warnings)) {
|
||
echo "\n \033[33m⚠ " . count($warnings) . " file(s) exceed 50MB\033[0m\n";
|
||
}
|
||
|
||
echo "\n";
|
||
|
||
return empty($warnings);
|
||
}
|
||
|
||
/**
|
||
* Findet alle Log-Dateien rekursiv
|
||
*/
|
||
private function findLogFiles(string $directory): array
|
||
{
|
||
$files = [];
|
||
|
||
$iterator = new \RecursiveIteratorIterator(
|
||
new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS),
|
||
\RecursiveIteratorIterator::SELF_FIRST
|
||
);
|
||
|
||
foreach ($iterator as $file) {
|
||
if ($file->isFile() && preg_match('/\.(log|log\.\d+|log\.\d+\.gz)$/', $file->getFilename())) {
|
||
$files[] = $file->getPathname();
|
||
}
|
||
}
|
||
|
||
return $files;
|
||
}
|
||
|
||
/**
|
||
* Gibt relativen Pfad zurück
|
||
*/
|
||
private function relativePath(string $path, string $basePath): string
|
||
{
|
||
return str_replace($basePath . '/', '', $path);
|
||
}
|
||
}
|