chore: complete update
This commit is contained in:
123
src/Framework/Logging/Handlers/ConsoleHandler.php
Normal file
123
src/Framework/Logging/Handlers/ConsoleHandler.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Logging\Handlers;
|
||||
|
||||
use App\Framework\Console\ConsoleColor;
|
||||
use App\Framework\Logging\LogHandler;
|
||||
use App\Framework\Logging\LogLevel;
|
||||
use App\Framework\Logging\LogRecord;
|
||||
|
||||
/**
|
||||
* Handler für die Ausgabe von Log-Einträgen in der Konsole.
|
||||
*/
|
||||
class ConsoleHandler implements LogHandler
|
||||
{
|
||||
/**
|
||||
* @var LogLevel Minimales Level, ab dem dieser Handler aktiv wird
|
||||
*/
|
||||
private LogLevel $minLevel;
|
||||
|
||||
/**
|
||||
* @var bool Ob der Handler nur im Debug-Modus aktiv ist
|
||||
*/
|
||||
private bool $debugOnly;
|
||||
|
||||
/**
|
||||
* @var string Format für die Ausgabe
|
||||
*/
|
||||
private string $outputFormat;
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen ConsoleHandler
|
||||
*
|
||||
* @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}',
|
||||
private LogLevel $stderrLevel = LogLevel::WARNING,
|
||||
) {
|
||||
$this->minLevel = $minLevel instanceof LogLevel ? $minLevel : LogLevel::fromValue($minLevel);
|
||||
$this->debugOnly = $debugOnly;
|
||||
$this->outputFormat = $outputFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Überprüft, ob dieser Handler den Log-Eintrag verarbeiten soll
|
||||
*/
|
||||
public function isHandling(LogRecord $record): bool
|
||||
{
|
||||
// Nur im CLI-Modus aktiv - NIE bei Web-Requests!
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Optional: Debug-Modus-Check nur in CLI
|
||||
if ($this->debugOnly && !filter_var(getenv('APP_DEBUG'), FILTER_VALIDATE_BOOLEAN)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return $record->getLevel()->value >= $this->minLevel->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verarbeitet einen Log-Eintrag
|
||||
*/
|
||||
public function handle(LogRecord $record): void
|
||||
{
|
||||
$logLevel = $record->getLevel();
|
||||
$color = $logLevel->getConsoleColor()->value;
|
||||
$reset = ConsoleColor::RESET->value;
|
||||
|
||||
// Request-ID-Teil erstellen, falls vorhanden
|
||||
$requestId = $record->hasExtra('request_id')
|
||||
? "[{$record->getExtra('request_id')}] "
|
||||
: '';
|
||||
|
||||
// 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()}] " : '',
|
||||
];
|
||||
|
||||
// Formatierte Ausgabe erstellen
|
||||
$output = strtr($this->outputFormat, $values) . PHP_EOL;
|
||||
|
||||
// Fehler und Warnungen auf stderr, alles andere auf stdout
|
||||
if ($record->getLevel()->value >= $this->stderrLevel->value) {
|
||||
// WARNING, ERROR, CRITICAL, ALERT, EMERGENCY -> stderr
|
||||
file_put_contents('php://stderr', $output);
|
||||
} else {
|
||||
// DEBUG, INFO, NOTICE -> stdout
|
||||
echo $output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimales Log-Level setzen
|
||||
*/
|
||||
public function setMinLevel(LogLevel|int $level): self
|
||||
{
|
||||
$this->minLevel = $level instanceof LogLevel ? $level : LogLevel::fromValue($level);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ausgabeformat setzen
|
||||
*/
|
||||
public function setOutputFormat(string $format): self
|
||||
{
|
||||
$this->outputFormat = $format;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
157
src/Framework/Logging/Handlers/FileHandler.php
Normal file
157
src/Framework/Logging/Handlers/FileHandler.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Logging\Handlers;
|
||||
|
||||
use App\Framework\Logging\LogHandler;
|
||||
use App\Framework\Logging\LogLevel;
|
||||
use App\Framework\Logging\LogRecord;
|
||||
|
||||
/**
|
||||
* Handler für die Ausgabe von Log-Einträgen in Dateien.
|
||||
*/
|
||||
class FileHandler implements LogHandler
|
||||
{
|
||||
/**
|
||||
* @var LogLevel Minimales Level, ab dem dieser Handler aktiv wird
|
||||
*/
|
||||
private LogLevel $minLevel;
|
||||
|
||||
/**
|
||||
* @var string Pfad zur Log-Datei
|
||||
*/
|
||||
private string $logFile;
|
||||
|
||||
/**
|
||||
* @var string Format für die Ausgabe
|
||||
*/
|
||||
private string $outputFormat;
|
||||
|
||||
/**
|
||||
* @var int Datei-Modi für die Log-Datei
|
||||
*/
|
||||
private int $fileMode;
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen FileHandler
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
public function __construct(
|
||||
string $logFile,
|
||||
LogLevel|int $minLevel = LogLevel::DEBUG,
|
||||
string $outputFormat = '[{timestamp}] [{level_name}] {request_id}{channel}{message}',
|
||||
int $fileMode = 0644
|
||||
) {
|
||||
$this->logFile = $logFile;
|
||||
$this->minLevel = $minLevel instanceof LogLevel ? $minLevel : LogLevel::fromValue($minLevel);
|
||||
$this->outputFormat = $outputFormat;
|
||||
$this->fileMode = $fileMode;
|
||||
|
||||
// Stelle sicher, dass das Verzeichnis existiert
|
||||
$this->ensureDirectoryExists(dirname($logFile));
|
||||
}
|
||||
|
||||
/**
|
||||
* Überprüft, ob dieser Handler den Log-Eintrag verarbeiten soll
|
||||
*/
|
||||
public function isHandling(LogRecord $record): bool
|
||||
{
|
||||
return $record->getLevel()->value >= $this->minLevel->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verarbeitet einen Log-Eintrag
|
||||
*/
|
||||
public function handle(LogRecord $record): void
|
||||
{
|
||||
// Request-ID-Teil erstellen, falls vorhanden
|
||||
$requestId = $record->hasExtra('request_id')
|
||||
? "[{$record->getExtra('request_id')}] "
|
||||
: '';
|
||||
|
||||
// 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;
|
||||
|
||||
// In die Datei schreiben
|
||||
$this->write($output);
|
||||
|
||||
// Bei kritischen Fehlern Cache leeren, um sicherzustellen, dass der Eintrag geschrieben wird
|
||||
if ($record->getLevel()->value >= LogLevel::CRITICAL->value) {
|
||||
$this->flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schreibt einen String in die Log-Datei
|
||||
*/
|
||||
protected function write(string $output): void
|
||||
{
|
||||
file_put_contents($this->logFile, $output, FILE_APPEND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Leert Cache und stellt sicher, dass alles geschrieben wurde
|
||||
*/
|
||||
protected function flush(): void
|
||||
{
|
||||
if (function_exists('opcache_reset')) {
|
||||
opcache_reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stellt sicher, dass ein Verzeichnis existiert
|
||||
*/
|
||||
private function ensureDirectoryExists(string $dir): void
|
||||
{
|
||||
if (!file_exists($dir)) {
|
||||
mkdir($dir, 0777, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimales Log-Level setzen
|
||||
*/
|
||||
public function setMinLevel(LogLevel|int $level): self
|
||||
{
|
||||
$this->minLevel = $level instanceof LogLevel ? $level : LogLevel::fromValue($level);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ausgabeformat setzen
|
||||
*/
|
||||
public function setOutputFormat(string $format): self
|
||||
{
|
||||
$this->outputFormat = $format;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log-Datei setzen
|
||||
*/
|
||||
public function setLogFile(string $logFile): self
|
||||
{
|
||||
$this->logFile = $logFile;
|
||||
$this->ensureDirectoryExists(dirname($logFile));
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
137
src/Framework/Logging/Handlers/JsonFileHandler.php
Normal file
137
src/Framework/Logging/Handlers/JsonFileHandler.php
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Logging\Handlers;
|
||||
|
||||
use App\Framework\Logging\LogHandler;
|
||||
use App\Framework\Logging\LogLevel;
|
||||
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.
|
||||
*/
|
||||
class JsonFileHandler implements LogHandler
|
||||
{
|
||||
/**
|
||||
* @var LogLevel Minimales Level, ab dem dieser Handler aktiv wird
|
||||
*/
|
||||
private LogLevel $minLevel;
|
||||
|
||||
/**
|
||||
* @var string Pfad zur Log-Datei
|
||||
*/
|
||||
private string $logFile;
|
||||
|
||||
/**
|
||||
* @var array Liste der Felder, die in der JSON-Ausgabe enthalten sein sollen
|
||||
*/
|
||||
private array $includedFields;
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen JsonFileHandler
|
||||
*
|
||||
* @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)
|
||||
*/
|
||||
public function __construct(
|
||||
string $logFile,
|
||||
LogLevel|int $minLevel = LogLevel::INFO,
|
||||
?array $includedFields = null
|
||||
) {
|
||||
$this->logFile = $logFile;
|
||||
$this->minLevel = $minLevel instanceof LogLevel ? $minLevel : LogLevel::fromValue($minLevel);
|
||||
|
||||
// Standardfelder, falls nicht anders angegeben
|
||||
$this->includedFields = $includedFields ?? [
|
||||
'timestamp',
|
||||
'level_name',
|
||||
'message',
|
||||
'context',
|
||||
'extra',
|
||||
'channel',
|
||||
];
|
||||
|
||||
// Stelle sicher, dass das Verzeichnis existiert
|
||||
$this->ensureDirectoryExists(dirname($logFile));
|
||||
}
|
||||
|
||||
/**
|
||||
* Überprüft, ob dieser Handler den Log-Eintrag verarbeiten soll
|
||||
*/
|
||||
public function isHandling(LogRecord $record): bool
|
||||
{
|
||||
return $record->getLevel()->value >= $this->minLevel->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verarbeitet einen Log-Eintrag
|
||||
*/
|
||||
public function handle(LogRecord $record): void
|
||||
{
|
||||
// Alle Daten des Records als Array holen
|
||||
$data = $record->toArray();
|
||||
|
||||
// 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;
|
||||
$this->write($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schreibt einen String in die Log-Datei
|
||||
*/
|
||||
protected function write(string $output): void
|
||||
{
|
||||
file_put_contents($this->logFile, $output, FILE_APPEND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stellt sicher, dass ein Verzeichnis existiert
|
||||
*/
|
||||
private function ensureDirectoryExists(string $dir): void
|
||||
{
|
||||
if (!file_exists($dir)) {
|
||||
mkdir($dir, 0777, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimales Log-Level setzen
|
||||
*/
|
||||
public function setMinLevel(LogLevel|int $level): self
|
||||
{
|
||||
$this->minLevel = $level instanceof LogLevel ? $level : LogLevel::fromValue($level);
|
||||
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
|
||||
*/
|
||||
public function setLogFile(string $logFile): self
|
||||
{
|
||||
$this->logFile = $logFile;
|
||||
$this->ensureDirectoryExists(dirname($logFile));
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
29
src/Framework/Logging/Handlers/QueuedLogHandler.php
Normal file
29
src/Framework/Logging/Handlers/QueuedLogHandler.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\Logging\Handlers;
|
||||
|
||||
use App\Framework\Database\DatabaseManager;
|
||||
use App\Framework\Logging\LogHandler;
|
||||
use App\Framework\Logging\LogLevel;
|
||||
use App\Framework\Logging\LogRecord;
|
||||
use App\Framework\Logging\ProcessLogCommand;
|
||||
use App\Framework\Queue\Queue;
|
||||
|
||||
final readonly class QueuedLogHandler implements LogHandler
|
||||
{
|
||||
public function __construct(
|
||||
private Queue $queue,
|
||||
#private LogLevel $minLevel = LogLevel::INFO,
|
||||
){}
|
||||
|
||||
public function isHandling(LogRecord $record): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handle(LogRecord $record): void
|
||||
{
|
||||
$job = new ProcessLogCommand($record);
|
||||
$this->queue->push($job);
|
||||
}
|
||||
}
|
||||
95
src/Framework/Logging/Handlers/SyslogHandler.php
Normal file
95
src/Framework/Logging/Handlers/SyslogHandler.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Logging\Handlers;
|
||||
|
||||
use App\Framework\Logging\LogHandler;
|
||||
use App\Framework\Logging\LogLevel;
|
||||
use App\Framework\Logging\LogRecord;
|
||||
|
||||
/**
|
||||
* Handler für Syslog-basiertes Logging
|
||||
*/
|
||||
final class SyslogHandler implements LogHandler
|
||||
{
|
||||
private bool $isOpen = false;
|
||||
|
||||
public function __construct(
|
||||
private readonly string $ident = 'php-app',
|
||||
private readonly int $facility = LOG_USER,
|
||||
private readonly LogLevel $minLevel = LogLevel::DEBUG
|
||||
) {}
|
||||
|
||||
public function isHandling(LogRecord $record): bool
|
||||
{
|
||||
return $record->getLevel()->value >= $this->minLevel->value;
|
||||
}
|
||||
|
||||
public function handle(LogRecord $record): void
|
||||
{
|
||||
$this->openSyslog();
|
||||
|
||||
$priority = $this->mapLogLevelToSyslogPriority($record->getLevel());
|
||||
$message = $this->formatMessage($record);
|
||||
|
||||
syslog($priority, $message);
|
||||
}
|
||||
|
||||
private function openSyslog(): void
|
||||
{
|
||||
if (!$this->isOpen) {
|
||||
openlog($this->ident, LOG_PID | LOG_PERROR, $this->facility);
|
||||
$this->isOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
private function formatMessage(LogRecord $record): string
|
||||
{
|
||||
$parts = [];
|
||||
|
||||
// Request-ID falls vorhanden
|
||||
if ($record->hasExtra('request_id')) {
|
||||
$parts[] = "[{$record->getExtra('request_id')}]";
|
||||
}
|
||||
|
||||
// Channel falls vorhanden
|
||||
if ($record->getChannel()) {
|
||||
$parts[] = "[{$record->getChannel()}]";
|
||||
}
|
||||
|
||||
// Hauptnachricht
|
||||
$parts[] = $record->getMessage();
|
||||
|
||||
// Context-Daten falls vorhanden
|
||||
$context = $record->getContext();
|
||||
if (!empty($context)) {
|
||||
$parts[] = 'Context: ' . json_encode($context, JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
return implode(' ', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mappt Framework LogLevel auf Syslog-Prioritäten
|
||||
*/
|
||||
private function mapLogLevelToSyslogPriority(LogLevel $level): int
|
||||
{
|
||||
return match ($level) {
|
||||
LogLevel::EMERGENCY => LOG_EMERG,
|
||||
LogLevel::ALERT => LOG_ALERT,
|
||||
LogLevel::CRITICAL => LOG_CRIT,
|
||||
LogLevel::ERROR => LOG_ERR,
|
||||
LogLevel::WARNING => LOG_WARNING,
|
||||
LogLevel::NOTICE => LOG_NOTICE,
|
||||
LogLevel::INFO => LOG_INFO,
|
||||
LogLevel::DEBUG => LOG_DEBUG,
|
||||
};
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->isOpen) {
|
||||
closelog();
|
||||
}
|
||||
}
|
||||
}
|
||||
66
src/Framework/Logging/Handlers/WebHandler.php
Normal file
66
src/Framework/Logging/Handlers/WebHandler.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Logging\Handlers;
|
||||
|
||||
use App\Framework\Logging\LogHandler;
|
||||
use App\Framework\Logging\LogLevel;
|
||||
use App\Framework\Logging\LogRecord;
|
||||
|
||||
final class WebHandler implements LogHandler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly LogLevel $minLevel = LogLevel::DEBUG,
|
||||
private readonly bool $debugOnly = true
|
||||
) {}
|
||||
|
||||
public function isHandling(LogRecord $record): bool
|
||||
{
|
||||
// Nur bei Web-Requests (nicht CLI)
|
||||
if (PHP_SAPI === 'cli') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Debug-Modus-Check
|
||||
if ($this->debugOnly && !filter_var(getenv('APP_DEBUG'), FILTER_VALIDATE_BOOLEAN)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $record->getLevel()->value >= $this->minLevel->value;
|
||||
}
|
||||
|
||||
public function handle(LogRecord $record): void
|
||||
{
|
||||
$timestamp = $record->getFormattedTimestamp();
|
||||
$level = $record->getLevel()->getName();
|
||||
$message = $record->getMessage();
|
||||
$channel = $record->getChannel();
|
||||
|
||||
// Request-ID falls vorhanden
|
||||
$requestId = $record->hasExtra('request_id')
|
||||
? "[{$record->getExtra('request_id')}] "
|
||||
: '';
|
||||
|
||||
// Channel falls vorhanden
|
||||
$channelPrefix = $channel ? "[$channel] " : '';
|
||||
|
||||
$logLine = sprintf(
|
||||
'[%s] %s[%s] %s%s',
|
||||
$timestamp,
|
||||
$requestId,
|
||||
$level,
|
||||
$channelPrefix,
|
||||
$message
|
||||
);
|
||||
|
||||
// Context-Daten falls vorhanden
|
||||
$context = $record->getContext();
|
||||
if (!empty($context)) {
|
||||
$logLine .= ' | Context: ' . json_encode($context, JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
// In error_log schreiben
|
||||
error_log($logLine);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user