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:
@@ -4,8 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Logging\Handlers;
|
||||
|
||||
use App\Framework\Console\ConsoleColor;
|
||||
use App\Framework\Logging\LogHandler;
|
||||
use App\Framework\Logging\Formatter\LogFormatter;
|
||||
use App\Framework\Logging\LogLevel;
|
||||
use App\Framework\Logging\LogRecord;
|
||||
|
||||
@@ -25,26 +25,26 @@ class ConsoleHandler implements LogHandler
|
||||
private bool $debugOnly;
|
||||
|
||||
/**
|
||||
* @var string Format für die Ausgabe
|
||||
* @var LogFormatter Formatter für die Log-Ausgabe
|
||||
*/
|
||||
private string $outputFormat;
|
||||
private LogFormatter $formatter;
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen ConsoleHandler
|
||||
*
|
||||
* @param LogFormatter $formatter Formatter für die Log-Ausgabe
|
||||
* @param LogLevel|int $minLevel Minimales Level, ab dem dieser Handler aktiv wird
|
||||
* @param bool $debugOnly Ob der Handler nur im Debug-Modus aktiv ist
|
||||
* @param string $outputFormat Format für die Ausgabe
|
||||
*/
|
||||
public function __construct(
|
||||
LogLevel|int $minLevel = LogLevel::DEBUG,
|
||||
bool $debugOnly = true,
|
||||
string $outputFormat = '{color}[{level_name}]{reset} {timestamp} {request_id}{message}{structured}',
|
||||
LogFormatter $formatter,
|
||||
LogLevel|int $minLevel = LogLevel::DEBUG,
|
||||
bool $debugOnly = true,
|
||||
private readonly LogLevel $stderrLevel = LogLevel::WARNING,
|
||||
) {
|
||||
$this->formatter = $formatter;
|
||||
$this->minLevel = $minLevel instanceof LogLevel ? $minLevel : LogLevel::fromValue($minLevel);
|
||||
$this->debugOnly = $debugOnly;
|
||||
$this->outputFormat = $outputFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,32 +71,18 @@ class ConsoleHandler implements LogHandler
|
||||
*/
|
||||
public function handle(LogRecord $record): void
|
||||
{
|
||||
$logLevel = $record->getLevel();
|
||||
$color = $logLevel->getConsoleColor()->value;
|
||||
$reset = ConsoleColor::RESET->value;
|
||||
// Formatter verwenden für Formatierung
|
||||
$formatted = ($this->formatter)($record);
|
||||
|
||||
// Request-ID-Teil erstellen, falls vorhanden
|
||||
$requestId = $record->hasExtra('request_id')
|
||||
? "[{$record->getExtra('request_id')}] "
|
||||
: '';
|
||||
// Formatter gibt immer string zurück für Console
|
||||
$output = is_string($formatted)
|
||||
? $formatted
|
||||
: json_encode($formatted, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
// Structured Logging Extras formatieren
|
||||
$structuredInfo = $this->formatStructuredExtras($record);
|
||||
|
||||
// Werte für Platzhalter im Format
|
||||
$values = [
|
||||
'{color}' => $color,
|
||||
'{reset}' => $reset,
|
||||
'{level_name}' => $record->getLevel()->getName(),
|
||||
'{timestamp}' => $record->getFormattedTimestamp(),
|
||||
'{request_id}' => $requestId,
|
||||
'{message}' => $record->getMessage(),
|
||||
'{channel}' => $record->getChannel() ? "[{$record->getChannel()}] " : '',
|
||||
'{structured}' => $structuredInfo,
|
||||
];
|
||||
|
||||
// Formatierte Ausgabe erstellen
|
||||
$output = strtr($this->outputFormat, $values) . PHP_EOL;
|
||||
// Stelle sicher, dass ein Newline vorhanden ist
|
||||
if (!str_ends_with($output, PHP_EOL)) {
|
||||
$output .= PHP_EOL;
|
||||
}
|
||||
|
||||
// Fehler und Warnungen auf stderr, alles andere auf stdout
|
||||
if ($record->getLevel()->value >= $this->stderrLevel->value) {
|
||||
@@ -108,6 +94,14 @@ class ConsoleHandler implements LogHandler
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den Formatter zurück
|
||||
*/
|
||||
public function getFormatter(): LogFormatter
|
||||
{
|
||||
return $this->formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimales Log-Level setzen
|
||||
*/
|
||||
@@ -117,101 +111,4 @@ class ConsoleHandler implements LogHandler
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ausgabeformat setzen
|
||||
*/
|
||||
public function setOutputFormat(string $format): self
|
||||
{
|
||||
$this->outputFormat = $format;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatiert Structured Logging Extras für Console-Ausgabe mit Farben
|
||||
*/
|
||||
private function formatStructuredExtras(LogRecord $record): string
|
||||
{
|
||||
$parts = [];
|
||||
$reset = ConsoleColor::RESET->toAnsi();
|
||||
|
||||
// Tags anzeigen (Cyan mit Tag-Symbol)
|
||||
if ($record->hasExtra('structured_tags')) {
|
||||
$tags = $record->getExtra('structured_tags');
|
||||
if (! empty($tags)) {
|
||||
$cyan = ConsoleColor::CYAN->toAnsi();
|
||||
$tagString = implode(',', $tags);
|
||||
$parts[] = "{$cyan}🏷 [{$tagString}]{$reset}";
|
||||
}
|
||||
}
|
||||
|
||||
// Trace-Kontext anzeigen (Blau mit Trace-Symbol)
|
||||
if ($record->hasExtra('trace_context')) {
|
||||
$traceContext = $record->getExtra('trace_context');
|
||||
$blue = ConsoleColor::BLUE->toAnsi();
|
||||
|
||||
if (isset($traceContext['trace_id'])) {
|
||||
$traceId = substr($traceContext['trace_id'], 0, 8);
|
||||
$parts[] = "{$blue}🔍 {$traceId}{$reset}";
|
||||
}
|
||||
if (isset($traceContext['active_span']['spanId'])) {
|
||||
$spanId = substr($traceContext['active_span']['spanId'], 0, 8);
|
||||
$parts[] = "{$blue}↳ {$spanId}{$reset}";
|
||||
}
|
||||
}
|
||||
|
||||
// User-Kontext anzeigen (Grün mit User-Symbol)
|
||||
if ($record->hasExtra('user_context')) {
|
||||
$userContext = $record->getExtra('user_context');
|
||||
$green = ConsoleColor::GREEN->toAnsi();
|
||||
|
||||
if (isset($userContext['user_id'])) {
|
||||
// Anonymisierte User-ID für Privacy
|
||||
$userId = substr(md5($userContext['user_id']), 0, 8);
|
||||
$parts[] = "{$green}👤 {$userId}{$reset}";
|
||||
} elseif (isset($userContext['is_authenticated']) && ! $userContext['is_authenticated']) {
|
||||
$parts[] = "{$green}👤 anon{$reset}";
|
||||
}
|
||||
}
|
||||
|
||||
// Request-Kontext anzeigen (Gelb mit HTTP-Symbol)
|
||||
if ($record->hasExtra('request_context')) {
|
||||
$requestContext = $record->getExtra('request_context');
|
||||
if (isset($requestContext['request_method'], $requestContext['request_uri'])) {
|
||||
$yellow = ConsoleColor::YELLOW->toAnsi();
|
||||
$method = $requestContext['request_method'];
|
||||
$uri = $requestContext['request_uri'];
|
||||
|
||||
// Kompakte URI-Darstellung
|
||||
if (strlen($uri) > 25) {
|
||||
$uri = substr($uri, 0, 22) . '...';
|
||||
}
|
||||
|
||||
$parts[] = "{$yellow}🌐 {$method} {$uri}{$reset}";
|
||||
}
|
||||
}
|
||||
|
||||
// Context-Data anzeigen (Grau mit Data-Symbol)
|
||||
$context = $record->getContext();
|
||||
if (! empty($context)) {
|
||||
$contextKeys = array_keys($context);
|
||||
// Interne Keys ausfiltern
|
||||
$contextKeys = array_filter($contextKeys, fn ($key) => ! str_starts_with($key, '_'));
|
||||
|
||||
if (! empty($contextKeys)) {
|
||||
$gray = ConsoleColor::GRAY->toAnsi();
|
||||
$keyCount = count($contextKeys);
|
||||
|
||||
if ($keyCount <= 3) {
|
||||
$keyString = implode('·', $contextKeys);
|
||||
} else {
|
||||
$keyString = implode('·', array_slice($contextKeys, 0, 2)) . "·+{$keyCount}";
|
||||
}
|
||||
$parts[] = "{$gray}📊 {$keyString}{$reset}";
|
||||
}
|
||||
}
|
||||
|
||||
return empty($parts) ? '' : "\n " . implode(' ', $parts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,9 @@ declare(strict_types=1);
|
||||
namespace App\Framework\Logging\Handlers;
|
||||
|
||||
use App\Framework\Config\Environment;
|
||||
use App\Framework\Logging\FormattableHandler;
|
||||
use App\Framework\Logging\Formatter\JsonFormatter;
|
||||
use App\Framework\Logging\LogHandler;
|
||||
use App\Framework\Logging\Formatter\LogFormatter;
|
||||
use App\Framework\Logging\LogLevel;
|
||||
use App\Framework\Logging\LogRecord;
|
||||
use App\Framework\Logging\Security\SensitiveDataRedactor;
|
||||
@@ -33,7 +34,7 @@ use App\Framework\Logging\Security\SensitiveDataRedactor;
|
||||
* - docker logs <container> 2>&1 | jq 'select(.level == "ERROR")'
|
||||
* - docker logs <container> 2>&1 | jq -r '[.timestamp, .level, .message] | @tsv'
|
||||
*/
|
||||
final readonly class DockerJsonHandler implements LogHandler
|
||||
final readonly class DockerJsonHandler implements FormattableHandler
|
||||
{
|
||||
private JsonFormatter $formatter;
|
||||
private LogLevel $minLevel;
|
||||
@@ -79,6 +80,14 @@ final readonly class DockerJsonHandler implements LogHandler
|
||||
echo $json . PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den Formatter zurück
|
||||
*/
|
||||
public function getFormatter(): LogFormatter
|
||||
{
|
||||
return $this->formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt minimales Log-Level
|
||||
*/
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Framework\Logging\Handlers;
|
||||
|
||||
use App\Framework\Core\PathProvider;
|
||||
use App\Framework\Logging\Formatter\LogFormatter;
|
||||
use App\Framework\Logging\LogHandler;
|
||||
use App\Framework\Logging\LogLevel;
|
||||
use App\Framework\Logging\LogRecord;
|
||||
@@ -26,9 +27,9 @@ class FileHandler implements LogHandler
|
||||
private string $logFile;
|
||||
|
||||
/**
|
||||
* @var string Format für die Ausgabe
|
||||
* @var LogFormatter Formatter für die Log-Ausgabe
|
||||
*/
|
||||
private string $outputFormat;
|
||||
private LogFormatter $formatter;
|
||||
|
||||
/**
|
||||
* @var int Datei-Modi für die Log-Datei
|
||||
@@ -48,22 +49,23 @@ class FileHandler implements LogHandler
|
||||
/**
|
||||
* Erstellt einen neuen FileHandler
|
||||
*
|
||||
* @param LogFormatter $formatter Formatter für die Log-Ausgabe
|
||||
* @param string $logFile Pfad zur Log-Datei
|
||||
* @param LogLevel|int $minLevel Minimales Level, ab dem dieser Handler aktiv wird
|
||||
* @param string $outputFormat Format für die Ausgabe
|
||||
* @param int $fileMode Datei-Modi für die Log-Datei
|
||||
* @param LogRotator|null $rotator Optional: Log-Rotator für automatische Rotation
|
||||
* @param PathProvider|null $pathProvider Optional: PathProvider für die Auflösung von Pfaden
|
||||
*/
|
||||
public function __construct(
|
||||
LogFormatter $formatter,
|
||||
string $logFile,
|
||||
LogLevel|int $minLevel = LogLevel::DEBUG,
|
||||
string $outputFormat = '[{timestamp}] [{level_name}] {request_id}{channel}{message}',
|
||||
int $fileMode = 0644,
|
||||
?LogRotator $rotator = null,
|
||||
?PathProvider $pathProvider = null
|
||||
) {
|
||||
$this->pathProvider = $pathProvider;
|
||||
$this->formatter = $formatter;
|
||||
|
||||
// Pfad auflösen, falls PathProvider vorhanden
|
||||
if ($this->pathProvider !== null && ! str_starts_with($logFile, '/')) {
|
||||
@@ -72,7 +74,6 @@ class FileHandler implements LogHandler
|
||||
|
||||
$this->logFile = $logFile;
|
||||
$this->minLevel = $minLevel instanceof LogLevel ? $minLevel : LogLevel::fromValue($minLevel);
|
||||
$this->outputFormat = $outputFormat;
|
||||
$this->fileMode = $fileMode;
|
||||
$this->rotator = $rotator;
|
||||
|
||||
@@ -93,27 +94,13 @@ class FileHandler implements LogHandler
|
||||
*/
|
||||
public function handle(LogRecord $record): void
|
||||
{
|
||||
// Request-ID-Teil erstellen, falls vorhanden
|
||||
$requestId = $record->hasExtra('request_id')
|
||||
? "[{$record->getExtra('request_id')}] "
|
||||
: '';
|
||||
// Formatter verwenden für Formatierung
|
||||
$formatted = ($this->formatter)($record);
|
||||
|
||||
// Channel-Teil erstellen, falls vorhanden
|
||||
$channel = $record->getChannel()
|
||||
? "[{$record->getChannel()}] "
|
||||
: '';
|
||||
|
||||
// Werte für Platzhalter im Format
|
||||
$values = [
|
||||
'{level_name}' => $record->getLevel()->getName(),
|
||||
'{timestamp}' => $record->getFormattedTimestamp(),
|
||||
'{request_id}' => $requestId,
|
||||
'{channel}' => $channel,
|
||||
'{message}' => $record->getMessage(),
|
||||
];
|
||||
|
||||
// Formatierte Ausgabe erstellen
|
||||
$output = strtr($this->outputFormat, $values) . PHP_EOL;
|
||||
// Formatter kann string oder array zurückgeben
|
||||
$output = is_string($formatted)
|
||||
? $formatted . PHP_EOL
|
||||
: json_encode($formatted, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . PHP_EOL;
|
||||
|
||||
// Prüfe Rotation vor dem Schreiben
|
||||
if ($this->rotator !== null) {
|
||||
@@ -129,6 +116,14 @@ class FileHandler implements LogHandler
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den Formatter zurück
|
||||
*/
|
||||
public function getFormatter(): LogFormatter
|
||||
{
|
||||
return $this->formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schreibt einen String in die Log-Datei
|
||||
*/
|
||||
@@ -167,15 +162,6 @@ class FileHandler implements LogHandler
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ausgabeformat setzen
|
||||
*/
|
||||
public function setOutputFormat(string $format): self
|
||||
{
|
||||
$this->outputFormat = $format;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log-Datei setzen
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,9 +5,10 @@ declare(strict_types=1);
|
||||
namespace App\Framework\Logging\Handlers;
|
||||
|
||||
use App\Framework\Core\PathProvider;
|
||||
use App\Framework\Logging\LogHandler;
|
||||
use App\Framework\Logging\Formatter\LogFormatter;
|
||||
use App\Framework\Logging\LogChannel;
|
||||
use App\Framework\Logging\LogConfig;
|
||||
use App\Framework\Logging\LogHandler;
|
||||
use App\Framework\Logging\LogLevel;
|
||||
use App\Framework\Logging\LogRecord;
|
||||
|
||||
@@ -24,13 +25,19 @@ final class MultiFileHandler implements LogHandler
|
||||
*/
|
||||
private array $fileHandles = [];
|
||||
|
||||
/**
|
||||
* @var LogFormatter Formatter für die Log-Ausgabe
|
||||
*/
|
||||
private LogFormatter $formatter;
|
||||
|
||||
public function __construct(
|
||||
private readonly mixed $logConfig,
|
||||
private readonly mixed $pathProvider,
|
||||
private readonly LogConfig $logConfig,
|
||||
private readonly PathProvider $pathProvider,
|
||||
LogFormatter $formatter,
|
||||
private readonly LogLevel $minLevel = LogLevel::DEBUG,
|
||||
private readonly string $outputFormat = '[{timestamp}] [{level_name}] [{channel}] {message}',
|
||||
private readonly int $fileMode = 0644
|
||||
) {
|
||||
$this->formatter = $formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,13 +63,31 @@ final class MultiFileHandler implements LogHandler
|
||||
// Entsprechende Log-Datei ermitteln
|
||||
$logFile = $this->getLogFileForChannel($channel);
|
||||
|
||||
// Log-Nachricht formatieren
|
||||
$formattedMessage = $this->formatMessage($record);
|
||||
// Formatter verwenden für Formatierung
|
||||
$formatted = ($this->formatter)($record);
|
||||
|
||||
// Formatter kann string oder array zurückgeben
|
||||
$formattedMessage = is_string($formatted)
|
||||
? $formatted
|
||||
: json_encode($formatted, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
// Stelle sicher, dass ein Newline vorhanden ist
|
||||
if (!str_ends_with($formattedMessage, PHP_EOL)) {
|
||||
$formattedMessage .= PHP_EOL;
|
||||
}
|
||||
|
||||
// In Datei schreiben
|
||||
$this->writeToFile($logFile, $formattedMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den Formatter zurück
|
||||
*/
|
||||
public function getFormatter(): LogFormatter
|
||||
{
|
||||
return $this->formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ermittelt die Log-Datei für einen Channel
|
||||
*/
|
||||
@@ -81,29 +106,6 @@ final class MultiFileHandler implements LogHandler
|
||||
return $this->logConfig->getLogPath($logPathKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatiert die Log-Nachricht
|
||||
*/
|
||||
private function formatMessage(LogRecord $record): string
|
||||
{
|
||||
$replacements = [
|
||||
'{timestamp}' => $record->getFormattedTimestamp('Y-m-d H:i:s'),
|
||||
'{level_name}' => $record->getLevel()->getName(),
|
||||
'{channel}' => $record->getChannel() ?? 'app',
|
||||
'{message}' => $record->getMessage(),
|
||||
];
|
||||
|
||||
$formatted = str_replace(array_keys($replacements), array_values($replacements), $this->outputFormat);
|
||||
|
||||
// Context hinzufügen, falls vorhanden
|
||||
$context = $record->getContext();
|
||||
if (! empty($context)) {
|
||||
$formatted .= ' ' . json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
return $formatted . PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schreibt die formatierte Nachricht in eine Datei
|
||||
*/
|
||||
|
||||
@@ -26,8 +26,9 @@ final readonly class QueuedLogHandler implements LogHandler
|
||||
|
||||
public function handle(LogRecord $record): void
|
||||
{
|
||||
$job = new ProcessLogCommand($record);
|
||||
$payload = JobPayload::immediate($job);
|
||||
$this->queue->push($payload);
|
||||
var_dump('whould be queued');
|
||||
#$job = new ProcessLogCommand($record);
|
||||
#$payload = JobPayload::immediate($job);
|
||||
#$this->queue->push($payload);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user