Files
michaelschiemer/src/Framework/Logging/Handlers/RotatingFileHandler.php
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

340 lines
9.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\Logging\Handlers;
use App\Framework\Core\PathProvider;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Logging\LogLevel;
use App\Framework\Logging\LogRecord;
use App\Framework\Logging\LogRotator;
/**
* Handler für Log-Rotation mit Size- und Time-based Strategien.
*
* Erweitert FileHandler mit automatischer Log-Rotation basierend auf:
* - Dateigröße (via LogRotator)
* - Zeit (täglich, wöchentlich, monatlich)
*
* Verwendung:
* ```php
* // Size-based rotation (10MB, 5 files)
* $handler = RotatingFileHandler::withSizeRotation(
* 'storage/logs/app.log',
* maxFileSize: Byte::fromMegabytes(10),
* maxFiles: 5
* );
*
* // Time-based rotation (täglich)
* $handler = RotatingFileHandler::daily('storage/logs/app.log');
*
* // Kombiniert (Size + Time)
* $handler = RotatingFileHandler::daily('storage/logs/app.log')
* ->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
];
}
}