feat(Production): Complete production deployment infrastructure

- 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.
This commit is contained in:
2025-10-25 19:18:37 +02:00
parent caa85db796
commit fc3d7e6357
83016 changed files with 378904 additions and 20919 deletions

View File

@@ -4,7 +4,9 @@ declare(strict_types=1);
namespace App\Framework\Logging\Handlers;
use App\Framework\Config\Environment;
use App\Framework\Core\PathProvider;
use App\Framework\Filesystem\Serializers\JsonSerializer;
use App\Framework\Logging\LogHandler;
use App\Framework\Logging\LogLevel;
use App\Framework\Logging\LogRecord;
@@ -12,6 +14,15 @@ use App\Framework\Logging\LogRecord;
/**
* Handler für die Ausgabe von Log-Einträgen als JSON in Dateien.
* Besonders nützlich für maschinelle Verarbeitung und Log-Aggregatoren.
*
* Nutzt JsonSerializer für einheitliche JSON-Ausgabe konsistent mit JsonFormatter.
*
* Standard-Felder für Log-Aggregatoren:
* - @timestamp: Elasticsearch-konformes Zeitstempelfeld
* - severity: RFC 5424 Severity Level (0-7)
* - environment: Deployment-Umgebung (production, staging, development)
* - host: Server-Hostname
* - service: Service-/Anwendungsname
*/
class JsonFileHandler implements LogHandler
{
@@ -35,6 +46,31 @@ class JsonFileHandler implements LogHandler
*/
private ?PathProvider $pathProvider = null;
/**
* @var JsonSerializer JSON Serializer für konsistente Ausgabe
*/
private JsonSerializer $serializer;
/**
* @var bool Flatten LogContext für bessere Aggregator-Kompatibilität
*/
private bool $flattenContext;
/**
* @var string Deployment-Umgebung (production, staging, development)
*/
private string $environment;
/**
* @var string Server-Hostname
*/
private string $host;
/**
* @var string Service-/Anwendungsname
*/
private string $serviceName;
/**
* Erstellt einen neuen JsonFileHandler
*
@@ -42,14 +78,21 @@ class JsonFileHandler implements LogHandler
* @param LogLevel|int $minLevel Minimales Level, ab dem dieser Handler aktiv wird
* @param array|null $includedFields Liste der Felder, die in der JSON-Ausgabe enthalten sein sollen (null = alle)
* @param PathProvider|null $pathProvider Optional: PathProvider für die Auflösung von Pfaden
* @param bool $flattenContext Flatten LogContext structured data (default: true)
* @param Environment|null $env Optional: Environment für Konfiguration
* @param string|null $serviceName Optional: Service-/Anwendungsname
*/
public function __construct(
string $logFile,
LogLevel|int $minLevel = LogLevel::INFO,
?array $includedFields = null,
?PathProvider $pathProvider = null
?PathProvider $pathProvider = null,
bool $flattenContext = true,
?Environment $env = null,
?string $serviceName = null
) {
$this->pathProvider = $pathProvider;
$this->flattenContext = $flattenContext;
// Pfad auflösen, falls PathProvider vorhanden
if ($this->pathProvider !== null && ! str_starts_with($logFile, '/')) {
@@ -59,16 +102,34 @@ class JsonFileHandler implements LogHandler
$this->logFile = $logFile;
$this->minLevel = $minLevel instanceof LogLevel ? $minLevel : LogLevel::fromValue($minLevel);
// Standardfelder, falls nicht anders angegeben
// Standardfelder, falls nicht anders angegeben (konsistent mit JsonFormatter)
$this->includedFields = $includedFields ?? [
'timestamp',
'level_name',
'@timestamp',
'level',
'level_value',
'severity',
'channel',
'message',
'environment',
'host',
'service',
'context',
'extra',
'channel',
];
// Compact JSON für Datei-Ausgabe (eine Zeile pro Log)
$this->serializer = JsonSerializer::compact();
// Environment Detection (production, staging, development)
$this->environment = $env?->getString('APP_ENV', 'production') ?? 'production';
// Host Detection
$this->host = gethostname() ?: 'unknown';
// Service Name (default: app name from env)
$this->serviceName = $serviceName ?? $env?->getString('APP_NAME', 'app') ?? 'app';
// Stelle sicher, dass das Verzeichnis existiert
$this->ensureDirectoryExists(dirname($logFile));
}
@@ -86,25 +147,83 @@ class JsonFileHandler implements LogHandler
*/
public function handle(LogRecord $record): void
{
// Alle Daten des Records als Array holen
$data = $record->toArray();
// Formatiere Record zu einheitlichem Array (konsistent mit JsonFormatter)
$data = $this->formatRecord($record);
// Nur die gewünschten Felder behalten
if (! empty($this->includedFields)) {
$data = array_intersect_key($data, array_flip($this->includedFields));
}
// Zeitstempel als ISO 8601 formatieren für bessere Interoperabilität
if (isset($data['datetime']) && $data['datetime'] instanceof \DateTimeInterface) {
$data['timestamp_iso'] = $data['datetime']->format(\DateTimeInterface::ATOM);
unset($data['datetime']); // DateTime-Objekt entfernen
}
// Als JSON formatieren und in die Datei schreiben
$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR) . PHP_EOL;
// Als JSON formatieren mit JsonSerializer und in die Datei schreiben
$json = $this->serializer->serialize($data) . PHP_EOL;
$this->write($json);
}
/**
* Formatiert LogRecord zu einheitlichem Array für JSON-Serialisierung
* (Gleiche Logik wie JsonFormatter für Konsistenz)
*
* @return array<string, mixed>
*/
private function formatRecord(LogRecord $record): array
{
$timestamp = $record->timestamp->format('c'); // ISO 8601
$data = [
// Standard Log Fields
'timestamp' => $timestamp,
'@timestamp' => $timestamp, // Elasticsearch convention
'level' => $record->level->getName(),
'level_value' => $record->level->value,
'severity' => $record->level->toRFC5424(), // RFC 5424 (0-7)
'channel' => $record->channel,
'message' => $record->message,
// Infrastructure Fields (for log aggregators)
'environment' => $this->environment,
'host' => $this->host,
'service' => $this->serviceName,
];
// Context hinzufügen mit optionalem Flatten
$context = $record->context->toArray();
if ($this->flattenContext && isset($context['structured'])) {
// Flatten: Nur strukturierte Daten für bessere Aggregator-Kompatibilität
$data['context'] = $context['structured'];
} else {
// Raw: Gesamtes LogContext-Array
$data['context'] = $context;
}
// Extras hinzufügen (wenn vorhanden)
if (!empty($record->extra)) {
$data['extra'] = $record->extra;
}
// Nur gewünschte Felder behalten, in Reihenfolge von $includedFields
return $this->filterFields($data);
}
/**
* Filtert Array nach includedFields
*
* @param array<string, mixed> $data
* @return array<string, mixed>
*/
private function filterFields(array $data): array
{
if (empty($this->includedFields)) {
return $data;
}
$filtered = [];
foreach ($this->includedFields as $field) {
if (array_key_exists($field, $data)) {
$filtered[$field] = $data[$field];
}
}
return $filtered;
}
/**
* Schreibt einen String in die Log-Datei
*/