withMaxSize(Byte::fromMegabytes(50)); * ``` */ final class RotatingFileHandler extends FileHandler { private ?string $rotationFrequency = null; private ?int $lastRotationCheck = null; /** * Factory: Size-based Rotation */ public static function withSizeRotation( string $logFile, Byte $maxFileSize = new Byte(10 * 1024 * 1024), // 10MB default int $maxFiles = 5, bool $compress = true, LogLevel|int $minLevel = LogLevel::DEBUG, ?PathProvider $pathProvider = null ): self { $rotator = new LogRotator($maxFileSize, $maxFiles, $compress); return new self( logFile: $logFile, minLevel: $minLevel, rotator: $rotator, pathProvider: $pathProvider ); } /** * Factory: Daily Rotation (täglich um Mitternacht) */ public static function daily( string $logFile, int $maxFiles = 7, bool $compress = true, LogLevel|int $minLevel = LogLevel::DEBUG, ?PathProvider $pathProvider = null ): self { $rotator = new LogRotator( maxFileSize: Byte::fromMegabytes(50), maxFiles: $maxFiles, compress: $compress ); $handler = new self( logFile: $logFile, minLevel: $minLevel, rotator: $rotator, pathProvider: $pathProvider ); $handler->rotationFrequency = 'daily'; return $handler; } /** * Factory: Weekly Rotation (wöchentlich am Montag) */ public static function weekly( string $logFile, int $maxFiles = 4, bool $compress = true, LogLevel|int $minLevel = LogLevel::DEBUG, ?PathProvider $pathProvider = null ): self { $rotator = new LogRotator( maxFileSize: Byte::fromMegabytes(100), maxFiles: $maxFiles, compress: $compress ); $handler = new self( logFile: $logFile, minLevel: $minLevel, rotator: $rotator, pathProvider: $pathProvider ); $handler->rotationFrequency = 'weekly'; return $handler; } /** * Factory: Monthly Rotation (monatlich am 1. des Monats) */ public static function monthly( string $logFile, int $maxFiles = 12, bool $compress = true, LogLevel|int $minLevel = LogLevel::DEBUG, ?PathProvider $pathProvider = null ): self { $rotator = new LogRotator( maxFileSize: Byte::fromMegabytes(200), maxFiles: $maxFiles, compress: $compress ); $handler = new self( logFile: $logFile, minLevel: $minLevel, rotator: $rotator, pathProvider: $pathProvider ); $handler->rotationFrequency = 'monthly'; return $handler; } /** * Factory: Production-optimized (25MB, 10 files, täglich) */ public static function production( string $logFile, LogLevel|int $minLevel = LogLevel::INFO, ?PathProvider $pathProvider = null ): self { $rotator = LogRotator::production(); $handler = new self( logFile: $logFile, minLevel: $minLevel, rotator: $rotator, pathProvider: $pathProvider ); $handler->rotationFrequency = 'daily'; return $handler; } /** * Verarbeitet einen Log-Eintrag mit Time-based Rotation */ public function handle(LogRecord $record): void { // Time-based Rotation prüfen (vor Parent-Handle) if ($this->rotationFrequency !== null) { $this->checkTimeBasedRotation(); } // Parent FileHandler handle() führt Size-based Rotation aus parent::handle($record); } /** * Prüft ob Time-based Rotation notwendig ist * * Diese Methode wird direkt vor handle() aufgerufen und rotiert die Log-Datei, * falls die Zeit-basierte Rotation-Bedingung erfüllt ist. * Der Parent FileHandler hat Zugriff auf logFile und rotator via reflection. */ private function checkTimeBasedRotation(): void { $currentTime = time(); // Cache Rotation-Check für Performance (max. 1x pro Minute) if ($this->lastRotationCheck !== null && $currentTime - $this->lastRotationCheck < 60) { return; } $this->lastRotationCheck = $currentTime; $shouldRotate = match ($this->rotationFrequency) { 'daily' => $this->shouldRotateDaily(), 'weekly' => $this->shouldRotateWeekly(), 'monthly' => $this->shouldRotateMonthly(), default => false }; // Nur rotieren wenn Rotation-Bedingung erfüllt ist // Parent FileHandler hat den LogRotator, wir müssen ihn nicht direkt aufrufen if ($shouldRotate) { // Force rotation durch setzen einer sehr kleinen max file size temporär // Der Parent FileHandler wird dann beim nächsten handle() rotieren $this->triggerRotation(); } } /** * Triggert eine Rotation durch Manipulation des Rotation-Status */ private function triggerRotation(): void { // Nutze Reflection um auf private logFile Property zuzugreifen $reflection = new \ReflectionClass(parent::class); $logFileProperty = $reflection->getProperty('logFile'); $logFile = $logFileProperty->getValue($this); $rotatorProperty = $reflection->getProperty('rotator'); $rotator = $rotatorProperty->getValue($this); // Rotiere direkt wenn Rotator vorhanden if ($rotator instanceof LogRotator && file_exists($logFile)) { $rotator->rotateLog($logFile); } } /** * Prüft ob Daily Rotation notwendig ist */ private function shouldRotateDaily(): bool { $logFile = $this->getLogFilePath(); if (!file_exists($logFile)) { return false; } $fileDate = date('Y-m-d', filemtime($logFile)); $currentDate = date('Y-m-d'); // Rotiere wenn Log-Datei von gestern oder älter return $fileDate < $currentDate; } /** * Prüft ob Weekly Rotation notwendig ist */ private function shouldRotateWeekly(): bool { $logFile = $this->getLogFilePath(); if (!file_exists($logFile)) { return false; } $fileWeek = date('Y-W', filemtime($logFile)); $currentWeek = date('Y-W'); // Rotiere wenn Log-Datei von letzter Woche oder älter return $fileWeek < $currentWeek; } /** * Prüft ob Monthly Rotation notwendig ist */ private function shouldRotateMonthly(): bool { $logFile = $this->getLogFilePath(); if (!file_exists($logFile)) { return false; } $fileMonth = date('Y-m', filemtime($logFile)); $currentMonth = date('Y-m'); // Rotiere wenn Log-Datei von letztem Monat oder älter return $fileMonth < $currentMonth; } /** * Holt Log-File-Pfad via Reflection */ private function getLogFilePath(): string { $reflection = new \ReflectionClass(parent::class); $logFileProperty = $reflection->getProperty('logFile'); return $logFileProperty->getValue($this); } /** * Setzt maximale Dateigröße (für kombinierte Size + Time Rotation) */ public function withMaxSize(Byte $maxSize, int $maxFiles = 5, bool $compress = true): self { // Setze Rotator via Reflection um dynamic property warning zu vermeiden $reflection = new \ReflectionClass(parent::class); $rotatorProperty = $reflection->getProperty('rotator'); $newRotator = new LogRotator( maxFileSize: $maxSize, maxFiles: $maxFiles, compress: $compress ); $rotatorProperty->setValue($this, $newRotator); return $this; } /** * Gibt Rotation-Strategie-Info zurück */ public function getRotationStrategy(): array { // Prüfe via Reflection ob Rotator im Parent gesetzt ist $reflection = new \ReflectionClass(parent::class); $rotatorProperty = $reflection->getProperty('rotator'); $rotator = $rotatorProperty->getValue($this); return [ 'time_based' => $this->rotationFrequency ?? 'none', 'size_based' => $rotator !== null, 'last_check' => $this->lastRotationCheck ? date('Y-m-d H:i:s', $this->lastRotationCheck) : null ]; } }