Files
michaelschiemer/src/Framework/Logging/DefaultLogger.php

198 lines
6.4 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\Logging;
use App\Framework\Attributes\Singleton;
use App\Framework\DateTime\Clock;
use App\Framework\Logging\ValueObjects\LogContext;
use DateMalformedStringException;
/**
* Einfacher Logger für das Framework.
*/
#[Singleton]
final readonly class DefaultLogger implements Logger, SupportsChannels
{
private ChannelLoggerRegistry $channelRegistry;
/**
* @param Clock $clock Clock für Timestamp-Generierung
* @param LogLevel $minLevel Minimales Level, das geloggt werden soll
* @param array<LogHandler> $handlers Array von Log-Handlern
* @param ProcessorManager $processorManager Processor Manager für die Verarbeitung
* @param LogContextManager|null $contextManager Optional: Context Manager für automatische Kontext-Anreicherung
*/
public function __construct(
private Clock $clock,
private LogLevel $minLevel = LogLevel::DEBUG,
/** @var LogHandler[] */
private array $handlers = [],
private ProcessorManager $processorManager = new ProcessorManager(),
private ?LogContextManager $contextManager = null,
) {
// Channel-Logger-Registry initialisieren
$this->channelRegistry = new ChannelLoggerRegistry($this);
}
public function debug(string $message, ?LogContext $context = null): void
{
$this->log(LogLevel::DEBUG, $message, $context);
}
public function info(string $message, ?LogContext $context = null): void
{
$this->log(LogLevel::INFO, $message, $context);
}
public function notice(string $message, ?LogContext $context = null): void
{
$this->log(LogLevel::NOTICE, $message, $context);
}
public function warning(string $message, ?LogContext $context = null): void
{
$this->log(LogLevel::WARNING, $message, $context);
}
public function error(string $message, ?LogContext $context = null): void
{
$this->log(LogLevel::ERROR, $message, $context);
}
public function critical(string $message, ?LogContext $context = null): void
{
$this->log(LogLevel::CRITICAL, $message, $context);
}
public function alert(string $message, ?LogContext $context = null): void
{
$this->log(LogLevel::ALERT, $message, $context);
}
public function emergency(string $message, ?LogContext $context = null): void
{
$this->log(LogLevel::EMERGENCY, $message, $context);
}
/**
* Log-Nachricht mit beliebigem Level erstellen
*
* @param LogLevel $level Log-Level
* @param string $message Log-Nachricht
* @param LogContext|null $context Strukturierter LogContext
* @throws DateMalformedStringException
*/
public function log(LogLevel $level, string $message, ?LogContext $context = null): void
{
$this->createAndProcessRecord($level, $message, $context);
}
/**
* Loggt in einen spezifischen Channel
*
* @internal Wird von ChannelLogger verwendet
*/
public function logToChannel(LogChannel $channel, LogLevel $level, string $message, ?LogContext $context = null): void
{
$this->createAndProcessRecord($level, $message, $context, $channel->value);
}
/**
* Erstellt und verarbeitet einen Log-Record
*
* @param LogLevel $level Log-Level
* @param string $message Log-Nachricht
* @param LogContext|null $context Strukturierter LogContext
* @param string|null $channel Optional: Channel-Name
* @throws DateMalformedStringException
*/
private function createAndProcessRecord(LogLevel $level, string $message, ?LogContext $context = null, ?string $channel = null): void
{
// Wenn kein Context übergeben, leeren Context erstellen
if ($context === null) {
$context = LogContext::empty();
}
// Prüfen ob Level hoch genug ist
if ($level->isLowerThan($this->minLevel)) {
return;
}
// LogContext automatisch mit aktuellem Context anreichern
$finalContext = $this->enrichWithCurrentContext($context);
// Log-Record erstellen
$record = new LogRecord(
message: $message,
context: $finalContext,
level: $level,
timestamp: $this->clock->now(),
channel: $channel
);
// Record durch alle Processors verarbeiten
$processedRecord = $this->processorManager->processRecord($record);
// Alle Handler durchlaufen
foreach ($this->handlers as $handler) {
if ($handler->isHandling($processedRecord)) {
$array = explode('\\',$handler::class);
$handlerName = end($array);
// Log-Record erstellen
$record = new LogRecord(
message: $handlerName . ' --- ' . $message,
context: $finalContext,
level: $level,
timestamp: $this->clock->now(),
channel: $channel
);
$processedRecord = $this->processorManager->processRecord($record);
$handler->handle($processedRecord);
}
}
}
/**
* Reichert den übergebenen Context mit dem aktuellen Context vom LogContextManager an
*/
private function enrichWithCurrentContext(LogContext $context): LogContext
{
// Wenn kein ContextManager verfügbar ist, Context unverändert zurückgeben
if ($this->contextManager === null) {
return $context;
}
$currentContext = $this->contextManager->getCurrentContext();
// Wenn der übergebene Context ein LogContext ist, mit aktuellem Context mergen
return $currentContext->merge($context);
}
/**
* Holt einen ChannelLogger für einen spezifischen Channel
*/
public function channel(LogChannel|string $channel): Logger&HasChannel
{
return $this->channelRegistry->get($channel);
}
/**
* Gibt die aktuelle Konfiguration des Loggers zurück
*/
public function getConfiguration(): array
{
return [
'minLevel' => $this->minLevel->value,
'handlers' => array_map(fn (LogHandler $h) => get_class($h), $this->handlers),
'processors' => $this->processorManager->getProcessorList(),
'registeredChannels' => $this->channelRegistry->getRegisteredChannels(),
];
}
}