refactor: improve logging system and add deployment fixes

- Enhance logging handlers (Console, DockerJson, File, JsonFile, MultiFile)
- Improve exception and line formatters
- Update logger initialization and processor management
- Add Ansible playbooks for staging 502 error troubleshooting
- Update deployment documentation
- Fix serializer and queue components
- Update error kernel and queued log handler
This commit is contained in:
2025-11-02 01:37:49 +01:00
parent 2defdf2baf
commit cf0ad6e905
23 changed files with 612 additions and 556 deletions

View File

@@ -6,8 +6,9 @@ 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\Formatter\JsonFormatter;
use App\Framework\Logging\Formatter\LogFormatter;
use App\Framework\Logging\LogLevel;
use App\Framework\Logging\LogRecord;
@@ -15,7 +16,7 @@ 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.
* Nutzt JsonFormatter für einheitliche JSON-Ausgabe.
*
* Standard-Felder für Log-Aggregatoren:
* - @timestamp: Elasticsearch-konformes Zeitstempelfeld
@@ -37,62 +38,31 @@ class JsonFileHandler implements LogHandler
private string $logFile;
/**
* @var array Liste der Felder, die in der JSON-Ausgabe enthalten sein sollen
* @var JsonFormatter Formatter für die JSON-Ausgabe
*/
private array $includedFields;
private JsonFormatter $formatter;
/**
* @var PathProvider|null PathProvider für die Auflösung von Pfaden
*/
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
*
* @param JsonFormatter $formatter Formatter für die JSON-Ausgabe
* @param string $logFile Pfad zur Log-Datei
* @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(
JsonFormatter $formatter,
string $logFile,
LogLevel|int $minLevel = LogLevel::INFO,
?array $includedFields = null,
?PathProvider $pathProvider = null,
bool $flattenContext = true,
?Environment $env = null,
?string $serviceName = null
?PathProvider $pathProvider = null
) {
$this->pathProvider = $pathProvider;
$this->flattenContext = $flattenContext;
$this->formatter = $formatter;
// Pfad auflösen, falls PathProvider vorhanden
if ($this->pathProvider !== null && ! str_starts_with($logFile, '/')) {
@@ -102,34 +72,6 @@ class JsonFileHandler implements LogHandler
$this->logFile = $logFile;
$this->minLevel = $minLevel instanceof LogLevel ? $minLevel : LogLevel::fromValue($minLevel);
// Standardfelder, falls nicht anders angegeben (konsistent mit JsonFormatter)
$this->includedFields = $includedFields ?? [
'timestamp',
'@timestamp',
'level',
'level_value',
'severity',
'channel',
'message',
'environment',
'host',
'service',
'context',
'extra',
];
// 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));
}
@@ -147,81 +89,26 @@ class JsonFileHandler implements LogHandler
*/
public function handle(LogRecord $record): void
{
// Formatiere Record zu einheitlichem Array (konsistent mit JsonFormatter)
$data = $this->formatRecord($record);
// Formatter verwenden für JSON-Formatierung
$json = ($this->formatter)($record);
// Als JSON formatieren mit JsonSerializer und in die Datei schreiben
$json = $this->serializer->serialize($data) . PHP_EOL;
$this->write($json);
// JsonFormatter gibt immer string zurück
$output = is_string($json) ? $json : json_encode($json, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
// Stelle sicher, dass ein Newline vorhanden ist
if (!str_ends_with($output, PHP_EOL)) {
$output .= PHP_EOL;
}
$this->write($output);
}
/**
* Formatiert LogRecord zu einheitlichem Array für JSON-Serialisierung
* (Gleiche Logik wie JsonFormatter für Konsistenz)
*
* @return array<string, mixed>
* Gibt den Formatter zurück
*/
private function formatRecord(LogRecord $record): array
public function getFormatter(): LogFormatter
{
$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;
return $this->formatter;
}
/**
@@ -252,15 +139,6 @@ class JsonFileHandler implements LogHandler
return $this;
}
/**
* Setzt die Liste der Felder, die in der JSON-Ausgabe enthalten sein sollen
*/
public function setIncludedFields(array $fields): self
{
$this->includedFields = $fields;
return $this;
}
/**
* Log-Datei setzen